""" FeedItems.py

Straw module for handling items that belongs to a feed. This modules is
responsible for adding,cutting, and deleting items of the current feed.
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """GNU General Public License

This program is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA. """


import QueueDict
import Config
import Feed
import Event
import ItemStore
import error
import locale

class FeedItems(object):
    def __init__(self, feed):
        self._feed = feed
        self._items = QueueDict.ItemQueue()
        self._init_items()
        config = Config.get_instance()
        config.signal_connect(Event.ItemOrderChangedSignal, self.item_order_changed)
        config.signal_connect(Event.NumberOfItemsStoredChangedSignal, self.items_stored_changed)

    def _init_items(self):
        config = Config.get_instance()
        self._items.clear()
        self._items.sort_order = config.item_order
        self._prefs_items_stored = config.number_of_items_stored
        self._idseq = 0
        self._number_of_items = None
        self._loaded = False


    @apply
    def number_of_unread():
        doc = ""
        def fget(self):
            return self._feed.n_items_unread
        def fset(self, n):
            self._feed.n_items_unread = n
        return property(**locals())

    @apply
    def number_of_items():
        doc = ""
        def fget(self):
            if self._number_of_items:
                return self._number_of_items
            if self._loaded:
                error.log("was loaded but number_of_items was None!")
                return 0
        def fset(self, n):
            self._number_of_items = n
        return property(**locals())

    def item_order_changed(self, event):
        self._items.sort_order = event.sender.item_order
        self._feed.signal_refresh_display()

    def items_stored_changed(self, event):
        self._prefs_items_stored = event.sender.number_of_items_stored
        self._feed.signal_refresh_display()

    def add_items(self, new_items):
        cutpoint = self._get_cutpoint()
        items = sorted(new_items, cmp=self._cmp)
        newitems = []
        removed_items = []
        for x, item in enumerate(items):
            if item in self._items:
                continue
            if not item.id:
                self._idseq += 1
                item.id = self._idseq
            item.feed = self._feed
            if self._number_of_items < cutpoint:
                self._items.append(item)
            else:
                # no point on increasing the number of items here since it's
                # already over the cutpoint
                ritem = self._items.replace(item)
                removed_items.append(ritem)
            newitems.append(item)
            item.signal_connect(Event.ItemReadSignal, self.item_read)
            item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal)
        self._idseq = max(self._items.keys())
        self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen])
        self.number_of_items = len(self._items)

        if removed_items:
            self._feed.signal_deleted_item(removed_items)
        if newitems:
            self._feed.signal_new_items(newitems)
        self._feed.signal_refresh_display()

    def restore_items(self, items):
        """
        Restores the given items in an ordered form

        # should we check for duplicates here? Or should that be done
        # when new items arrive?

        """
        items = self._sort(items)
        no_restore = []
        cutpoint = self._get_cutpoint()
        for x,item in enumerate(items):
            item.feed = self._feed
            if item.sticky or x <= cutpoint:
                item.signal_connect(Event.ItemReadSignal, self.item_read)
                item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal)
                self._items.append(item)
                continue
            item.clean_up()
            no_restore.append(item)
        self._idseq = max(self._items.keys())
        self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen])
        self.number_of_items = len(self._items)
        if no_restore:
            self._feed.signal_deleted_item(no_restore)

    def _disconnect_item_signals(self, item):
        item.signal_disconnect(Event.ItemReadSignal, self.item_read)
        item.signal_disconnect(Event.ItemStickySignal, self._feed.forward_signal)

    def item_read(self, signal):
        if signal.sender.seen:
            change = -1
        else:
            change = 1
        self.number_of_unread = self.number_of_unread + change
        self._feed.forward_signal(signal)

    def get_items(self):
        if not self._loaded:
            self.load()
        return self._items.values()

    def get_item_index(self, item):
        if not self._loaded:
            self.load()
        idx = self._items.index(item.id)
        return idx

    def mark_all_read(self):
        if not self._loaded:
            self.load()
        keys = self._items.keys()
        changed = [(keys.index(key), value) for key, value in self._items if value.set_seen_quiet()]
        self.number_of_unread = 0
        self._feed.signal_all_items_read(changed)
        return

    def load(self):
        """
        Loads and restores the items from the data store

        """
        if self._loaded:
            return False
        itemstore = ItemStore.get_instance()
        items = itemstore.read_feed_items(self._feed)
        if items:
            #error.log("load ", self._feed.title, ", number of items: ", len(items), ", number of unread before restore: ", self.number_of_unread)
            self.restore_items(items)
            self._loaded = True
        else:
            #print "No items, not loading ", self._feed.title
            self._loaded = False
        return self._loaded

    def unload(self):
        """
        Unloads the items by disconnecting the signals and reinitialising the
        instance variables

        TODO: unload seems to lose some circular references. garbage collector
        will find them, though, so maybe it's not a problem.
        """
        if not self._loaded:
            return
        for key, item in self._items:
            self._disconnect_item_signals(item)
        self._init_items()

    def _get_cutpoint(self):
        """
        Returns the current cutpoint

        """
        if self._feed.number_of_items_stored == Feed.Feed.DEFAULT:
            cutpoint = self._prefs_items_stored
        else:
            cutpoint = self._feed.number_of_items_stored
        return cutpoint

    def _sort(self, items):
        """
        Sorts the given items according to the sort order
        """
        items.sort(self._cmp)
        if self._items.sort_order:
            items.reverse()
        return items

    def _cmp(self, a, b):
        """
        Comparator method to compare items based on the item's pub_date attribute

        If an item doesn't have a pub_date, it uses title and prioritizes the
        unread items
        """
        try:
            return cmp(a.pub_date, b.pub_date)
        except AttributeError:
            return locale.strcoll(a.title, b.title) and not a.seen or not b.seen
