#    Paperwork - Using OCR to grep dead trees the easy way
#    Copyright (C) 2012-2014  Jerome Flesch
#
#    Paperwork is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Paperwork is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Paperwork.  If not, see <http://www.gnu.org/licenses/>.
"""
Paperwork configuration management code
"""

import collections
import logging

from .. import PluginBase


LOGGER = logging.getLogger(__name__)


class Setting(object):
    def __init__(self, core, section, token, default_value_func):
        self.core = core
        self.section = section
        self.token = token
        self.default_value_func = default_value_func

    def get(self):
        value = self.core.call_success(
            "config_backend_get", self.section, self.token, None
        )
        if value is None:
            return self.default_value_func()
        else:
            return value

    def put(self, value):
        self.core.call_all(
            "config_backend_put", self.section, self.token, value
        )


class Plugin(PluginBase):
    """
    Translate values from the configuration into more usable ones.
    Provides default values (except for plugins).
    """

    def __init__(self):
        self.core = None
        self.settings = {}
        self.values = {}
        # applicatiom here is a bit more specific: paperwork-gtk,
        # paperwork-shell, etc.
        # It is used to known which plugin list must be loaded
        self.plugin_list_name = None
        self.observers = collections.defaultdict(set)

    def get_interfaces(self):
        return ['config']

    def get_deps(self):
        return [
            {
                'interface': 'app',
                'defaults': ['openpaperwork_core.app'],
            },
            {
                'interface': 'config_backend',
                'defaults': ['openpaperwork_core.config.backend.configparser'],
            },
        ]

    def init(self, core):
        self.core = core
        self.settings = {}

    def config_load(self):
        application = self.core.call_success("app_get_fs_name")
        LOGGER.info("Loading configuration for %s", application)

        self.values = {}
        self.core.call_all('config_backend_load', application)
        for observers in self.observers.values():
            for observer in observers:
                observer()

    def config_load_plugins(self, plugin_list_name, default_plugins=[]):
        LOGGER.info("Loading plugins for '%s'", plugin_list_name)
        self.plugin_list_name = plugin_list_name
        self.core.call_all(
            'config_backend_load_plugins', plugin_list_name, default_plugins
        )

    def config_get_plugin_list_name(self):
        return self.plugin_list_name

    def config_save(self):
        LOGGER.info("Saving configuration")
        self.core.call_all('config_backend_save')

    def config_build_simple(
                self, section, token, default_value_func
            ):
        """
        Provide a default simple implementation for a new setting that can
        be registered using 'config_register'.

        Arguments:
          - section: Section in which option must be stored (see ConfigParser)
          - token: token name for the option (see ConfigParser)
          - default_value_func: function to call to get the default value if
            none is stored in the file.
        """
        return Setting(self.core, section, token, default_value_func)

    def config_register(self, key: str, setting):
        """
        Add another setting to manage. Make this setting available to other
        components.

        Arguments:
          - key: configuration key
          - setting: Setting must be an object providing a method `get()`
            and a method `put(value)`. See :func:`config_build_simple` to
            get quickly a default implementation.
        """
        LOGGER.debug("Registering configuration: %s", key)
        self.settings[key] = setting

    def config_list_options(self):
        return list(self.settings.keys())

    def config_get_setting(self, key: str):
        return self.settings[key]

    def config_get(self, key: str):
        LOGGER.debug("Config get: %s", key)
        if key not in self.settings:
            return None
        if key not in self.values:
            self.values[key] = self.settings[key].get()
        return self.values[key]

    def config_get_default(self, key: str):
        return self.settings[key].default_value_func()

    def config_put(self, key: str, value):
        """
        Store a setting value.
        Warning: You must call :func:`config_save` so the changes are actually
        saved.

        Arguments:
          - key: configuration key,
          - value: can be of many types (`str`, `int`, etc).
        """
        LOGGER.debug("Config put: %s", key)
        self.settings[key].put(value)
        self.values[key] = value
        if key in self.observers:
            for callback in self.observers[key]:
                callback()

    def config_add_plugin(self, plugin, plugin_list_name=None):
        if plugin_list_name is None:
            plugin_list_name = self.plugin_list_name
        LOGGER.debug("Config add plugin: %s -> %s", plugin, plugin_list_name)
        self.core.call_all(
            'config_backend_add_plugin', plugin_list_name, plugin
        )

    def config_remove_plugin(self, plugin, plugin_list_name=None):
        if plugin_list_name is None:
            plugin_list_name = self.plugin_list_name
        LOGGER.debug(
            "Config remove plugin: %s -> %s",
            plugin, plugin_list_name
        )
        self.core.call_all(
            'config_backend_remove_plugin', plugin_list_name, plugin
        )

    def config_list_plugins(self, plugin_list_name=None):
        if plugin_list_name is None:
            plugin_list_name = self.plugin_list_name
        return self.core.call_success(
            "config_backend_list_active_plugins", plugin_list_name
        )

    def config_reset_plugins(self, plugin_list_name=None):
        if plugin_list_name is None:
            plugin_list_name = self.plugin_list_name
        LOGGER.debug("Config reset plugin: %s", plugin_list_name)
        return self.core.call_success(
            "config_backend_reset_plugins", plugin_list_name
        )

    def config_add_observer(self, key: str, callback):
        self.observers[key].add(callback)

    def config_remove_observer(self, key: str, callback):
        self.observers[key].remove(callback)
