# -*- coding: utf-8 -*-
"""
    tipfy.config
    ~~~~~~~~~~~~

    Configuration object.

    :copyright: 2011 by tipfy.org.
    :license: BSD, see LICENSE.txt for more details.
"""
from werkzeug import import_string

__all__ = [
    'DEFAULT_VALUE', 'REQUIRED_VALUE',
]

#: Value used for missing default values.
DEFAULT_VALUE = object()
#: Value used for required values.
REQUIRED_VALUE = object()


class Config(dict):
    """A simple configuration dictionary keyed by module name. This is a
    dictionary of dictionaries. It requires all values to be dictionaries
    and applies updates and default values to the inner dictionaries instead
    of the first level one.

    The configuration object is available as a ``config`` attribute of
    :class:`tipfy.app.App`. If is instantiated and populated when the app
    is built::

        config = {}

        config['my.module'] = {
            'foo': 'bar',
        }

        app = App(rules=[Rule('/', name='home', handler=MyHandler)],
            config=config)

    Then to read configuration values, use :meth:`RequestHandler.get_config`::

        class MyHandler(RequestHandler):
            def get(self):
                foo = self.get_config('my.module', 'foo')

                # ...
    """
    #: Loaded module configurations.
    loaded = None

    def __init__(self, values=None, defaults=None):
        """Initializes the configuration object.

        :param values:
            A dictionary of configuration dictionaries for modules.
        :param defaults:
            A dictionary of configuration dictionaries for initial default
            values. These modules are marked as loaded.
        """
        self.loaded = []
        if values is not None:
            assert isinstance(values, dict)
            for module, config in values.iteritems():
                self.update(module, config)

        if defaults is not None:
            assert isinstance(defaults, dict)
            for module, config in defaults.iteritems():
                self.setdefault(module, config)
                self.loaded.append(module)

    def __getitem__(self, module):
        """Returns the configuration for a module. If it is not already
        set, loads a ``default_config`` variable from the given module and
        updates the configuration with those default values

        Every module that allows some kind of configuration sets a
        ``default_config`` global variable that is loaded by this function,
        cached and used in case the requested configuration was not defined
        by the user.

        :param module:
            The module name.
        :returns:
            A configuration value.
        """
        if module not in self.loaded:
            # Load default configuration and update config.
            values = import_string(module + '.default_config', silent=True)
            if values:
                self.setdefault(module, values)

            self.loaded.append(module)

        try:
            return dict.__getitem__(self, module)
        except KeyError:
            raise KeyError('Module %r is not configured.' % module)

    def __setitem__(self, module, values):
        """Sets a configuration for a module, requiring it to be a dictionary.

        :param module:
            A module name for the configuration, e.g.: `tipfy.i18n`.
        :param values:
            A dictionary of configurations for the module.
        """
        assert isinstance(values, dict), 'Module configuration must be a dict.'
        dict.__setitem__(self, module, SubConfig(module, values))

    def get(self, module, default=DEFAULT_VALUE):
        """Returns a configuration for a module. If default is not provided,
        returns an empty dict if the module is not configured.

        :param module: