# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.

"""
List for which changes are notified to observers
"""

import weakref

from elisa.core import log
from elisa.core.utils.threadsafe_list import ThreadsafeList
from elisa.core.observers.observable import Observable

class ListObservable(log.Loggable, ThreadsafeList):
    """
    ListObservable implements an observable list. It has all the
    functionalities of a normal L{list} and in addition informs all its
    observers about any change to its content.

    Its notification protocol is defined in L{ListObserver}. It is advised to
    use a subclass of L{ListObserver} in order to observe an instance of
    L{ListObservable}.
    All the methods that do writing operations declare what message they send
    to the observers in their documentation.

    Notification messages are always sent to the observers after the actual
    operation is done.
    """

    # FIXME: this class should be made thread-safe. This will probably allow
    # dropping the inheritance from ThreadsafeList.
    def __init__(self, *args, **kw):
        super(ListObservable, self).__init__(*args, **kw)
        self._observers = []

    def _send_message_to_observers(self, message, *args):
        for weak_observer in self._observers:
            observer = weak_observer()
            if observer == None:
                continue
            else:
                getattr(observer, message)(*args)

    def __hash__(self):
        return log.Loggable.__hash__(self)

    def __repr__(self):
        return "<ListObservable %r>" % ThreadsafeList.__repr__(self)

    def __eq__(self, other):
        return id(self) == id(other)

    def __setitem__(self, key, value):
        """
        Send the message 'modified' to the observers.
        """ 
        if key < len(self) and key >= -len(self):
            element = self[key]
            if isinstance(element, Observable):
                element.remove_observer(self)
        ThreadsafeList.__setitem__(self, key, value)

        self._send_message_to_observers("modified", key, value)

        if isinstance(value, Observable):
            value.add_observer(self)

    def __delitem__(self, key):
        """
        Send the message 'removed' to the observers.
        """
        if key < len(self) and key >= -len(self):
            element = self[key]
            if isinstance(element, Observable):
                element.remove_observer(self)
        ThreadsafeList.__delitem__(self, key)

        self._send_message_to_observers("removed", [element], key)

    def append(self, element):
        """
        Send the message 'inserted' to the observers.
        """
        position = len(self)
        ThreadsafeList.append(self, element)

        self._send_message_to_observers("inserted", [element], position)

        if isinstance(element, Observable):
            element.add_observer(self)

    def extend(self, elements):
        """
        Send the message 'inserted' to the observers.
        """
        position = len(self)
        ThreadsafeList.extend(self, elements)

        self._send_message_to_observers("inserted", elements, position)

        for element in elements:
            if isinstance(element, Observable):
                element.add_observer(self)

    def insert(self, position, element):
        """
        Send the message 'inserted' to the observers.
        """
        ThreadsafeList.insert(self, position, element)

        self._send_message_to_observers("inserted", [element], position)

        if isinstance(element, Observable):
            element.add_observer(self)

    def pop(self, position=-1):
        """
        Send the message 'removed' to the observers.
        """ 
        element = ThreadsafeList.pop(self, position)
        if isinstance(element, Observable):
            element.remove_observer(self)

        self._send_message_to_observers("removed", [element], position)

        return element

    def remove(self, element):
        """
        Send the message 'removed' to the observers.
        """ 
        if isinstance(element, Observable):
            element.remove_observer(self)
        position = self.index(element)
        ThreadsafeList.pop(self, position)

        self._send_message_to_observers("removed", [element], position)

    def reverse(self):
        """
        Send the message 'dirtied' to the observers.
        """
        ThreadsafeList.reverse(self)
        self._send_message_to_observers("dirtied")

    def sort(self, *args, **kwargs):
        """
        Send the message 'dirtied' to the observers.
        """
        ThreadsafeList.sort(self, *args, **kwargs)
        self._send_message_to_observers("dirtied")

    def __iadd__(self, operand):
        """
        Send the message 'inserted' to the observers.
        """
        position = len(self)
        result = ThreadsafeList.__iadd__(self, operand)

        self._send_message_to_observers("inserted", operand, position)

        for element in operand:
            if isinstance(element, Observable):
                element.add_observer(self)

        return result

    def __imul__(self, coefficient):
        """
        Send the message 'inserted' to the observers.
        """ 
        # FIXME: is it copying the elements?
        raise NotImplementedError

        position = len(self)
        result = ThreadsafeList.__imul__(self, coefficient)
        elements = result[position:]
        self._send_message_to_observers("inserted", elements, position)
        return result

    def __setslice__(self, i, j, elements):
        """
        Send successively the message 'removed' and the message 'inserted' to
        the observers.
        """
        removed_elements = self[i:j]
        for element in removed_elements:
            if isinstance(element, Observable):
                element.remove_observer(self)
        ThreadsafeList.__setslice__(self, i, j, elements)

        self._send_message_to_observers("removed", removed_elements, i)
        self._send_message_to_observers("inserted", elements, i)

        for element in elements:
            if isinstance(element, Observable):
                element.add_observer(self)

    def __delslice__(self, i, j):
        """
        Send the message 'removed' to the observers.
        """
        removed_elements = self[i:j]
        for element in removed_elements:
            if isinstance(element, Observable):
                element.remove_observer(self)
        ThreadsafeList.__delslice__(self, i, j)

        self._send_message_to_observers("removed", removed_elements, i)

    def add_observer(self, observer):
        """Attach an observer which will then be notified of all the changes
        applied to the observable.

        @param observer: observer to attach
        @type observer:  L{elisa.core.observers.observer.Observer}
        """
        self.debug("Adding observer %r", observer)
        real_observers = [weak_observer() for weak_observer in self._observers]
        if observer not in real_observers:
            self._observers.append(weakref.ref(observer))
        else:
            self.debug("Observer %r was already observing" % observer)

    def remove_observer(self, observer):
        """Detach an observer which will not be notified anymore of changes
        applied to the observable.

        @param observer: observer to detach
        @type observer:  L{elisa.core.observers.observer.Observer}
        """
        self.debug("Removing observer %r", observer)
        refs = weakref.getweakrefs(observer)
        for ref in refs:
            try:
                self._observers.remove(ref)
            except ValueError:
                pass
        """
        # ignore the call if the observer was not known
        self.debug("Observer %r was not observing", observer)
        """

    def attribute_set(self, origin, key, old_value, new_value):
        # notify the observers that some attribute of an element of the list
        # has changed
        position = self.index(origin)
        self._send_message_to_observers("element_attribute_set", position,
                                        key, old_value, new_value)


class ListObserver(object):

    def inserted(self, elements, position):
        """
        Message sent by a L{ListObservable} when new elements are inserted in
        it.

        @param elements: newly inserted elements
        @type elements:  list
        @param position: index where the elements have been inserted at
        @type position:  int
        """
        pass

    def removed(self, elements, position):
        """
        Message sent by a L{ListObservable} when elements are removed from it.

        @param elements: removed elements
        @type elements:  list
        @param position: former index of the elements
        @type position:  int
        """
        pass

    def modified(self, position, value):
        """
        Message sent by a L{ListObservable} when one of its elements is
        modified.

        @param position: index of the modified element
        @type position:  int
        @param value:    new value of the element
        @type value:     object
        """
        pass

    def dirtied(self):
        """
        Message sent by a L{ListObservable} when it is completely changed. For
        example, this happens after a reverse or a sort.
        """
        pass

    def element_attribute_set(self, position, key, old_value, new_value):
        """
        Message sent by a L{ListObservable} when an attribute of one of its
        element is changed.

        @param position:  index of the element
        @type position:   int
        @param key:       modified attribute of the element
        @type key:        str
        @param old_value: value of the attribute before the change
        @type old_value:  object
        @param new_value: value of the attribute after the change
        @type new_value:  object
        """
        pass

#if __name__ == "__main__":
#
#    l = ListObservable()
#    o = ListObserver()
#
#    l.add_observer(o)
#
#    print l
#    l.append(3)
#    print l
#    l.extend([44, 4, 78, 18])
#    print l
#    l.insert(0, 15)
#    print l
#    l.pop(0)
#    print l
#    l.remove(3)
#    print l
#    l.reverse()
#    print l
#    l.sort()
#    print l
#    l += [12, 29]
#    print l
#    l *= 4
#    print l
#    l[2] = 555
#    print l
#    del l[2]
#    print l
#    l[0:3] = []
#    print l
#    del l[:]
#    print l
