# -*- coding: utf-8 -*-

###############################################################################
#   Copyright(c) 2008-2013 pyLoad Team
#   http://www.pyload.org
#
#   This file is part of pyLoad.
#   pyLoad is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as
#   published by the Free Software Foundation, either version 3 of the
#   License, or (at your option) any later version.
#
#   Subjected to the terms and conditions in LICENSE
#
#   @author: RaNaN
###############################################################################

import __builtin__

from thread import start_new_thread
from threading import RLock

from types import MethodType

from pyload.threads.AddonThread import AddonThread
from pyload.PluginManager import literal_eval
from utils import lock, to_string

class AddonManager:
    """ Manages addons, loading, unloading.  """

    def __init__(self, core):
        self.core = core
        self.config = self.core.config

        __builtin__.addonManager = self #needed to let addons register themselves

        self.log = self.core.log
        # TODO: multiuser, addons can store the user itself, probably not needed here
        self.plugins = {}
        self.methods = {} # dict of names and list of methods usable by rpc
        self.events = {} # Contains event that will be registered

        self.lock = RLock()
        self.createIndex()

        # manage addons on config change
        self.addEvent("config:changed", self.manageAddons)

    @lock
    def callInHooks(self, event, eventName, *args):
        """  Calls a method in all addons and catch / log errors"""
        for plugin in self.plugins.itervalues():
            self.call(plugin, event, *args)
        self.dispatchEvent(eventName, *args)

    def call(self, addon, f, *args):
        try:
            func = getattr(addon, f)
            return func(*args)
        except Exception, e:
            addon.logError(_("Error when executing %s" % f), e)
            self.core.print_exc()

    def addRPC(self, plugin, func, doc):
        doc = doc.strip() if doc else ""

        if plugin in self.methods:
            self.methods[plugin][func] = doc
        else:
            self.methods[plugin] = {func: doc}

    def callRPC(self, plugin, func, args):
        if not args: args = []
        else:
            args = literal_eval(args)

        plugin = self.plugins[plugin]
        f = getattr(plugin, func)
        return f(*args)

    @lock
    def createIndex(self):
        active = []
        deactive = []

        for pluginname in self.core.pluginManager.getPlugins("addons"):
            try:
                # check first for builtin plugin
                attrs = self.core.pluginManager.loadAttributes("addons", pluginname)
                internal = attrs.get("internal", False)

                if internal or self.core.config.get(pluginname, "activated"):
                    pluginClass = self.core.pluginManager.loadClass("addons", pluginname)

                    if not pluginClass: continue

                    plugin = pluginClass(self.core, self)
                    self.plugins[pluginClass.__name__] = plugin

                    # hide internals from printing
                    if not internal and plugin.isActivated():
                        active.append(pluginClass.__name__)
                    else:
                        self.log.debug("Loaded internal plugin: %s" % pluginClass.__name__)
                else:
                    deactive.append(pluginname)


            except:
                self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
                self.core.print_exc()

        self.log.info(_("Activated addons: %s") % ", ".join(sorted(active)))
        self.log.info(_("Deactivate addons: %s") % ", ".join(sorted(deactive)))

    def manageAddons(self, plugin, name, value):
        # TODO: user

        # check if section was a plugin
        if plugin not in self.core.pluginManager.getPlugins("addons"):
            return

        if name == "activated" and value:
            self.activateAddon(plugin)
        elif name == "activated" and not value:
            self.deactivateAddon(plugin)

    @lock
    def activateAddon(self, plugin):
        #check if already loaded
        if plugin in self.plugins:
            return

        pluginClass = self.core.pluginManager.loadClass("addons", plugin)

        if not pluginClass: return

        self.log.debug("Plugin loaded: %s" % plugin)

        plugin = pluginClass(self.core, self)
        self.plugins[pluginClass.__name__] = plugin

        # active the addon in new thread
        start_new_thread(plugin.activate, tuple())
        self.registerEvents() # TODO: BUG: events will be destroyed and not re-registered

    @lock
    def deactivateAddon(self, plugin):
        if plugin not in self.plugins:
            return
        else:
            addon = self.plugins[plugin]

        if addon.__internal__: return

        self.call(addon, "deactivate")
        self.log.debug("Plugin deactivated: %s" % plugin)

        #remove periodic call
        self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(addon.cb))
        del self.plugins[addon.__name__]

        #remove event listener
        for f in dir(addon):
            if f.startswith("__") or type(getattr(addon, f)) != MethodType:
                continue
            self.core.eventManager.removeFromEvents(getattr(addon, f))

    def activateAddons(self):
        self.log.info(_("Activating Plugins..."))
        for plugin in self.plugins.itervalues():
            if plugin.isActivated():
                self.call(plugin, "activate")

        self.registerEvents()

    def deactivateAddons(self):
        """  Called when core is shutting down """
        self.log.info(_("Deactivating Plugins..."))
        for plugin in self.plugins.itervalues():
            self.call(plugin, "deactivate")

    def downloadPreparing(self, pyfile):
        self.callInHooks("downloadPreparing", "download:preparing", pyfile)

    def downloadFinished(self, pyfile):
        self.callInHooks("downloadFinished", "download:finished", pyfile)

    def downloadFailed(self, pyfile):
        self.callInHooks("downloadFailed", "download:failed", pyfile)

    def packageFinished(self, package):
        self.callInHooks("packageFinished", "package:finished", package)

    def beforeReconnecting(self, ip):
        self.callInHooks("beforeReconnecting", "reconnecting:before", ip)

    def afterReconnecting(self, ip):
        self.callInHooks("afterReconnecting", "reconnecting:after", ip)

    @lock
    def startThread(self, function, *args, **kwargs):
        AddonThread(self.core.threadManager, function, args, kwargs)

    def activePlugins(self):
        """ returns all active plugins """
        return [x for x in self.plugins.itervalues() if x.isActivated()]

    def getAllInfo(self):
        """returns info stored by addon plugins"""
        info = {}
        for name, plugin in self.plugins.iteritems():
            if plugin.info:
                #copy and convert so str
                info[name] = dict(
                    [(x, to_string(y)) for x, y in plugin.info.iteritems()])
        return info

    def getInfo(self, plugin):
        info = {}
        if plugin in self.plugins and self.plugins[plugin].info:
            info = dict([(x, to_string(y))
            for x, y in self.plugins[plugin].info.iteritems()])

        return info

    def addEventListener(self, plugin, func, event):
        """ add the event to the list """
        if plugin not in self.events:
            self.events[plugin] = []
        self.events[plugin].append((func, event))

    def registerEvents(self):
        """ actually register all saved events """
        for name, plugin in self.plugins.iteritems():
            if name in self.events:
                for func, event in self.events[name]:
                    self.addEvent(event, getattr(plugin, func))
                # clean up
                del self.events[name]

    def addEvent(self, *args):
        self.core.eventManager.addEvent(*args)

    def dispatchEvent(self, *args):
        self.core.eventManager.dispatchEvent(*args)