# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 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 Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

import gobject

class Notifier(gobject.GObject):
    __gsignals__ = {'items-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                                     (gobject.TYPE_INT, gobject.TYPE_PYOBJECT,)),
                    'items-deleted': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                                     (gobject.TYPE_INT, gobject.TYPE_PYOBJECT)),
                    'items-inserted': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                                     (gobject.TYPE_INT, gobject.TYPE_PYOBJECT,)),
                    'items-reordered': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                                        ())
                   }


NEGATIVE_INDEXES_ERROR_MSG = 'Notifying list does not support negative indexes'


class List(list):
    """
    This is a special kind of list that will emit signals from its notifier
    member whenever it is modified.
    
    As of now, it does not support negative indexes and raises an
    C{IndexError} whenever it meets one.
    The only exception to that is the end part of a slice, since it's not used
    as a parameter to the sent signal.
    
    Loosely based on GTK GtkTreeModel API.

    @ivar cookie: an integer that changes whenever the list is modified
    @type cookie: C{int}
    """


    def __init__(self, *args, **kwargs):
        list.__init__(self, *args, **kwargs)
        self.notifier = Notifier()
        self.cookie = 0

    def __setitem__(self, index, item):
        # FIXME: do not support slice arguments
        if index < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        list.__setitem__(self, index, item)
        self.cookie += 1
        self.notifier.emit('items-changed', index, [item])

    def __delitem__(self, index):
        # FIXME: do not support slice arguments
        if index < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        item = self[index]
        list.__delitem__(self, index)
        self.cookie += 1
        self.notifier.emit('items-deleted', index, [item])

    def append(self, item):
        index = len(self)
        list.append(self, item)
        self.cookie += 1
        self.notifier.emit('items-inserted', index, [item])

    def extend(self, items):
        index = len(self)
        list.extend(self, items)
        # warning: it does not create a new list
        self.cookie += 1
        self.notifier.emit('items-inserted', index, items)

    def insert(self, index, item):
        if index < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        list.insert(self, index, item)
        self.cookie += 1
        self.notifier.emit('items-inserted', index, [item])

    def pop(self, index=None):
        if index == None:
            index = len(self)-1
        if index < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        item = list.pop(self, index)
        self.cookie += 1
        self.notifier.emit('items-deleted', index, [item])
        return item

    def remove(self, item):
        index = self.index(item)
        list.pop(self, index)
        self.cookie += 1
        self.notifier.emit('items-deleted', index, [item])

    def reverse(self):
        list.reverse(self)
        self.cookie += 1
        self.notifier.emit('items-reordered')

    def sort(self, *args, **kwargs):
        list.sort(self, *args, **kwargs)
        self.cookie += 1
        self.notifier.emit('items-reordered')

    def __iadd__(self, operand):
        index = len(self)
        result = list.__iadd__(self, operand)
        # warning: it does not create a new list
        self.cookie += 1
        self.notifier.emit('items-inserted', index, operand)
        return result

    def __imul__(self, coefficient):
        index = len(self)
        result = list.__imul__(self, coefficient)
        # warning: it does not create a new list
        self.cookie += 1
        self.notifier.emit('items-inserted', index, self[index:])
        return result

    def __setslice__(self, i, j, items):
        if i < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        removed_items = self[i:j]
        list.__setslice__(self, i, j, items)
        self.cookie += 1
        if len(removed_items) > 0:
            self.notifier.emit('items-deleted', i, removed_items)
        if len(items) > 0:
            self.notifier.emit('items-inserted', i, items)

    def __delslice__(self, i, j):
        if i < 0:
            raise IndexError, NEGATIVE_INDEXES_ERROR_MSG
        removed_items = self[i:j]
        list.__delslice__(self, i, j)
        if len(removed_items) > 0:
            self.cookie += 1
            self.notifier.emit('items-deleted', i, removed_items)

if __name__ == '__main__':
    def on_items_inserted(model, index, items):
        print "inserted at", index

    def on_items_deleted(model, index, items):
        print "deleted at", index

    def on_items_changed(model, index, items):
        print "changed at", index

    def on_items_reordered(model):
        print "reordered"

    l = List()
    l.notifier.connect('items-changed', on_items_changed)
    l.notifier.connect('items-deleted', on_items_deleted)
    l.notifier.connect('items-inserted', on_items_inserted)
    l.notifier.connect('items-reordered', on_items_reordered)

    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
    l[:] = []
    print l
