

class FSMProxy(object):

    def __init__(self, cfg):
        from fysom import Fysom
        events = set([event['name'] for event in cfg['events']])
        cfg['callbacks'] = self.collect_event_listeners(events, cfg['callbacks'])
        self.fsm = Fysom(cfg)
        self.attach_proxy_methods(self.fsm, events)

    def collect_event_listeners(self, events, callbacks):
        callbacks = callbacks.copy()
        callback_names = []
        for event in events:
            callback_names.append(('_before_' + event, 'onbefore' + event))
            callback_names.append(('_after_' + event, 'onafter' + event))
        for fn_name, listener in callback_names:
            fn = getattr(self, fn_name, None)
            if callable(fn):
                if listener in callbacks:
                    old_fn = callbacks[listener]

                    def wrapper(e, old_fn=old_fn, fn=fn):
                        old_fn(e)
                        fn(e)
                    callbacks[listener] = wrapper
                else:
                    callbacks[listener] = fn
        return callbacks

    def attach_proxy_methods(self, fsm, events):
        def make_proxy(fsm, event):
            fn = getattr(fsm, event)

            def proxy(*args, **kwargs):
                if args:
                    raise FSMProxyError('FSMProxy event listeners only accept named arguments.')
                fn(**kwargs)
            return proxy

        for event in events:
            if not hasattr(self, event):
                setattr(self, event, make_proxy(fsm, event))

    def __getstate__(self):
        state = {}
        for key, value in self.__dict__.iteritems():
            if callable(value) or key == 'fsm':
                continue
            state[key] = value
        state['__class__'] = self.__module__ + '.' + self.__class__.__name__
        return state

    def __setstate__(self, state):
        for key in state:
            self.__dict__[key] = state[key]


class FSMProxyError(Exception):
    pass
