#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#

"""

The AlertManager handles all the libtorrent alerts.

This should typically only be used by the Core. Plugins should utilize the
`:mod:EventManager` for similar functionality.

"""
import logging
from types import SimpleNamespace

from twisted.internet import reactor

import deluge.component as component
from deluge._libtorrent import lt
from deluge.common import decode_bytes

log = logging.getLogger(__name__)


class AlertManager(component.Component):
    """AlertManager fetches and processes libtorrent alerts"""

    def __init__(self):
        log.debug('AlertManager init...')
        component.Component.__init__(self, 'AlertManager', interval=0.3)
        self.session = component.get('Core').session

        # Increase the alert queue size so that alerts don't get lost.
        self.alert_queue_size = 10000
        self.set_alert_queue_size(self.alert_queue_size)

        alert_mask = (
            lt.alert.category_t.error_notification
            | lt.alert.category_t.port_mapping_notification
            | lt.alert.category_t.storage_notification
            | lt.alert.category_t.tracker_notification
            | lt.alert.category_t.status_notification
            | lt.alert.category_t.ip_block_notification
            | lt.alert.category_t.performance_warning
            | lt.alert.category_t.file_progress_notification
        )

        self.session.apply_settings({'alert_mask': alert_mask})

        # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
        self.handlers = {}
        self.delayed_calls = []

    def update(self):
        self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
        self.handle_alerts()

    def stop(self):
        for delayed_call in self.delayed_calls:
            if delayed_call.active():
                delayed_call.cancel()
        self.delayed_calls = []

    def register_handler(self, alert_type, handler):
        """
        Registers a function that will be called when 'alert_type' is pop'd
        in handle_alerts.  The handler function should look like: handler(alert)
        Where 'alert' is the actual alert object from libtorrent.

        :param alert_type: str, this is string representation of the alert name
        :param handler: func(alert), the function to be called when the alert is raised
        """
        if alert_type not in self.handlers:
            # There is no entry for this alert type yet, so lets make it with an
            # empty list.
            self.handlers[alert_type] = []

        # Append the handler to the list in the handlers dictionary
        self.handlers[alert_type].append(handler)
        log.debug('Registered handler for alert %s', alert_type)

    def deregister_handler(self, handler):
        """
        De-registers the `:param:handler` function from all alert types.

        :param handler: func, the handler function to deregister
        """
        # Iterate through all handlers and remove 'handler' where found
        for (dummy_key, value) in self.handlers.items():
            if handler in value:
                # Handler is in this alert type list
                value.remove(handler)

    def handle_alerts(self):
        """
        Pops all libtorrent alerts in the session queue and handles them appropriately.
        """
        alerts = self.session.pop_alerts()
        if not alerts:
            return

        num_alerts = len(alerts)
        if log.isEnabledFor(logging.DEBUG):
            log.debug('Alerts queued: %s', num_alerts)
        if num_alerts > 0.9 * self.alert_queue_size:
            log.warning(
                'Warning total alerts queued, %s, passes 90%% of queue size.',
                num_alerts,
            )

        # Loop through all alerts in the queue
        for alert in alerts:
            alert_type = type(alert).__name__
            # Display the alert message
            if log.isEnabledFor(logging.DEBUG):
                log.debug('%s: %s', alert_type, decode_bytes(alert.message()))
            # Call any handlers for this alert type
            if alert_type in self.handlers:
                for handler in self.handlers[alert_type]:
                    if log.isEnabledFor(logging.DEBUG):
                        log.debug('Handling alert: %s', alert_type)
                    # Copy alert attributes
                    alert_copy = SimpleNamespace(
                        **{
                            attr: getattr(alert, attr)
                            for attr in dir(alert)
                            if not attr.startswith('__')
                        }
                    )
                    self.delayed_calls.append(reactor.callLater(0, handler, alert_copy))

    def set_alert_queue_size(self, queue_size):
        """Sets the maximum size of the libtorrent alert queue"""
        log.info('Alert Queue Size set to %s', queue_size)
        self.alert_queue_size = queue_size
        component.get('Core').apply_session_setting(
            'alert_queue_size', self.alert_queue_size
        )
