import sys
import odml


class Event (object):
    def __init__(self, name):
        self.handlers = []
        self.name = name

    def add_handler(self, handler):
        try:
            if handler not in self.handlers:
                self.handlers.append(handler)
        except:
            print("cannot add handler.")
        return self

    def remove_handler(self, handler):
        if handler in self.handlers:
            self.handlers.remove(handler)
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)
        self.finish(*args, **kargs)

    def finish(self, *args, **kargs):
        """
        a final handler for this event
        (typically passing the event on to higher layers)
        """
        pass

    def __len__(self):
        return len(self.handlers)

    def __repr__(self):
        return "<%sEvent>" % self.name

    __iadd__ = add_handler
    __isub__ = remove_handler
    __call__ = fire


class ChangeContext(object):
    """
    A ChangeContext holds information about a change event

    the context object stays the same within preChange and postChange
    events, thus you can store information in a preChange event and use
    it later in the postChange event where the information might not be
    available any more.

    Attributes:

    * action: the action that caused the event
    * obj: the object upon which the action is executed
    * val: a value assotiated with a action
    * preChange: True if the change has not yet occurred
    * postChange: True if the change has already occured

    Actions defined so far:

    * "set": val = (attribute_name, new_value)
    * "insert", "append": val = object to be inserted
    * "remove": val = object to be remove

    Events may be passed on in the hierarchy, thus the context also
    holds state for this. *cur* holds the current node receiving the
    event and is a direct or indirect parent of obj.

    The hidden attribute *_obj* holds the complete pass stack, where
    obj is obj[0] and cur is _obj[-1]
    """
    def __init__(self, val):
        self._obj = []
        self.val = val
        self.action = None
        self.when = None

    @property
    def preChange(self):
        return self.when is True

    @preChange.setter
    def preChange(self, val):
        self.when = True if val else None

    @property
    def postChange(self):
        return self.when is False

    @postChange.setter
    def postChange(self, val):
        self.when = False if val else None

    @property
    def obj(self):
        return self._obj[0]

    def getStack(self, count):
        """
        helper function used to obtain a event-pass-stack for a certain hierarchy

        i.e. for a property-change event caught at the parent section, _obj will be
        [property, section] with getStack you can now obtain:

        >>> value, property, section = getStack(3)

        without checking the depth of the level, this will also hold true for longer stacks
        such as [val, prop, sec, doc] and will still work as expected
        """
        if len(self._obj) < count:
            return ([None] * count + self._obj)[-count:]
        return self._obj[:count]

    @property
    def cur(self):
        return self._obj[-1]

    def passOn(self, obj):
        """
        pass the event to obj

        appends obj to the event-pass-stack and calls obj._Changed
        after handling obj is removed from the stack again
        """
        self._obj.append(obj)
        if obj._change_handler is not None: #hasattr(obj, "_change_handler"):
            obj._change_handler(self)
        obj._Changed(self)
        self._obj.remove(obj)

    def reset(self):
        self._obj = []

    def __repr__(self):
        v = ""
        if self.preChange: v = "Pre"
        if self.postChange: v = "Post"
        return "<%sChange %s.%s(%s)>" % (v, repr(self.obj), self.action, repr(self.val))

    def dump(self):
        return repr(self) + "\nObject stack:\n\t" + "\n\t".join(map(repr, self._obj))


class ChangedEvent(object):
    def __init__(self):
        pass


class EventHandler(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kargs):
        return self._func(*args, **kargs)


class ChangeHandlable(object):
    """
    For objects that support the add_change_handler
    and remove_change_handler functions.
    """
    _change_handler = None

    def add_change_handler(self, func):
        if self._change_handler is None:
            self._change_handler = Event(self.__class__.__name__)
        self._change_handler += func

    def remove_change_handler(self, func):
        if self._change_handler is not None:
            self._change_handler -= func
        if len(self._change_handler) == 0:
            del self._change_handler


class ModificationNotifier(ChangeHandlable):
    """
    Override some methods, to get notification on their calls
    """
    def __setattr__(self, name, value):
        fire = not name.startswith('_') and hasattr(self, name)
        func = lambda: super(ModificationNotifier, self).__setattr__(name, value)
        if fire:
            self.__fireChange("set", (name, value), func)
        else:
            func()

    def __fireChange(self, action, obj, func):
        """
        create a ChangeContext and

        * fire a preChange-event
        * call func
        * fire a postChange-event
        """
        c = ChangeContext(obj)
        c.action = action
        c.preChange = True
        c.passOn(self)
        res = func()
        c.reset()
        c.postChange = True
        c.passOn(self)
        return res

    def append(self, obj, *args, **kwargs):
        func = lambda: super(ModificationNotifier, self).append(obj, *args, **kwargs)
        self.__fireChange("append", obj, func)

    def remove(self, obj):
        func = lambda: super(ModificationNotifier, self).remove(obj)
        self.__fireChange("remove", obj, func)

    def insert(self, position, obj):
        func = lambda: super(ModificationNotifier, self).insert(position, obj)
        self.__fireChange("insert", obj, func)

    def _reorder(self, childlist, new_index):
        func = lambda: super(ModificationNotifier, self)._reorder(childlist, new_index)
        return self.__fireChange("reorder", (childlist, new_index), func)

# create a seperate global Event listeners for each class
# and provide ModificationNotifier Capabilities
name = "event"
provides = odml.getImplementation().provides + ["event"]


class Value(ModificationNotifier, odml.getImplementation().Value):
    _Changed = Event("value")


class Property(ModificationNotifier, odml.getImplementation().Property):
    _Changed = Event("prop")


class Section(ModificationNotifier, odml.getImplementation().Section):
    _Changed = Event("sec")


class Document(ModificationNotifier, odml.getImplementation().Document):
    _Changed = Event("doc")


def pass_on_change(context):
    """
    pass the change event to the parent node
    """
    parent = context.cur.parent
    if parent is not None:
        context.passOn(parent)


def pass_on_change_section(context):
    """
    pass the change event directly to the document in question
    don't go through all parents
    """
    document = context.cur.document
    if document is not None:
        context.passOn(document)

Value._Changed.finish    = pass_on_change
Property._Changed.finish = pass_on_change
Section._Changed.finish  = pass_on_change_section

odml.addImplementation(sys.modules[__name__])
