# -*- coding: utf-8 -*-
"""
    flask.module
    ~~~~~~~~~~~~

    Implements a class that represents module blueprints.

    :copyright: (c) 2010 by Armin Ronacher.
    :license: BSD, see LICENSE for more details.
"""

from .helpers import _PackageBoundObject, _endpoint_from_view_func


def _register_module(module, static_path):
    """Internal helper function that returns a function for recording
    that registers the `send_static_file` function for the module on
    the application if necessary.  It also registers the module on
    the application.
    """
    def _register(state):
        state.app.modules[module.name] = module
        # do not register the rule if the static folder of the
        # module is the same as the one from the application.
        if state.app.root_path == module.root_path:
            return
        path = static_path
        if path is None:
            path = state.app.static_path
        if state.url_prefix:
            path = state.url_prefix + path
        state.app.add_url_rule(path + '/<path:filename>',
                               endpoint='%s.static' % module.name,
                               view_func=module.send_static_file,
                               subdomain=module.subdomain)
    return _register


class _ModuleSetupState(object):

    def __init__(self, app, url_prefix=None, subdomain=None):
        self.app = app
        self.url_prefix = url_prefix
        self.subdomain = subdomain


class Module(_PackageBoundObject):
    """Container object that enables pluggable applications.  A module can
    be used to organize larger applications.  They represent blueprints that,
    in combination with a :class:`Flask` object are used to create a large
    application.

    A module is like an application bound to an `import_name`.  Multiple
    modules can share the same import names, but in that case a `name` has
    to be provided to keep them apart.  If different import names are used,
    the rightmost part of the import name is used as name.

    Here an example structure for a larger appliation::

        /myapplication
            /__init__.py
            /views
                /__init__.py
                /admin.py
                /frontend.py

    The `myapplication/__init__.py` can look like this::

        from flask import Flask
        from myapplication.views.admin import admin
        from myapplication.views.frontend import frontend

        app = Flask(__name__)
        app.register_module(admin, url_prefix='/admin')
        app.register_module(frontend)

    And here an example view module (`myapplication/views/admin.py`)::

        from flask import Module

        admin = Module(__name__)

        @admin.route('/')
        def index():
            pass

        @admin.route('/login')
        def login():
            pass

    For a gentle introduction into modules, checkout the
    :ref:`working-with-modules` section.

    .. versionadded:: 0.5
       The `static_path` parameter was added and it's now possible for
       modules to refer to their own templates and static files.  See
       :ref:`modules-and-resources` for more information.

    .. versionadded:: 0.6
       The `subdomain` parameter was added.

    :param import_name: the name of the Python package or module
                        implementing this :class:`Module`.
    :param name: the internal short name for the module.  Unless specified
                 the rightmost part of the import name
    :param url_prefix: an optional string that is used to prefix all the
                       URL rules of this module.  This can also be specified
                       when registering the module with the application.
    :param subdomain: used to set the subdomain setting for URL rules that
                      do not have a subdomain setting set.
    :param static_path: can be used to specify a different path for the
                        static files on the web.  Defaults to ``/static``.
                        This does not affect the folder the files are served
                        *from*.
    """

    def __init__(self, import_name, name=None, url_prefix=None,
                 static_path=None, subdomain=None):
        if name is None:
            assert '.' in import_name, 'name required if package name ' \
                'does not point to a submodule'
            name = import_name.rsplit('.', 1)[1]
        _PackageBoundObject.__init__(self, import_name)
        self.name = name
        self.url_prefix = url_prefix
        self.subdomain = subdomain
        self._register_events = [_register_module(self, static_path)]

    def route(self, rule, **options):
        """Like :meth:`Flask.route` but for a module.  The endpoint for the
        :func:`url_for` function is prefixed with the name of the module.
        """
        def decorator(f):
            self.add_url_rule(rule, f.__name__, f, **options)
            return f
        return decorator

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Like :meth:`Flask.add_url_rule` but for a module.  The endpoint for
        the :func:`url_for` function is prefixed with the name of the module.

        .. versionchanged:: 0.6
           The `endpoint` argument is now optional and will default to the
           function name to consistent with the function of the same name
           on the application object.
        """
        def register_rule(state):
            the_rule = rule
            if state.url_prefix:
                the_rule = state.url_prefix + rule
            options.setdefault('subdomain', state.subdomain)
            the_endpoint = endpoint
            if the_endpoint is None:
                the_endpoint = _endpoint_from_view_func(view_func)
            state.app.add_url_rule(the_rule, '%s.%s' % (self.name,
                                                        the_endpoint),
                                   view_func, **options)
        self._record(register_rule)

    def before_request(self, f):
        """Like :meth:`Flask.before_request` but for a module.  This function
        is only executed before each request that is handled by a function of
        that module.
        """
        self._record(lambda s: s.app.before_request_funcs
            .setdefault(self.name, []).append(f))
        return f

    def before_app_request(self, f):
        """Like :meth:`Flask.before_request`.  Such a function is executed
        before each request, even if outside of a module.
        """
        self._record(lambda s: s.app.before_request_funcs
            .setdefault(None, []).append(f))
        return f

    def after_request(self, f):
        """Like :meth:`Flask.after_request` but for a module.  This function
        is only executed after each request that is handled by a function of
        that module.
        """
        self._record(lambda s: s.app.after_request_funcs
            .setdefault(self.name, []).append(f))
        return f

    def after_app_request(self, f):
        """Like :meth:`Flask.after_request` but for a module.  Such a function
        is executed after each request, even if outside of the module.
        """
        self._record(lambda s: s.app.after_request_funcs
            .setdefault(None, []).append(f))
        return f

    def context_processor(self, f):
        """Like :meth:`Flask.context_processor` but for a module.  This
        function is only executed for requests handled by a module.
        """
        self._record(lambda s: s.app.template_context_processors
            .setdefault(self.name, []).append(f))
        return f

    def app_context_processor(self, f):
        """Like :meth:`Flask.context_processor` but for a module.  Such a
        function is executed each request, even if outside of the module.
        """
        self._record(lambda s: s.app.template_context_processors
            .setdefault(None, []).append(f))
        return f

    def app_errorhandler(self, code):
        """Like :meth:`Flask.errorhandler` but for a module.  This
        handler is used for all requests, even if outside of the module.

        .. versionadded:: 0.4
        """
        def decorator(f):
            self._record(lambda s: s.app.errorhandler(code)(f))
            return f
        return decorator

    def _record(self, func):
        self._register_events.append(func)
