import config
import db
import os
from shutil import rmtree

from plugins.model import Plugin
from plugins.pluginbase import PluginBase

class PluginManager:
    def __init__(self):
        # Instance list
        self.plugins = []
        orm = db.connect()

        for plugin_name in self._get_plugins_directories():
            instance = self._instance_plugin(plugin_name)
            self.plugins.append(instance)

        enabled_plugins = [i.name for i in orm.query(Plugin).filter(Plugin.enabled==True).all()]

        for plugin in self.plugins:
            if plugin.name in enabled_plugins:
                self.enable_plugin(plugin.name)

    def enable_plugin(self, name):
        '''
        Enables a plugin if all the deps are met. Else it raises UnmetDependencyException.
        Returns the list of enabled plugins. In this case, enable means this plugins will be enabled
        in the future: this function will only be called when the user enables a plugin in the
        pluginmanager.PluginListView.
        '''
        enabled_plugins = []
        plugin = self.get_plugin_by_name(name)
        if not plugin:
            return enabled_plugins

        plugin = self._instance_real_plugin(plugin, name)

        plugins_deps = self._get_dependencies(plugin)

        for plugin_dep in plugins_deps:
            if not plugin_dep.is_enabled():
                self._enable_plugin(plugin_dep)
                enabled_plugins.append(plugin_dep)

        return enabled_plugins

    def disable_plugin(self, name):
        '''
        Disables a plugin and all plugins that have given plugin as a dependency.
        Returns the list of disabled plugins. In this case, disable means this plugins will be disabled
        in the future: this function will only be called when the user disables a plugin in the
        pluginmanager.PluginListView.
        '''
        disabled_plugins = []
        plugin = self.get_plugin_by_name(name)
        if plugin == None:
            return disabled_plugins

        plugins_deps = self._get_reverse_dependencies(plugin)

        for plugin_dep in plugins_deps:
            if plugin_dep.is_enabled():
                self._disable_plugin(plugin_dep)
                disabled_plugins.append(plugin_dep)

        return disabled_plugins

    def uninstall_plugin(self, name, delete_files):
        '''
        Uninstall the given plugin. There are two ways of uninstall:
          * When delete_files is false, then this function only deletes
          all plugin's data in the db. In this way you can reinstall the
          plugin when you want.
          * When delete_files is true, then this function delete all plugin's
          data in the db and plugin's directory. In this way all data is
          deleted, so you have to get the whole plugin to reinstall.

        Either way, this function always removes the installed files in static/
        and templates/ for this plugin.

        Before uninstalling a plugin, it MUST be disabled and obviously all
        the plugins that have the plugin as a dependency.
        '''
        plugin = self.get_plugin_by_name(name)
        if plugin.enabled == True:
            return False

        plugin.uninstall()

        # Remove from the plugins' table of data base
        orm = db.connect()
        query = orm.query(Plugin).filter(Plugin.name == name)
        if query.count() == 1:
            orm.delete(query.first())
            orm.commit()

        static_dir = os.path.join(config.curdir, 'static/plugins', name)
        if os.path.islink(static_dir):
            os.unlink(static_dir)
        templates_dir = os.path.join(config.curdir, 'templates/plugins', name)
        if os.path.islink(templates_dir):
            os.unlink(templates_dir)

        # Deleting
        if delete_files:
            path = os.path.join(config.pluginsdir, name)
            rmtree(path)
            # Remove from the list of plugin
            self.plugins.remove(plugin)

        # removing plugin tables from database
        try:
            pmodule = __import__('plugins.%s.%s' % (plugin.name, 'model'), fromlist=["true"])
            db.drop_from_model(pmodule)
        except:
            pass

        return True

    def check_for_new_plugins(self):
        '''
        Look for new instances in plugins' directory and
        add to self.plugins.
        '''
        plugins_name = (p.name for p in self.plugins)

        for plugin_name in self._get_plugins_directories():
            if not plugin_name in plugins_name:
                instance = self._instance_plugin(plugin_name)
                self.plugins.append(instance)

    def get_plugin_by_name(self, name):
        '''
        Return the instance of the plugin whose name is given.
        None is returned if the instance is not found.
        '''
        for plugin in self.plugins:
            if plugin.name == name:
                return plugin
        return None

    def get_enabled_plugins(self):
        '''
        Return a instance list of the enabled plugins.
        '''
        return (i for i in self.plugins if i.is_enabled())

    def _enable_plugin(self, plugin):
        '''
        Enable a plugin without verify the dependencies.
        '''
        orm = db.connect()
        dbplugin = orm.query(Plugin).filter_by(name=plugin.name).first()

        if not dbplugin:
            # This means that the plugin is not installed, so we proceed to install it now
            self._install_plugin(plugin)

        else:
            dbplugin.enabled = True
            orm.commit()

        plugin.enable()
        plugin.enabled = True


    def _install_plugin(self, plugin):
        '''
        Called only by _enable_plugin when a plugin has not been installed before.

        * Creates the tables in the database from the plugin model
        * installs the static/ and templates/ plugin directories in
          static/plugins/<pluginname> and templates/plugins/<pluginname>/
        * calls to the install() function of the given plugin
        '''

        # * Create tables in the db from the plugin model
        orm = db.connect()
        dbplugin = Plugin(plugin)
        dbplugin.enabled = True
        orm.add(dbplugin)
        orm.commit()
        try:
            pmodule = __import__('plugins.%s.%s' % (plugin.name, 'model'), fromlist=["true"])
            db.create_from_model(pmodule)
        except:
            pass

        # * install static
        plugin_static_dir = os.path.join(config.pluginsdir, plugin.name, 'static')
        if os.path.isdir(plugin_static_dir):
            # only will install it if the plugin has a static/ dir, obviously
            plugins_static_dir = os.path.join(config.curdir, 'static/plugins')
            if not os.path.isdir(plugins_static_dir):
                # the static/plugins directory might not exist because we cannot create
                # a directory in git
                os.mkdir(plugins_static_dir)

            # finally copy the files
            new_static_dir = os.path.join(plugins_static_dir, plugin.name)
            os.symlink(plugin_static_dir, new_static_dir)

        # * install templates
        plugin_templates_dir = os.path.join(config.pluginsdir, plugin.name, 'templates')
        if os.path.isdir(plugin_templates_dir):
            plugins_templates_dir = os.path.join(config.curdir, 'templates/plugins')
            if not os.path.isdir(plugins_templates_dir):
                os.mkdir(plugins_templates_dir)
            new_templates_dir = os.path.join(plugins_templates_dir, plugin.name)
            os.symlink(plugin_templates_dir, new_templates_dir)

        plugin.install()

    def _disable_plugin(self, plugin):
        '''
        Disable a plugin without verify the dependencies.
        '''
        orm = db.connect()
        orm.query(Plugin).filter_by(name=plugin.name).first().enabled = False
        plugin.disable()
        plugin.enabled = False
        orm.commit()

    def _get_dependencies(self, plugin, checked_deps=None,
            checked_plugins=None):
        '''
        Recursively returns the list of the dependencies of the given plugins.
        If any of the dependencies is not met then it will raise a
        UnMetDependencyException.

        The list of plugins dependencies will include the plugin itself which
        will be at the end of the list, and the list of dependencies will be
        ordered in such a way that they can be safely activated one by one,
        because any of the listed plugins will be preceded by its dependencies.

        It will only be called by enable_plugin().
        '''

        if checked_deps is None: checked_deps = []
        if checked_plugins is None: checked_plugins = []

        # With this avoid infinite recursion with two plugins have mutual
        # dependency.
        checked_plugins.append(plugin.name)
        for dep in plugin.get_dependencies():
            if dep.dependon in (i.name for i in checked_deps):
                continue
            if dep.dependon in checked_plugins:
                continue
            if not dep.is_met():
                # raise UnmetDependencyException(dep.dependon)
                # FIXME
                pass
            self._get_dependencies(dep.get_plugin(), checked_deps,
                    checked_plugins)

        checked_deps.append(plugin)
        return checked_deps

    def _get_reverse_dependencies(self, plugin, checked_deps=None,
            checked_plugins=None):
        '''
        Recursively returns the list of the reverse dependencies of the given
        plugins.

        The list of plugins dependencies will include the plugin itself which
        will be at the end of the list, and the list of dependencies will be
        ordered in such a way that they can be safely desactivated one by one,
        because any of the listed plugins will be preceded by its dependencies.

        It will only be called by disable_plugin().
        '''

        if checked_deps is None: checked_deps = []
        if checked_plugins is None: checked_plugins = []
        # With this avoid infinite recursion with two plugins have mutual
        # dependency.
        checked_plugins.append(plugin.name)
        # For each enabled plugin
        for p in self.get_enabled_plugins():
            # not checked before
            if p in checked_plugins:
                continue
            if p.name in checked_plugins:
                continue
            # looking for a dependency which is the given
            for dep in p.get_dependencies():
                if dep.is_met_by(plugin):
                    self._get_reverse_dependencies(self, p, checked_deps,
                            checked_plugins)

        checked_deps.append(plugin)
        return checked_deps

    def _instance_plugin(self, name):
        '''
        Create a PluginBase object and load plugin configuration
        '''

        plugin = PluginBase(name)
        plugin.read_config()
        return plugin

    def _get_plugins_directories(self):
        '''
        Return the list of the directories of plugins that there are
        in the plugins directory.
        '''
        return (i for i in os.listdir(config.pluginsdir)
                if os.path.isdir(os.path.join(config.pluginsdir, i)))

    def _instance_real_plugin(self, oldp, name):

        '''
        Instance real plugin module and load its config, replace oldp in
        self.plugins with new plugin instance.

        return the new plugin instace.
        '''

        pmodule = __import__('plugins.' + name, fromlist=["true"])
        plugin = pmodule.Plugin(name)
        plugin.read_config()

        index = self.plugins.index(oldp)
        self.plugins.pop(index)
        self.plugins.insert(index, plugin)

        return plugin
