'''
Modules
=======

Modules are classes that can be loaded when a Kivy application is starting. The
loading of modules is managed by the config file. Currently, we include:

    * :class:`~kivy.modules.touchring`: Draw a circle around each touch.
    * :class:`~kivy.modules.monitor`: Add a red topbar that indicates the FPS
      and a small graph indicating input activity.
    * :class:`~kivy.modules.keybinding`: Bind some keys to actions, such as a
      screenshot.
    * :class:`~kivy.modules.recorder`: Record and playback a sequence of
      events.
    * :class:`~kivy.modules.screen`: Emulate the characteristics (dpi/density/
      resolution) of different screens.
    * :class:`~kivy.modules.inspector`: Examines your widget hierarchy and
      widget properties.
    * :class:`~kivy.modules.webdebugger`: Realtime examination of your app
      internals via a web browser.
    * :class:`~kivy.modules.joycursor`: Navigate in your app with a joystick.
    * :class:`~kivy.modules.showborder`: Show widget's border.

Modules are automatically loaded from the Kivy path and User path:

    * `PATH_TO_KIVY/kivy/modules`
    * `HOME/.kivy/mods`

Activating a module
-------------------

There are various ways in which you can activate a kivy module.

Activate a module in the config
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To activate a module this way, you can edit your configuration file (in your
`HOME/.kivy/config.ini`)::

    [modules]
    # uncomment to activate
    touchring =
    # monitor =
    # keybinding =

Only the name of the module followed by "=" is sufficient to activate the
module.

Activate a module in Python
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Before starting your application, preferably at the start of your import, you
can do something like this::

    import kivy
    kivy.require('1.0.8')

    # Activate the touchring module
    from kivy.config import Config
    Config.set('modules', 'touchring', '')

Activate a module via the commandline
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When starting your application from the commandline, you can add a
*-m <modulename>* to the arguments. For example::

    python main.py -m webdebugger

.. note::
    Some modules, such as the screen, may require additional parameters. They
    will, however, print these parameters to the console when launched without
    them.


Create your own module
----------------------

Create a file in your `HOME/.kivy/mods`, and create 2 functions::

    def start(win, ctx):
        pass

    def stop(win, ctx):
        pass

Start/stop are functions that will be called for every window opened in
Kivy. When you are starting a module, you can use these to store and
manage the module state. Use the `ctx` variable as a dictionary. This
context is unique for each instance/start() call of the module, and will
be passed to stop() too.

'''

__all__ = ('Modules', )

from kivy.config import Config
from kivy.logger import Logger
import kivy
import importlib
import os
import sys


class ModuleContext:
    '''Context of a module

    You can access to the config with self.config.
    '''

    def __init__(self):
        self.config = {}

    def __repr__(self):
        return repr(self.config)


class ModuleBase:
    '''Handle Kivy modules. It will automatically load and instantiate the
    module for the general window.'''

    def __init__(self, **kwargs):
        self.mods = {}
        self.wins = []

    def add_path(self, path):
        '''Add a path to search for modules in'''
        if not os.path.exists(path):
            return
        if path not in sys.path:
            sys.path.append(path)
        dirs = os.listdir(path)
        for module in dirs:
            name, ext = os.path.splitext(module)
            # accept only python extensions
            if ext not in ('.py', '.pyo', '.pyc') or name == '__init__':
                continue
            self.mods[name] = {
                'name': name,
                'activated': False,
                'context': ModuleContext()}

    def list(self):
        '''Return the list of available modules'''
        return self.mods

    def import_module(self, name):
        try:
            modname = 'kivy.modules.{0}'.format(name)
            module = importlib.__import__(name=modname)
            module = sys.modules[modname]
        except ImportError:
            try:
                module = importlib.__import__(name=name)
                module = sys.modules[name]
            except ImportError:
                Logger.exception('Modules: unable to import <%s>' % name)
                # protect against missing module dependency crash
                self.mods[name]['module'] = None
                return
        # basic check on module
        if not hasattr(module, 'start'):
            Logger.warning('Modules: Module <%s> missing start() function' %
                           name)
            return
        if not hasattr(module, 'stop'):
            err = 'Modules: Module <%s> missing stop() function' % name
            Logger.warning(err)
            return
        self.mods[name]['module'] = module

    def activate_module(self, name, win):
        '''Activate a module on a window'''
        if name not in self.mods:
            Logger.warning('Modules: Module <%s> not found' % name)
            return

        mod = self.mods[name]

        # ensure the module has been configured
        if 'module' not in mod:
            self._configure_module(name)

        pymod = mod['module']
        if not mod['activated']:
            context = mod['context']
            msg = 'Modules: Start <{0}> with config {1}'.format(
                  name, context)
            Logger.debug(msg)
            pymod.start(win, context)
            mod['activated'] = True

    def deactivate_module(self, name, win):
        '''Deactivate a module from a window'''
        if name not in self.mods:
            Logger.warning('Modules: Module <%s> not found' % name)
            return
        if 'module' not in self.mods[name]:
            return

        module = self.mods[name]['module']
        if self.mods[name]['activated']:
            module.stop(win, self.mods[name]['context'])
            self.mods[name]['activated'] = False

    def register_window(self, win):
        '''Add the window to the window list'''
        if win not in self.wins:
            self.wins.append(win)
        self.update()

    def unregister_window(self, win):
        '''Remove the window from the window list'''
        if win in self.wins:
            self.wins.remove(win)
        self.update()

    def update(self):
        '''Update the status of the module for each window'''
        modules_to_activate = [x[0] for x in Config.items('modules')]
        for win in self.wins:
            for name in self.mods:
                if name not in modules_to_activate:
                    self.deactivate_module(name, win)
            for name in modules_to_activate:
                try:
                    self.activate_module(name, win)
                except:
                    import traceback
                    traceback.print_exc()
                    raise

    def configure(self):
        '''(internal) Configure all the modules before using them.
        '''
        modules_to_configure = [x[0] for x in Config.items('modules')]
        for name in modules_to_configure:
            if name not in self.mods:
                Logger.warning('Modules: Module <%s> not found' % name)
                continue
            self._configure_module(name)

    def _configure_module(self, name):
        if 'module' not in self.mods[name]:
            try:
                self.import_module(name)
            except ImportError:
                return

        # convert configuration like:
        # -m mjpegserver:port=8080,fps=8
        # and pass it in context.config token
        config = dict()

        args = Config.get('modules', name)
        if args != '':
            values = Config.get('modules', name).split(',')
            for value in values:
                x = value.split('=', 1)
                if len(x) == 1:
                    config[x[0]] = True
                else:
                    config[x[0]] = x[1]

        self.mods[name]['context'].config = config

        # call configure if module have one
        if hasattr(self.mods[name]['module'], 'configure'):
            self.mods[name]['module'].configure(config)

    def usage_list(self):
        print('Available modules')
        print('=================')
        for module in sorted(self.list()):
            if 'module' not in self.mods[module]:
                self.import_module(module)

            # ignore modules without docstring
            if not self.mods[module]['module'].__doc__:
                continue

            text = self.mods[module]['module'].__doc__.strip("\n ")
            text = text.split('\n')
            # make sure we don't get IndexError along the way
            # then pretty format the header
            if len(text) > 2:
                if text[1].startswith('='):
                    # '\n%-12s: %s' -> 12 spaces + ": "
                    text[1] = '=' * (14 + len(text[1]))
            text = '\n'.join(text)
            print('\n%-12s: %s' % (module, text))


Modules = ModuleBase()
Modules.add_path(kivy.kivy_modules_dir)
if 'KIVY_DOC' not in os.environ:
    Modules.add_path(kivy.kivy_usermodules_dir)

if __name__ == '__main__':
    print(Modules.list())
