'''
Plugin base module
'''

import os
import sys
import re
import web
import codecs
import config
import ConfigParser
from action import Action, ActionInstance
from dependency import Dependency, Version

DEP = re.compile(r'^(?P<pname>\w+)\s*\((?P<operator>\>=|\>|=|\<=|\<)(?P<major>.*)\.(?P<minor>.*)\.(?P<release>.*)\)\s*$')


class ActionNotFoundError(Exception):
    pass

class ViewNotFoundError(Exception):
    pass

class PluginBase(object):
    '''
    Plugin base class. Each plugin should inherit from this class.

    Variables documentation:

    urls: This variable is a tuple of pairs in the form "url-regexp, view" as in web.py
    applications, but for this plugin only.
    '''
    def __init__(self, name):
        self.name = name
        self.human_name = 'nonamed'
        self.author = 'anonymous'
        self.description = 'nodescription'
        self._version = 'noversion'
        self.enabled = False
        self.urls = []
        self.actions_for_url = []
        self._dependencies = []
        self.actions = {}

    def get_dependencies(self):
        '''
        Returns the depencies list.
        '''
        return self._dependencies

    def set_dependencies(self, depstr):
        '''
        Generates the dependencies given a string.
        It's necesary for parsing the configuration file.
        '''
        deps = map(str.strip, depstr.split(','))
        for dep in deps:
            match = DEP.match(dep)
            print dep
            print match
            pname, operator, major, minor, release = match.groups()
            dep = Dependency(pname, operator, major, minor, release)
            self._dependencies.append(dep)

    depend = property(get_dependencies, set_dependencies)

    def get_version(self):
        '''
        Returns the version.
        '''
        return self._version

    def set_version(self, verstr):
        '''
        Generates the version given a string.
        It's necesary for parsing the configuration file.
        '''
        major, minor, release = verstr.split('.')
        self._version = Version(major, minor, release)

    version = property(get_version, set_version)

    def is_enabled(self):
        '''
        Returns if the plugin is enabled in exection time.
        '''
        return self.enabled

    def read_config(self):
        '''
        Parses plugin conf file and sets all values as object
        attributes.
        '''

        path = os.path.join(config.pluginsdir, self.name, 'plugin.conf')
        self.read_config_path(path)

    def read_config_path(self, path):
        '''
        Given a path, it generates the initial configuration of a plugin.
        '''
        parser = ConfigParser.ConfigParser()
        parser.readfp(codecs.open(path, 'r', sys.getfilesystemencoding(), errors="ignore"))

        # reading plugin config
        for k, v in  parser.items('plugin'):
            if k == 'name':
                continue
            setattr(self, k, v)

        # reading actions config
        actions = [i for i in parser.sections() if i.startswith('action/')]
        for a in actions:
            action = Action(self)
            for k, v in parser.items(a):
                if k == "appear_in_main_panel":
                    v = parser.getboolean(a, k)

                setattr(action, k, v)

            # Get the name from the section
            action.name = a.partition('/')[2]
            # Adding to the dict
            self.actions[action.name] = action

    def get_actions(self):
        '''
        Returns the actions.
        '''
        return self.actions.values()

    def get_main_panel_actions(self):
        '''
        Returns the actions which appears in the main panel
        '''
        return (a for a in self.actions.values() if a.appear_in_main_panel)


    def enable(self):
        '''
        This function initialize the plugin in such a way that it can used.
        It will be called in two cases:
        1.  When the plugin is disabled and it's going to be enabled by the
            admins of the aplication. In this case the install function will
            also be called if the plugin has not previously been
            installed.
        2.  Once the plugin is already enabled and installed the pluginmanager
            will instanciate the plugin and then will call this enable
            function. This is because the enable function will always before
            the plugin can be used.

        You can reimplement this function if you want to execute custom
        actions when your plugin is going to be used.
        '''
        pass

    def disable(self):
        '''
        This function will be called when the plugin is enabled and it's
        going to be disabled by the admins of the aplication. It is called only
        by the pluginmanager. WARNING: Note that disable will NOT be called
        when the application shutdowns. Use __del__ for that.

        You can reimplement this function if you want to execute custom
        actions when your plugin is going to be disabled.
        '''
        pass

    def install(self):
        '''
        This function will be called when the plugin is enabled the first
        time.

        You can reimplement this function if you want to install some tables
        in the data base, or to do something only once.
        '''
        pass

    def uninstall(self):
        '''
        This function will be called when the user wants to delete the plugin
        from the system.

        You can reimplement this function like a cleanup function. You MUST
        erase all data that plugin generated when it's not goint to be used
        ever. For example all tables in the data base or data generated in
        install function.
        '''
        pass

    def _request(self, args, plugin_url, method):
        '''
        Request a GET or a POST petition to the plugin. Redirects a url to a view inside
        the plugin and returns the data that the appropiate view returns. The "method"
        argument can be either "GET" or "POST".

        This function is ONLY called by PluginView. When a request of type
        /plugin/<plugin_name>/<args> is received, PluginView deals with it and acts as a
        proxy, calling to this _request function of the plugin called "plugin_name", with
        args= "args".

        Each plugin deals with GET and POST requests by configuring the self.url tuple of
        pairs url-regexp, view, as webpy applications do.

        NOTE: We don't expand groupdict args, only groups
        '''

        for i in xrange(0, len(self.urls), 2):
            path = self.urls[i]
            view = self.urls[i+1]
            view_instance = view()
            match = re.match(path, args)
            if not match:
                continue

            # Found the match!
            if method == "GET":
                return view_instance.GET(*match.groups())
            else:
                return view_instance.POST(*match.groups())

        raise web.notfound()

    def get_options(self):
        '''
        This function has to return a form. This form contains the options
        of the plugin that the user can change. If you want options
        in your plugin you have to reimplement this function.
        Also you will want to reimplement save_options function which is
        called when the user submits.

        In the other hand if you don't need any options you haven't to do
        anything.
        '''
        return None

    def save_options(self, inputs):
        '''
        This function is called when the user filles the form returned
        by get_options, which is passed like argument.
        It's useful to save the user preferences.
        You can reimplement this function.
        '''
        pass

    def instance_action(self, action, args):
        '''
        Instance an action given the action args. It will
        use the urls var to fill the data for a given action with args.
        '''
        instanced_actions=[]
        for i in xrange(0, len(self.urls), 2):
            path = self.urls[i]
            if path.startswith("action/%s" % action.name):
                path_filled = re.sub('\([^)]+\)', '%s', path) % args

                # sanity check: see if path_filled  matches path, and also this allows
                # for multiple action urls per action
                match = re.match(path, path_filled)
                if not match:
                    continue
                url = "/plugin/" + action.owner.name + "/" + path_filled
                return ActionInstance(action, url)

        # Happens if couldn't instance action, which shouldn't happen. it can only happen
        # if the plugin is badly written
        raise Exception('No action url valid for the given args. Probably the plugin %s fault' % self.name)

    def get_action_url(self, action_name):
        '''
        Returns an automatic url for a given action
        '''
        if not self.actions_for_url:
            raise ActionNotFoundError("actions_for_url was not defined, perhaps you need to define it before urls in your plugin?")

        # find the action in actions_for_url
        path = None
        for i in xrange(0, len(self.actions_for_url), 2):
            path = self.actions_for_url[i]
            action = self.actions_for_url[i + 1]
            if action == action_name:
                break

        if not path:
            raise ActionNotFoundError()

        path_args = '/'.join(re.findall('\([^)]+\)', path))

        # generate the action url
        ret = 'action/' + action_name + '/' + path_args
        return ret

    def fill_url(self, view, *args):
        '''
        Generate an url for a given view
        '''
        for i in xrange(0, len(self.urls), 2):
            base_url = self.urls[i]
            current_view = self.urls[i + 1]
            if view != current_view:
                continue
            return '/plugin/' +self.name + "/" +\
                re.sub('\([^)]+\)', '%s', base_url) % args

        raise ViewNotFoundError()

    def get_action_instances_for_url(self, url):
        '''
        Returns the actions for a given url (i.e. '/plugin/hardware_inventory/view/454'). DEPRECATED, use
        get_action_instances_for_view instead.
        '''
        action_instances = []
        for i in xrange(0, len(self.actions_for_url), 2):
            path = self.actions_for_url[i]
            match = re.match(path, url)
            if not match:
                continue
            action = self.actions[self.actions_for_url[i + 1]]
            instanced_action = self.instance_action(action, match.groups())
            if instanced_action:
                action_instances += [instanced_action]
        return action_instances

    def get_action_instances_for_view(self, view, args):
        '''
        Returns the actions for a given view and its arguments
        '''
        return []
