"""
Notifier.

Author(s): Jelle Roozenburg
"""
from __future__ import absolute_import

import logging
import threading

from Tribler.Core.simpledefs import (
    NTFY_ACTIVITIES, NTFY_CHANNEL, NTFY_CHANNELCAST, NTFY_CHANNEL_ENTITY, NTFY_CLOSE_TICK, NTFY_COMMENTS,
    NTFY_CREDIT_MINING, NTFY_DELETE, NTFY_INSERT, NTFY_MARKET_IOM_INPUT_REQUIRED, NTFY_MARKET_ON_ASK,
    NTFY_MARKET_ON_ASK_TIMEOUT, NTFY_MARKET_ON_BID, NTFY_MARKET_ON_BID_TIMEOUT, NTFY_MARKET_ON_PAYMENT_RECEIVED,
    NTFY_MARKET_ON_PAYMENT_SENT, NTFY_MARKET_ON_TRANSACTION_COMPLETE, NTFY_MARKINGS, NTFY_MODERATIONS,
    NTFY_MODIFICATIONS, NTFY_MYPREFERENCES, NTFY_NEW_VERSION, NTFY_PLAYLISTS, NTFY_REACHABLE, NTFY_STARTUP_TICK,
    NTFY_TORRENT, NTFY_TORRENTS, NTFY_TRACKERINFO, NTFY_TRIBLER, NTFY_TUNNEL, NTFY_UPDATE, NTFY_UPGRADER,
    NTFY_UPGRADER_TICK, NTFY_VOTECAST, NTFY_WATCH_FOLDER_CORRUPT_TORRENT, SIGNAL_ALLCHANNEL_COMMUNITY, SIGNAL_CHANNEL,
    SIGNAL_CHANNEL_COMMUNITY, SIGNAL_GIGACHANNEL_COMMUNITY, SIGNAL_RESOURCE_CHECK, SIGNAL_RSS_FEED,
    SIGNAL_SEARCH_COMMUNITY, SIGNAL_TORRENT, STATE_SHUTDOWN)


class Notifier(object):

    SUBJECTS = [NTFY_TORRENTS, NTFY_PLAYLISTS, NTFY_COMMENTS, NTFY_MODIFICATIONS, NTFY_MODERATIONS, NTFY_MARKINGS,
                NTFY_MYPREFERENCES, NTFY_ACTIVITIES, NTFY_REACHABLE, NTFY_CHANNELCAST, NTFY_CLOSE_TICK,
                NTFY_STARTUP_TICK, NTFY_TRACKERINFO, NTFY_TUNNEL, NTFY_UPGRADER, NTFY_VOTECAST,
                SIGNAL_ALLCHANNEL_COMMUNITY, SIGNAL_CHANNEL, SIGNAL_CHANNEL_COMMUNITY, SIGNAL_RSS_FEED,
                SIGNAL_SEARCH_COMMUNITY, SIGNAL_TORRENT, NTFY_WATCH_FOLDER_CORRUPT_TORRENT, NTFY_NEW_VERSION,
                NTFY_TRIBLER, NTFY_UPGRADER_TICK, NTFY_TORRENT, NTFY_CHANNEL, NTFY_MARKET_ON_ASK, NTFY_MARKET_ON_BID,
                NTFY_MARKET_ON_ASK_TIMEOUT, NTFY_MARKET_ON_BID_TIMEOUT, NTFY_MARKET_ON_TRANSACTION_COMPLETE,
                NTFY_MARKET_ON_PAYMENT_RECEIVED, NTFY_MARKET_ON_PAYMENT_SENT, NTFY_MARKET_IOM_INPUT_REQUIRED,
                SIGNAL_RESOURCE_CHECK, NTFY_CREDIT_MINING, STATE_SHUTDOWN, SIGNAL_GIGACHANNEL_COMMUNITY,
                NTFY_CHANNEL_ENTITY]

    def __init__(self):
        self._logger = logging.getLogger(self.__class__.__name__)

        self.observers = []
        self.observerscache = {}
        self.observertimers = {}
        self.observerLock = threading.Lock()

    def add_observer(self, func, subject, changeTypes=None, id=None, cache=0):
        changeTypes = changeTypes or [NTFY_UPDATE, NTFY_INSERT, NTFY_DELETE]
        """
        Add observer function which will be called upon certain event
        Example:
        addObserver(NTFY_TORRENTS, [NTFY_INSERT,NTFY_DELETE]) -> get callbacks
                    when peers are added or deleted
        addObserver(NTFY_TORRENTS, [NTFY_SEARCH_RESULT], 'a_search_id') -> get
                    callbacks when peer-searchresults of of search
                    with id=='a_search_id' come in
        """
        assert isinstance(changeTypes, list)
        assert subject in self.SUBJECTS, 'Subject %s not in SUBJECTS' % subject

        obs = (func, subject, changeTypes, id, cache)
        self.observerLock.acquire()
        self.observers.append(obs)
        self.observerLock.release()

    def remove_observer(self, func):
        """ Remove all observers with function func
        """
        with self.observerLock:
            i = 0
            while i < len(self.observers):
                ofunc = self.observers[i][0]
                if ofunc == func:
                    del self.observers[i]
                else:
                    i += 1

    def remove_observers(self):
        with self.observerLock:
            for timer in self.observertimers.values():
                timer.cancel()
            self.observerscache = {}
            self.observertimers = {}
            self.observers = []

    def notify(self, subject, changeType, obj_id, *args):
        """
        Notify all interested observers about an event with threads from the pool
        """
        tasks = []
        assert subject in self.SUBJECTS, 'Subject %s not in SUBJECTS' % subject

        args = [subject, changeType, obj_id] + list(args)

        self.observerLock.acquire()
        for ofunc, osubject, ochangeTypes, oid, cache in self.observers:
            try:
                if (subject == osubject and
                    changeType in ochangeTypes and
                        (oid is None or oid == obj_id)):

                    if not cache:
                        tasks.append(ofunc)
                    else:
                        if ofunc not in self.observerscache:
                            def doQueue(ofunc):
                                self.observerLock.acquire()
                                if ofunc in self.observerscache:
                                    events = self.observerscache[ofunc]
                                    del self.observerscache[ofunc]
                                    del self.observertimers[ofunc]
                                else:
                                    events = []
                                self.observerLock.release()

                                if events:
                                    ofunc(events)

                            t = threading.Timer(cache, doQueue, (ofunc,))
                            t.setName("Notifier-timer-%s" % subject)
                            t.start()

                            self.observerscache[ofunc] = []
                            self.observertimers[ofunc] = t

                        self.observerscache[ofunc].append(args)
            except:
                self._logger.exception("OIDs were %s %s", repr(oid), repr(obj_id))

        self.observerLock.release()
        for task in tasks:
            task(*args)  # call observer function in this thread
