''' 
LICENSING
-------------------------------------------------

daemoniker: Cross-platform daemonization tools.
    Copyright (C) 2016 Muterra, Inc.
    
    Contributors
    ------------
    Nick Badger 
        badg@muterra.io | badg@nickbadger.com | nickbadger.com

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the 
    Free Software Foundation, Inc.,
    51 Franklin Street, 
    Fifth Floor, 
    Boston, MA  02110-1301 USA

------------------------------------------------------
'''

# Global dependencies
import os
import sys
import signal
import logging
import atexit
import traceback
import shutil

# Intra-package dependencies
from .utils import platform_specificker
from .utils import default_to

from ._signals_common import _SighandlerCore

from .exceptions import ReceivedSignal
from .exceptions import SignalError
from .exceptions import SIGINT
from .exceptions import SIGTERM
from .exceptions import SIGABRT

_SUPPORTED_PLATFORM = platform_specificker(
    linux_choice = True,
    win_choice = False,
    cygwin_choice = False,
    osx_choice = True,
    # Dunno if this is a good idea but might as well try
    other_choice = True
)

if _SUPPORTED_PLATFORM:
    import fcntl
    import pwd
    import grp
    import resource


# ###############################################
# Boilerplate
# ###############################################


import logging
logger = logging.getLogger(__name__)

# Control * imports.
__all__ = [
    # 'Inquisitor', 
]


# ###############################################
# Library
# ###############################################
        
        
def _restore_any_previous_handler(signum, maybe_handler, force_clear=False):
    ''' Makes sure that a previous handler was actually set, and then
    restores it.
    
    maybe_handler is the cached potential handler. If it's the constant
    (ZeroDivisionError) we've been using to denote nothingness, then
    maybe_handler will either:
    
    1. do nothing, if force_clear == False
    2. restore signal.SIG_DEFL, if force_clear == True
    '''
    # Nope, wasn't previously set
    if maybe_handler == ZeroDivisionError:
        # Restore the default if we're forcing it
        if force_clear:
            signal.signal(signum, signal.SIG_DFL)
        # (Do nothing otherwise)
        
    # It was previously set, so re-set it.
    else:
        signal.signal(signum, maybe_handler)
    

class SignalHandler1(_SighandlerCore):
    ''' Signal handling system using lightweight wrapper around built-in
    signal.signal handling.
    '''
    def __init__(self, pid_file, sigint=None, sigterm=None, sigabrt=None):
        ''' Creates a signal handler, using the passed callables. None
        will assign the default handler (raise in main). passing
        IGNORE_SIGNAL constant will result in the signal being noop'd.
        '''
        self.sigint = sigint
        self.sigterm = sigterm
        self.sigabrt = sigabrt
        
        # Yeah, except this isn't used at all (just here for cross-platform
        # consistency)
        self._pidfile = pid_file
        
        # Assign these to impossible values so we can compare against them
        # later, when deciding whether or not to restore a previous handler.
        # Don't use None, in case it gets used to denote a default somewhere.
        # This is deliberately unconventional.
        self._old_sigint = ZeroDivisionError
        self._old_sigterm = ZeroDivisionError
        self._old_sigabrt = ZeroDivisionError
        
        self._running = False
        
    def start(self):
        ''' Starts signal handling.
        '''
        if self._running:
            raise RuntimeError('SignalHandler is already running.')
            
        # Initialize stuff to values that are impossible for signal.signal.
        # Don't use None, in case it gets used to denote a default somewhere.
        # This is deliberately unconventional.
        old_sigint = ZeroDivisionError
        old_sigterm = ZeroDivisionError
        old_sigabrt = ZeroDivisionError
        
        try:
            # First we need to make closures around all of our attributes, so
            # they can be updated after we start listening to signals
            def sigint_closure(signum, frame):
                return self.sigint(signum)
            def sigterm_closure(signum, frame):
                return self.sigterm(signum)
            def sigabrt_closure(signum, frame):
                return self.sigabrt(signum)
                
            # Now simply register those with signal.signal
            old_sigint = signal.signal(signal.SIGINT, sigint_closure)
            old_sigterm = signal.signal(signal.SIGTERM, sigterm_closure)
            old_sigabrt = signal.signal(signal.SIGABRT, sigabrt_closure)
            
        # If that fails, restore previous state and reraise
        except:
            _restore_any_previous_handler(signal.SIGINT, old_sigint)
            _restore_any_previous_handler(signal.SIGTERM, old_sigterm)
            _restore_any_previous_handler(signal.SIGABRT, old_sigabrt)
            raise
            
        # If that succeeds, set self._running and cache old handlers
        else:
            self._old_sigint = old_sigint
            self._old_sigterm = old_sigterm
            self._old_sigabrt = old_sigabrt
            self._running = True
        
    def stop(self):
        ''' Stops signal handling, returning all signal handlers to
        their previous handlers, or restoring their defaults if
        something went fishy.
        '''
        try:
            _restore_any_previous_handler(
                signal.SIGINT,
                self._old_sigint,
                force_clear = True
            )
            _restore_any_previous_handler(
                signal.SIGTERM,
                self._old_sigterm,
                force_clear = True
            )
            _restore_any_previous_handler(
                signal.SIGABRT,
                self._old_sigabrt,
                force_clear = True
            )
            
        # If we get an exception there, just force restoring all defaults and
        # reraise
        except:
            signal.signal(signal.SIGINT, signal.SIG_DFL)
            signal.signal(signal.SIGTERM, signal.SIG_DFL)
            signal.signal(signal.SIGABRT, signal.SIG_DFL)
            raise
            
        finally:
            # See notes above re: using ZeroDivisionError instead of None
            self._old_sigint = ZeroDivisionError
            self._old_sigterm = ZeroDivisionError
            self._old_sigabrt = ZeroDivisionError
            self._running = False
        
    @staticmethod
    def _default_handler(signum, *args):
        ''' The default signal handler for Unix.
        '''
        # Just parallel the sighandlers that are available in Windows, because
        # it is definitely the limiting factor here
        sigs = {
            signal.SIGABRT: SIGABRT,
            signal.SIGINT: SIGINT,
            signal.SIGTERM: SIGTERM,
        }
        
        try:
            exc = sigs[signum]
        except KeyError:
            exc = ReceivedSignal
            
        raise exc()
