"""$Id $
Handles signal handlers within Pydb.
"""
import signal

def lookup_signame(num):
    """Find the corresponding signal name for 'num'. Return None
    if 'num' is invalid."""
    for signame in signal.__dict__.keys():
        if signal.__dict__[signame] == num:
            return signame

def lookup_signum(name):
    """Find the corresponding signal number for 'name'. Return None
    if 'name' is invalid."""
    if hasattr(signal, name):
        return getattr(signal, name)
    else:
        return None

class SigHandler:

    """Store information about what we do when we handle a signal,

    - Do we print/not print when signal is caught
    - Do we pass/not pass the signal to the program
    - Do we stop/not stop when signal is caught

    All the methods to change these attributes return None on error, or
    True or False if we have set the action (pass/print/stop) for a signal
    handler.
    """
    def __init__(self, pydb): 
        self.pydb = pydb
        # This list contains tuples made up of four items, one tuple for
        # every signal handler we've created. The tuples contain
        # (signal_num, stop, print, pass)
        self._sig_attr = []
        self._sig_stop = []
        self._sig_print = []
        self._sig_pass = []

        for sig in signal.__dict__.keys():
            if sig.startswith('SIG') and '_' not in sig:
                self._sig_attr.append(sig)

        # set up signal handling for some known signals
        fatal = ['SIGINT', 'SIGTRAP', 'SIGTERM', 'SIGQUIT', 'SIGILL', \
                 'SIGKILL', 'SIGSTOP']
        for sig in self._sig_attr:
            if str(sig) not in fatal:
                num = lookup_signum(sig)
                if num:
                    self._set_sig(sig, (True, True, True))
                    signal.signal(num, self.handle)
            else:
                self._set_sig(sig, (False, False, True))

    def _get_sig(self, name):
        st = name in self._sig_stop
        pr = name in self._sig_print
        pa = name in self._sig_pass
        return (st, pr, pa)

    def _set_sig(self, name, (st, pr, pa)):
        """Set the actions to be taken when a signal, specified by
        'name', is received.
        """
        if st:
            if name not in self._sig_stop:
                self._sig_stop.append(name)
        else:
            if name in self._sig_stop:
                self._sig_stop.pop(self._sig_stop.index(name))
        if pr:
            if name not in self._sig_print:
                self._sig_print.append(name)
        else:
            if name in self._sig_print:
                self._sig_print.pop(self._sig_print.index(name))
        if pa:
            if name not in self._sig_pass:
                self._sig_pass.append(name)
        else:
            if name in self._sig_pass:
                self._sig_pass.pop(self._sig_pass.index(name))

    def info_signal(self, signame):
        """Print information about a signal"""
        if 'handle' in signame or 'signal' in signame:
            # This has come from pydb's info command
            if len(signame) == 1:
                self.pydb.msg('NAME\t\tSTOP\tPRINT\tPASS')
                for sig in self._sig_attr:
                    s = sig in self._sig_stop
                    pr = sig in self._sig_print
                    pa = sig in self._sig_pass
                    self.pydb.msg('%s\t\t%s\t%s\t%s' % (sig,s,pr,pa))
            else:
                self.info_signal(signame[1])
            return
            
        s = signame in self._sig_stop
        pr = signame in self._sig_print
        pa = signame in self._sig_pass
        self.pydb.msg('NAME\t\tSTOP\tPRINT\tPASS')
        self.pydb.msg('%s\t\t%s\t%s\t%s' % (signame, s, pr, pa))

    def action(self, arg):
        """Delegate the actions specified in 'arg' to another
        method.
        """
        if not arg:
            self.info_signal(['handle'])
            return
        args = arg.split()
        if args[0] in self._sig_attr:
            if len(args) == 1:
                self.info_signal(args[0])
                return
            # multiple commands might be specified, i.e. 'nopass nostop'
            for attr in args[1:]:
                if attr.startswith('no'):
                    on = False
                    attr = attr[2:]
                else:
                    on = True
                if attr.startswith('stop'):
                    self.handle_stop(args[0], on)
                elif attr.startswith('print'):
                    self.handle_print(args[0], on)
                elif attr.startswith('pass'):
                    self.handle_pass(args[0], on)
                else:
                    self.pydb.errmsg('Invalid arguments')

    def handle_stop(self, signum, change):
        """Change whether we stop or not when this signal is caught.
        If 'change' is True your program will stop when this signal
        happens."""
        if not isinstance(change, bool):
            return
        old_attr = self._get_sig(signum)
        st, pr, pa = change, old_attr[1], old_attr[2]
        if st:
            pr = True
        self._set_sig(signum, (st, pr, pa))
        return change

    def handle_pass(self, signum, change):
        """Change whether we pass this signal to the program (or not)
        when this signal is caught. If change is True, Pydb should allow
        your program to see this signal.
        """
        if not isinstance(change, bool):
            return
        old_attr = self._get_sig(signum)
        st, pr, pa = old_attr[0], old_attr[1], change
        self._set_sig(signum, (st, pr, pa))
        return change

    # ignore is a synonym for nopass and noignore is a synonym for pass
    def handle_ignore(self, signum, change):
        if not isinstance(change, bool):
            return
        self.handle_pass(not change)
        return change

    def handle_print(self, signum, change):
        """Change whether we print or not when this signal is caught."""
        if not isinstance(change, bool):
            return
        old_attr = self._get_sig(signum)
        st, pr, pa = old_attr[0], change, old_attr[2]
        if not change:
            # noprint implies nostop
            st = False
        self._set_sig(signum, (st, pr, pa))
        return change

    def handle(self, signum, frame):
        """This method is called when a signal is received."""
        sig = lookup_signame(signum)
        st, pa, pr = self._get_sig(sig)
        if pr:
            self.pydb.msg('Program received signal %s' % sig)
        if st:
            # XXX Rocky what's the best way to handle this? 
            self.pydb.use_rawinput = False
            self.pydb.interaction(self.pydb.curframe, None)
