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

from __future__ import with_statement

import os
import re
import sys
import time

from operator import itemgetter

from pyload.network.RequestFactory import getURL
from pyload.plugin.Addon import Expose, Addon, threaded
from pyload.utils import fs_join
from pyload import __status_code__ as release_status


# Case-sensitive os.path.exists
def exists(path):
    if os.path.exists(path):
        if os.name == 'nt':
            dir, name = os.path.split(path)
            return name in os.listdir(dir)
        else:
            return True
    else:
        return False


class UpdateManager(Addon):
    __name    = "UpdateManager"
    __type    = "addon"
    __version = "0.50"

    __config  = [("activated", "bool", "Activated", False),
                 ("checkinterval", "int", "Check interval in hours", 8),
                 ("autorestart", "bool",
                  "Auto-restart pyLoad when required", True),
                 ("checkonstart", "bool", "Check for updates on startup", True),
                 ("checkperiod", "bool",
                  "Check for updates periodically", True),
                 ("reloadplugins", "bool",
                  "Monitor plugin code changes in debug mode", True),
                 ("nodebugupdate", "bool", "Don't update plugins in debug mode", False)]

    __description = """ Check for updates """
    __license     = "GPLv3"
    __authors     = [("Walter Purcaro", "vuolter@gmail.com")]

    SERVER_URL         = "http://updatemanager.pyload.org" if release_status == 5 else None
    MIN_CHECK_INTERVAL = 3 * 60 * 60  #: 3 hours

    def activate(self):
        if self.checkonstart:
            self.update()

        self.initPeriodical()

    def setup(self):
        self.interval = 10
        self.info     = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()}
        self.mtimes   = {}  #: store modification time for each plugin

        if self.getConfig('checkonstart'):
            self.core.api.pauseServer()
            self.checkonstart = True
        else:
            self.checkonstart = False

    def periodical(self):
        if self.core.debug:
            if self.getConfig('reloadplugins'):
                self.autoreloadPlugins()

            if self.getConfig('nodebugupdate'):
                return

        if self.getConfig('checkperiod') \
           and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']:
            self.update()

    @Expose
    def autoreloadPlugins(self):
        """ reload and reindex all modified plugins """
        modules = filter(
            lambda m: m and (m.__name.startswith("pyload.plugin.") or
                             m.__name.startswith("userplugins.")) and
            m.__name.count(".") >= 2, sys.modules.itervalues()
        )

        reloads = []

        for m in modules:
            root, type, name = m.__name.rsplit(".", 2)
            id = (type, name)
            if type in self.core.pluginManager.plugins:
                f = m.__file__.replace(".pyc", ".py")
                if not os.path.isfile(f):
                    continue

                mtime = os.stat(f).st_mtime

                if id not in self.mtimes:
                    self.mtimes[id] = mtime
                elif self.mtimes[id] < mtime:
                    reloads.append(id)
                    self.mtimes[id] = mtime

        return True if self.core.pluginManager.reloadPlugins(reloads) else False

    def server_response(self):
        try:
            return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()

        except Exception:
            self.logWarning(_("Unable to retrieve server to get updates"))

    @Expose
    @threaded
    def update(self):
        """ check for updates """

        self.core.api.pauseServer()

        if self._update() is 2 and self.getConfig('autorestart'):
            self.core.api.restart()
        else:
            self.core.api.unpauseServer()

    def _update(self):
        data = self.server_response()

        self.info['last_check'] = time.time()

        if not data:
            exitcode = 0

        elif data[0] == "None":
            self.logInfo(_("No new pyLoad version available"))
            exitcode = self._updatePlugins(data[1:])

        elif onlyplugin:
            exitcode = 0

        else:
            self.logInfo(_("***  New pyLoad Version %s available  ***") % data[0])
            self.logInfo(_("***  Get it here: https://github.com/pyload/pyload/releases  ***"))
            self.info['pyload']  = True
            self.info['version'] = data[0]
            exitcode = 3

        # Exit codes:
        # -1 = No plugin updated, new pyLoad version available
        #  0 = No plugin updated
        #  1 = Plugins updated
        #  2 = Plugins updated, but restart required
        return exitcode

    def _updatePlugins(self, data):
        """ check for plugin updates """

        exitcode = 0
        updated  = []

        url    = data[0]
        schema = data[1].split('|')

        VERSION = re.compile(r'__version.*=.*("|\')([\d.]+)')

        if "BLACKLIST" in data:
            blacklist  = data[data.index('BLACKLIST') + 1:]
            updatelist = data[2:data.index('BLACKLIST')]
        else:
            blacklist  = []
            updatelist = data[2:]

        updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist]
        blacklist  = [dict(zip(schema, x.split('|'))) for x in blacklist]

        if blacklist:
            type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist]

            # Protect UpdateManager from self-removing
            try:
                type_plugins.remove(("addon", "UpdateManager"))
            except ValueError:
                pass

            for t, n in type_plugins:
                for idx, plugin in enumerate(updatelist):
                    if n == plugin['name'] and t == plugin['type']:
                        updatelist.pop(idx)
                        break

            for t, n in self.removePlugins(sorted(type_plugins)):
                self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % {
                    'type': t,
                    'name': n,
                })

        for plugin in sorted(updatelist, key=itemgetter("type", "name")):
            filename = plugin['name']
            prefix   = plugin['type']
            version  = plugin['version']

            if filename.endswith(".pyc"):
                name = filename[:filename.find("_")]
            else:
                name = filename.replace(".py", "")

            #@TODO: Remove in 0.4.10
            if prefix.endswith("s"):
                type = prefix[:-1]
            else:
                type = prefix

            plugins = getattr(self.core.pluginManager, "%sPlugins" % type)

            oldver = float(plugins[name]['version']) if name in plugins else None
            newver = float(version)

            if not oldver:
                msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)"
            elif newver > oldver:
                msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)"
            else:
                continue

            self.logInfo(_(msg) % {'type': type,
                                   'name': name,
                                   'oldver': oldver,
                                   'newver': newver})
            try:
                content = getURL(url % plugin)
                m = VERSION.search(content)

                if m and m.group(2) == version:
                    with open(fs_join("userplugins", type, filename), "wb") as f:
                        f.write(content)

                    updated.append((type, name))
                else:
                    raise Exception, _("Version mismatch")

            except Exception, e:
                self.logError(_("Error updating plugin: %s") % filename, e)

        if updated:
            self.logInfo(_("*** Plugins updated ***"))

            if self.core.pluginManager.reloadPlugins(updated):
                exitcode = 1
            else:
                self.logWarning(_("Restart pyLoad to reload the updated plugins"))
                self.info['plugins'] = True
                exitcode = 2

            self.manager.dispatchEvent("plugin_updated", updated)
        else:
            self.logInfo(_("No plugin updates available"))

        # Exit codes:
        # 0 = No plugin updated
        # 1 = Plugins updated
        # 2 = Plugins updated, but restart required
        return exitcode

    @Expose
    def removePlugins(self, type_plugins):
        """ delete plugins from disk """

        if not type_plugins:
            return

        removed = set()

        self.logDebug("Requested deletion of plugins: %s" % type_plugins)

        for type, name in type_plugins:
            rootplugins = os.path.join(pypath, "module", "plugins")

            for dir in ("userplugins", rootplugins):
                py_filename  = fs_join(dir, type, name + ".py")
                pyc_filename = py_filename + "c"

                if type == "addon":
                    try:
                        self.manager.deactivateAddon(name)

                    except Exception, e:
                        self.logDebug(e)

                for filename in (py_filename, pyc_filename):
                    if not exists(filename):
                        continue

                    try:
                        os.remove(filename)

                    except OSError, e:
                        self.logError(_("Error removing: %s") % filename, e)

                    else:
                        id = (type, name)
                        removed.add(id)

        #: return a list of the plugins successfully removed
        return list(removed)