"""
Copyright (c) 2016-2020 Keith Sterling http://www.keithsterling.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from programy.utils.logging.ylogger import YLogger

from programy.utils.classes.loader import ClassLoader
from programy.dynamic.sets.numeric import IsNumeric
from programy.dynamic.maps.plural import PluralMap
from programy.dynamic.maps.singular import SingularMap
from programy.dynamic.maps.predecessor import PredecessorMap
from programy.dynamic.maps.successor import SuccessorMap


class DynamicsCollection:

    def __init__(self):
        self._dynamic_sets = {}
        self._dynamic_maps = {}
        self._dynamic_vars = {}

    def load_from_configuration(self, dynamics_configuration):

        if dynamics_configuration is not None:

            for set_name in dynamics_configuration.dynamic_sets:
                self.add_dynamic_set(set_name, dynamics_configuration.dynamic_sets[set_name], dynamics_configuration)

            for map_name in dynamics_configuration.dynamic_maps:
                self.add_dynamic_map(map_name, dynamics_configuration.dynamic_maps[map_name], dynamics_configuration)

            for var_name in dynamics_configuration.dynamic_vars:
                self.add_dynamic_var(var_name, dynamics_configuration.dynamic_vars[var_name], dynamics_configuration)

            self.load_default_dynamics(dynamics_configuration)

    def load_default_dynamics(self, dynamics_configuration):
        self.load_default_dynamic_sets(dynamics_configuration)
        self.load_default_dynamic_maps(dynamics_configuration)
        self.load_default_dynamic_vars(dynamics_configuration)

    def load_default_dynamic_sets(self, dynamics_configuration):
        if IsNumeric.NAME not in self._dynamic_sets:
            YLogger.warning(self, "Dynamic set %s not defined, adding default implementation", IsNumeric.NAME)
            self._dynamic_sets[IsNumeric.NAME] = IsNumeric(dynamics_configuration)

    def load_default_dynamic_maps(self, dynamics_configuration):
        if PluralMap.NAME not in self._dynamic_maps:
            YLogger.warning(self, "Dynamic set %s not defined, adding default implementation", PluralMap.NAME)
            self._dynamic_maps[PluralMap.NAME] = PluralMap(dynamics_configuration)

        if SingularMap.NAME not in self._dynamic_maps:
            YLogger.warning(self, "Dynamic set %s not defined, adding default implementation", SingularMap.NAME)
            self._dynamic_maps[SingularMap.NAME] = SingularMap(dynamics_configuration)

        if SuccessorMap.NAME not in self._dynamic_maps:
            YLogger.warning(self, "Dynamic set %s not defined, adding default implementation", SuccessorMap.NAME)
            self._dynamic_maps[SuccessorMap.NAME] = SuccessorMap(dynamics_configuration)

        if PredecessorMap.NAME not in self._dynamic_maps:
            YLogger.warning(self, "Dynamic set %s not defined, adding default implementation", PredecessorMap.NAME)
            self._dynamic_maps[PredecessorMap.NAME] = PredecessorMap(dynamics_configuration)

    def load_default_dynamic_vars(self, dynamics_configuration):
        del dynamics_configuration
        return

    ###################################################################################################
    # Dynamic Sets

    @property
    def dynamic_sets(self):
        return self._dynamic_sets

    def add_dynamic_set(self, name, classname, config_file):
        self._dynamic_sets[name.upper()] = (ClassLoader.instantiate_class(classname))(config_file)

    def is_dynamic_set(self, name):
        return bool(name.upper() in self._dynamic_sets)

    def dynamic_set(self, client_context, name, value, additional=None):
        name = name.upper()
        if name in self._dynamic_sets:
            dynamic_set = self._dynamic_sets[name]
            return dynamic_set.is_member(client_context, value, additional)
        return None

    ###################################################################################################
    # Dynamic Maps

    @property
    def dynamic_maps(self):
        return self._dynamic_maps

    def add_dynamic_map(self, name, classname, config_file):
        self._dynamic_maps[name.upper()] = (ClassLoader.instantiate_class(classname))(config_file)

    def is_dynamic_map(self, name):
        return bool(name.upper() in self._dynamic_maps)

    def dynamic_map(self, client_context, name, value):
        name = name.upper()
        if name in self._dynamic_maps:
            dynamic_map = self._dynamic_maps[name]
            return dynamic_map.map_value(client_context, value)
        return None

    ###################################################################################################
    # Dynamic Vars

    @property
    def dynamic_vars(self):
        return self._dynamic_vars

    def add_dynamic_var(self, name, classname, config_file):
        self._dynamic_vars[name.upper()] = (ClassLoader.instantiate_class(classname))(config_file)

    def is_dynamic_var(self, name):
        return bool(name.upper() in self._dynamic_vars)

    def dynamic_var(self, client_context, name, value=None):
        name = name.upper()
        if name in self._dynamic_vars:
            dynamic_var = self._dynamic_vars[name]
            return dynamic_var.get_value(client_context, value)

        return None

    def _set_value(self, client_context, name, value):
        name = name.upper()
        if name in self._dynamic_vars:
            dynamic_var = self._dynamic_vars[name]
            dynamic_var.set_value(client_context, value)
            return True

        return False

    def set_dynamic_var(self, client_context, name, value):
        try:
            return self._set_value(client_context, name, value)

        except Exception as e:
            YLogger.exception(self, "Unable to set value for dynamic var %s", e, name)

        return False
