"""
adesklets events handler module
"""
import re
import posix_signal
import types
import string
import sys

from signal_handler import Signal_handler
from singleton import _Singleton, _States

class _Events_handler(Signal_handler, _States):
    """
    Low level events handler
    """
    def __init__(self):
        _States.__init__(self)
        
        self.comm = self._States__shared_state['communicator']        
        Signal_handler.__init__(self,['USR1', 'ALRM', 'TERM'],self._fire_event)
        print >> self.comm, "events_set_echo", 1
        print >> self.comm, "events_set_send_sigusr1", 1

    def _fire_event(self, signo, frame):
        pass

    def _flush(self):
        """
        Complety purges stdin and stdout of communicator
        from its content
        """
        for command in ['out','err']:
            try:
                while self.comm.__class__.__dict__[command](self.comm):
                    pass
            except ADESKLETSError:
                pass

    def __del__(self):
        Signal_handler.__del__(self)
        print >> self.comm, "events_set_echo", 0
        print >> self.comm, "events_set_send_sigusr1", 0
        print >> self.comm, "events_reset_all"
        self._flush()

class Events_handler(_Singleton,_Events_handler):
    """
    High-level events handler

    Made to be used as a base class in applications wanting to catch
    adesklets related events. 

    class My_events_handler(Events_handler):
    	def __init__(self):
            Events_handler.__init__(self)
        def __del__(self):
            Events_handler.__del__(self)
        ...

    from there, you just have to redefine the methods you are interested in,
    choosing between ready(), quit(), alarm(), menu_fire(), background_grab(),
    button_press(), button_release(), motion_notify(), enter_notify() and
    leave_notify() - see short descriptions below.

    As soon as you will instanciate an object from your class, all
    events corresponding to functions you 'overloaded' will
    automatically be caugth and will cause your object methods to be
    appropriately called.

    Note 1: all methods events but alarm() are protected critical
    sections, if POSIX reliable signals are supported on your
    architecture. This means they will not get interrupted by normal
    adesklets operation. No adesklets events will ever be lost either:
    they are garanteed to be all processed in the order they arrived,
    except for one case: quit() signal is always processed right after
    the current event method returns.
    
    	WARNING 1: Of course, this means that events are not guaranteed
        to be processed as they arrive: they can be arbitrarily delayed
        since former events processing could have taken any amount of time.
        This is the reason you are encouraged to take advantage of the
        'delayed' flag passed to most methods (and to write quick to compute
        methods!). For instance, depending of your desklet, you may want
        to ignore all motion_notify events that were not processed right
        away, since they are no longuer relevant for giving quick feedback
        to the user.

        WARNING 2: As stated above, the alarm() method, used for periodic
        action, could get interrupted, unless on the initial call.
        You are responsible for providing proper exception recovery
        in case of problem with IO, for instance. Alternatively, you can
        use the posix_signal API, already imported, to make your
        own signal management, or use the block()/unblock() methods
        provided. Please note it is probably a bad idea to have a
        long-running alarm() method blocking signals without any possibility
        of being interrupted.
        
    Note 2: every time an object from a class deriving from Events_handler
    is instanciated, the content of all communications channel to adesklets
    interpreter is flushed.
    
    Note 3: for obvious reasons, at most one instance of Events_handler's
    children can exist at any given time.

    Note 4: when using the python interpreter in interactive mode,
            calls to Events_handler's functions could block, depending
            of the platform/python version you use. This is due to
            the way python handle access to various streams, including
            stdout and stderr. If this happens to you, there are
            two workarounds:
                - Invoke python with the -u swith or the environment variable
                PYTHONUNBUFFERED=1 - this works many times
                - Avoid interactive mode - directly build a test script
                and run it. :-)

    Note 5: It you need to (from adesklets 0.3.0), you also have the two
    methods Events_handler::get_events() and Events_handler::set_events()
    that give you an easy way to change at run time what events are catched,
    and using what unbounded methods.

    This is especially convenient if you want to program interruptable
    timed 'effects' (see test/fading.py), while not having them
    interrupetd by the same set of events you normally use.
    """

    _events = [ ('MenuFire'       , 'menu_fire'     ),
                ('BackgroundGrab' , 'background_grab'),
                ('ButtonPress'    , 'button_press'   ),
                ('ButtonRelease'  , 'button_release' ),
                ('MotionNotify'   , 'motion_notify'  ),
                ('EnterNotify'    , 'enter_notify'   ),
                ('LeaveNotify'    , 'leave_notify'   ) ]
     
    def __init__(self):
        _Singleton.__init__(self,'Events_handler')
        _States.__init__(self)
        _Events_handler.__init__(self)
        
        self.__re_event=re.compile('^event:')
        self.__events_re = []
        self.__posix_signal_support = hasattr(posix_signal,'sigprocmask')
        
        for expr, func_name in self._events:
            # Build a regular expression, then find corresponding function
            # reference.
            if self.__class__.__dict__.has_key(func_name):
                name=self.__class__.__dict__[func_name]
                print >> self.comm, "event_catch", expr
            else:
                name=None
            self.__events_re.append(
                [re.compile('^event: '+ string.lower(expr) + '(.*)'), name])
        self._flush()

        self.block()
        self.ready()
        self._alarm()
        self.unblock()

    def block(self):
        """
        Convenience functions to block signals generated
        by adesklets, if supported by your platform

        WARNING: you never need to call it for any event but alarm()
        """
        if self.__posix_signal_support:
            posix_signal.sigprocmask(posix_signal.SIG_BLOCK,
                                     [posix_signal.SIGTERM,
                                      posix_signal.SIGUSR1,
                                      posix_signal.SIGALRM])
    def unblock(self):
        """
        Convenience functions to block signals generated
        by adesklets, if supported by your platform
        
        WARNING: you never need to call it for any event but alarm()
        """
        if self.__posix_signal_support:
            posix_signal.sigprocmask(posix_signal.SIG_UNBLOCK,
                                     [posix_signal.SIGTERM,
                                      posix_signal.SIGUSR1,
                                      posix_signal.SIGALRM])
            
    def _fire_event(self, signo, frame):
        if signo==posix_signal.SIGALRM:
            self._alarm()
            return
        # define a critical section: everything inside the handler should be
        # protected from further signals call
        self.block()
        if signo==posix_signal.SIGTERM:
            self._quit()
        else:
            delayed=False
            while 1:
                # 'TERM' signal should be processed in priority
                if delayed:
                    if posix_signal.SIGTERM in posix_signal.sigpending():
                        posix_signal.sigsuspend(posix_signal.SIGUSR1|
                                                posix_signal.SIGALRM)
                event=self.comm.err()
                if event!=None:
                    if self.__re_event.match(event):
                        # Now, we know we have both an event and a match
                        for expr, func in self.__events_re:
                            match=expr.match(event)
                            if match:
                                # Unpack, then convert arguments to integer
                                # as needed, then call proper function
                                func(self,delayed,
                                     *map(lambda str : self._to_int(str),
                                          [x for x in
                                           re.split('\W+',match.expand('\\1'))
                                           if x != '']))
                    delayed=True
                else:
                    break
        self.unblock()

    def pause(self):
        """
        Put python intepreter to sleep forever, only waking up to
        process events.
        """
        while 1:
            posix_signal.pause()

    def ready(self):
        """
        Called only once when adesklets is ready to receive command
        """
        pass
    def quit(self):
        """
        Called only once when adesklets is about to quit:
        the python interpreter will exit right after this function returns.

        Note: this function is guaranteed not to be interrupted
        by others signals from adesklets, but SIGKILL, whether you have POSIX
        reliable signal support or not.
        """
        pass
    def alarm(self):
        """
        Called periodically. Usefull to perform any perdiodic operation.
        Delay until next call is determined from return value (which must be
        an integer), in seconds (zero means not to reschedule). First call is
        automatically scheduled right after the ready() event method returns.
        """
        return 0
    def menu_fire(self, delayed, menu_id, item):
        """
        Called whenever a catchable menu selection returns.
        `item' may be None if no selection was made.
        """
        pass
    def background_grab(self, delayed):
        """
        Called whenever adesklets' background image is updated
        """
        pass
    def button_press(self, delayed, x, y, button):
        """
        Called whenever a button is pressed
        """
        pass
    def button_release(self, delayed, x, y, button):
        """
        Called whenever a button is released
        """
        pass
    def motion_notify(self, delayed, x, y):
        """
        Called whenever pointer is moved inside the window
        """
        pass
    def enter_notify(self, delayed, x, y):
        """
        Called whenever pointer enters the window
        """
        pass
    def leave_notify(self, delayed, x, y):
        """
        Called whenever pointer leaves the window
        """
        pass
        
    def _quit(self):
        Signal_handler.clear(self,['USR1','ALRM'])
        self.quit()
        sys.exit(0)

    def _alarm(self):
        timeout=self.alarm()
        if type(timeout)!=types.IntType:
            timeout=0
        posix_signal.alarm(timeout)
        pass
        
    def _to_int(self,str):
        """
        Try to perform the conversion.
        If it fails, return basic object.
        """
        try:
            result=int(str)
        except ValueError:
            return str
        else:
            return result

    def get_events(self):
        """
        This returns a dictionary of all events catched by the desklet
        at the time of calling. 

        For reliable use, it should always be called when signals are blocked
        (See Events_handler class description above)
        """
        return dict(list(self._get_events()))

    def set_events(self,events):
        """
        This overrides the catched events using a dictionnary compatible to
        the one sent back by Events_handler::get_events().

        This gives the desklet programmer a way to dynamically change
        what signals are caught, which may be useful when working
        with indirect mode programming (adesklets.start() command and others
        alike).
        """
        self.__events_re=[]
        print >> self.comm, 'events_reset_all'
        for expr, func in events.items():
            self.__events_re.append(
                [re.compile('^event: '+ string.lower(expr) + '(.*)'),
                 {0:None,1:func}[callable(func)]])
            if callable(func):
                print >> self.comm, "event_catch", expr
    
    def _get_events(self):
        for expr, func in self.__events_re:
            mapped = False
            for event in self._events:
                if expr.match('event: ' + string.lower(event[0])):
                    mapped = True
                    yield event[0], func
            if not mapped:
                yield event[0], None
        
    def __del__(self):
        _Events_handler.__del__(self)
        _States.__del__(self)
        _Singleton.__del__(self)
