# -*- coding: utf-8 -*-
"""
    weppy.app
    ---------

    Provides the central application object.

    :copyright: (c) 2014-2016 by Giovanni Barillari
    :license: BSD, see LICENSE for more details.
"""

import sys
import os
import click
from yaml import load as ymlload
from ._compat import basestring
from ._internal import get_root_path, create_missing_app_folders
from .datastructures import sdict, ConfigData
from .expose import Expose, url
from .extensions import Extension, TemplateExtension
from .globals import current
from .templating.core import Templater
from .utils import dict_to_sdict, cachedprop, read_file
from .wsgi import error_handler


class App(object):
    debug = None
    test_client_class = None

    def __init__(self, import_name, root_path=None,
                 template_folder='templates', config_folder='config'):
        self.import_name = import_name
        #: Set paths for the application
        if root_path is None:
            root_path = get_root_path(self.import_name)
        self.root_path = root_path
        self.static_path = os.path.join(self.root_path, "static")
        self.template_path = os.path.join(self.root_path, template_folder)
        self.config_path = os.path.join(self.root_path, config_folder)
        #: The click command line context for this application.
        self.cli = click.Group(self)
        #: Init the configuration
        self.config = ConfigData()
        self.config.hostname_default = None
        self.config.static_version = None
        self.config.static_version_urls = None
        self.config.url_default_namespace = None
        self.config.templates_auto_reload = False
        #: Trying to create needed folders
        create_missing_app_folders(self)
        #: init expose module
        Expose.application = self
        self.error_handlers = {}
        self.template_default_extension = '.html'
        #: init logger
        self._logger = None
        self.logger_name = self.import_name
        #: set request.now reference
        self.now_reference = "utc"
        #: init languages
        self.languages = []
        self.language_default = None
        self.language_force_on_url = False
        self.language_write = False
        #: init extensions
        self.ext = sdict()
        self._extensions_env = sdict()
        self.template_extensions = []
        self.template_preloaders = {}
        self.template_lexers = {}
        self.templater = Templater(self)

    @cachedprop
    def name(self):
        """The name of the application. This is usually the import name
        with the difference that it's guessed from the run file if the
        import name is main.
        """
        if self.import_name == '__main__':
            fn = getattr(sys.modules['__main__'], '__file__', None)
            if fn is None:
                rv = '__main__'
            else:
                rv = os.path.splitext(os.path.basename(fn))[0]
        else:
            rv = self.import_name
        return rv

    @property
    def route(self):
        return Expose

    @property
    def common_handlers(self):
        return self.route.common_handlers

    @common_handlers.setter
    def common_handlers(self, handlers):
        self.route.common_handlers = handlers

    @property
    def common_helpers(self):
        return self.route.common_helpers

    @common_helpers.setter
    def common_helpers(self, helpers):
        self.route.common_helpers = helpers

    @property
    def routing_processors(self):
        return self.route.processors

    def on_error(self, code):
        def decorator(f):
            self.error_handlers[code] = f
            return f
        return decorator

    @property
    def command(self):
        return self.cli.command

    @property
    def log(self):
        if self._logger and self._logger.name == self.logger_name:
            return self._logger
        from .logger import _logger_lock, create_logger
        with _logger_lock:
            if self._logger and self._logger.name == self.logger_name:
                return self._logger
            self._logger = rv = create_logger(self)
            return rv

    def render_template(self, filename):
        ctx = {'current': current, 'url': url}
        return self.templater.render(self.template_path, filename, ctx)

    def config_from_yaml(self, filename, namespace=None):
        #: import configuration from yaml files
        rc = read_file(os.path.join(self.config_path, filename))
        rc = ymlload(rc)
        c = self.config if namespace is None else self.config[namespace]
        for key, val in rc.items():
            c[key] = dict_to_sdict(val)

    #: Creates the extensions' environments and configs
    def __init_extension(self, ext):
        if ext.namespace is None:
            ext.namespace = ext.__name__
        if self._extensions_env[ext.namespace] is None:
            self._extensions_env[ext.namespace] = sdict()
        return self._extensions_env[ext.namespace], self.config[ext.namespace]

    #: Add an extension to application
    def use_extension(self, ext):
        if not issubclass(ext, Extension):
            raise RuntimeError('%s is an invalid weppy extension' %
                               ext.__name__)
        ext_env, ext_config = self.__init_extension(ext)
        self.ext[ext.__name__] = ext(self, ext_env, ext_config)
        self.ext[ext.__name__].on_load()

    #: Add a template extension to application
    def add_template_extension(self, ext):
        if not issubclass(ext, TemplateExtension):
            raise RuntimeError('%s is an invalid weppy template extension' %
                               ext.__name__)
        ext_env, ext_config = self.__init_extension(ext)
        self.template_extensions.append(ext(ext_env, ext_config))
        fext = self.template_extensions[-1].file_extension
        if fext is not None and isinstance(fext, basestring):
            if fext not in self.template_preloaders.keys():
                self.template_preloaders[fext] = []
            self.template_preloaders[fext].append(self.template_extensions[-1])
        lexers = self.template_extensions[-1].lexers
        for name, lexer in lexers.items():
            self.template_lexers[name] = lexer(self.template_extensions[-1])

    def make_shell_context(self, context={}):
        """Returns the shell context for an interactive shell for this
        application.  This runs all the registered shell context
        processors.
        """
        context['app'] = self
        return context

    def _run(self, host, port):
        from .libs.rocket import Rocket
        r = Rocket((host, port), 'wsgi', {'wsgi_app': self})
        r.start()

    def run(self, host=None, port=None, reloader=True, debug=True):
        if host is None:
            host = "127.0.0.1"
        if port is None:
            port = 8000
        self.debug = debug
        if os.environ.get('WEPPY_RUN_MAIN') != 'true':
            quit_msg = "(press CTRL+C to quit)"
            self.log.info("> weppy application %s running on http://%s:%i %s" %
                          (self.import_name, host, port, quit_msg))
        if reloader:
            from ._reloader import run_with_reloader
            run_with_reloader(self, host, port)
        else:
            self._run(host, port)

    def test_client(self, use_cookies=True, **kwargs):
        tclass = self.test_client_class
        if tclass is None:
            from .testing import WeppyTestClient
            tclass = WeppyTestClient
        return tclass(self, use_cookies=use_cookies, **kwargs)

    def wsgi_handler(self, environ, start_request):
        return error_handler(self, environ, start_request)

    def __call__(self, environ, start_request):
        return self.wsgi_handler(environ, start_request)


class AppModule(object):
    def __init__(self, app, name, import_name, template_folder=None,
                 template_path=None, url_prefix=None, hostname=None,
                 root_path=None):
        self.app = app
        self.name = name
        self.import_name = import_name
        if root_path is None:
            root_path = get_root_path(self.import_name)
        self.root_path = root_path
        #: template_folder is referred to application template_path
        self.template_folder = template_folder
        #: template_path is referred to module root_directory
        if template_path and not template_path.startswith("/"):
            template_path = self.root_path + template_path
        self.template_path = template_path
        # how to route static?
        # and.. do we want this?? I think not..
        #if static_folder:
        #    self.static_folder = self.root_path+"/"+static_folder
        #if static_prefix:
        #    self.static_folder = self.app.static_folder+"/"+static_prefix
        self.url_prefix = url_prefix
        self.hostname = hostname
        self.common_handlers = []
        self.common_helpers = []

    def route(self, path=None, name=None, template=None, **kwargs):
        if name is not None and "." in name:
            raise RuntimeError(
                "App modules' route names should not contains dots"
            )
        name = self.name + "." + (name or "")
        handlers = kwargs.get('handlers', [])
        helpers = kwargs.get('helpers', [])
        if self.common_handlers:
            handlers = self.common_handlers + handlers
        kwargs['handlers'] = handlers
        if self.common_helpers:
            helpers = self.common_helpers + helpers
        kwargs['helpers'] = helpers
        return self.app.route(
            path=path, name=name, template=template, prefix=self.url_prefix,
            template_folder=self.template_folder,
            template_path=self.template_path, hostname=self.hostname, **kwargs)
