"""
This module contains configuration objects for execution graphs. Look at the Setting class.
"""

from abc import abstractmethod
from typing import Any, Generic, TypeVar
from .colors import Color
from Viper.meta.decorators import hybridmethod, staticproperty

__all__ = ["Setting", "ColorSetting", "SizeSetting", "SwitchSetting", "WeightSetting", "CompilationParameters", "ajust_for_background_color"]





T = TypeVar("T")

class Setting(Generic[T]):

    """
    Just a class to hold settings for BAGUETTE. Cannot be used as such, you need to specialize it similarly to generic class of the type of object the setting will hold:

    >>> s = Setting(Color.white)
    ValueError: Cannot instanciate unspecialized Setting class
    >>> ColorSetting = Setting[Color]
    >>> s = ColorSetting(Color.white)
    >>> class A:
    ...     x = ColorSetting(Color.red)
    ... 
    >>> a = A()
    >>> a.x
    Color.red
    >>> a.x = 1
    TypeError: Expected Color, got 'int'
    >>> a.x = Color.yellow
    >>> a.x
    Color.yellow
    >>> A.x         # Note that settings are actually affected at a class level.
    Color.yellow
    >>> del a.x
    >>> a.x
    Color.red
    
    The advantage of Settings over simple properties is that references are held at the class level to all the settings declared in all classes namespaces:

    >>> Setting.list()
    {(<class '__main__.A'>, 'x'): <baguette.bakery.source.config.Setting[Color] object at 0x0000016345399590>}
    >>> IntSetting = Setting[int]
    >>> IntSetting.list()
    {}
    >>> ColorSetting.list()
    {(<class '__main__.A'>, 'x'): <baguette.bakery.source.config.Setting[Color] object at 0x0000016345399590>}
    >>> a.x = Color.yellow
    >>> Setting.list()[A, "x"].value = Color.black
    >>> a.x
    Color.black
    """

    __slots__ = {
        "__default" : "The default value of this Setting",
        "__current" : "The current value set for this Setting",
    }

    __per_class : dict[tuple[type, str], "Setting"] = {}

    @classmethod
    def __class_getitem__(cls, key : type) -> type["Setting"]:
        if not isinstance(key, type):
            raise TypeError(f"Expected type, got '{type(key).__name__}'")
        from Viper.meta.decorators import staticproperty
        return type(cls)(f"Setting[{key.__name__}]", (Setting, ), {"cls" : staticproperty(lambda : key)})

    def __new__(cls, default : T):
        if not issubclass(cls, Setting):
            raise TypeError("Cannot use Setting.__new__ on something that is not a subclass of Setting")
        res : Setting = super().__new__(cls)
        try:
            res.cls
        except NotImplementedError:
            raise ValueError("Cannot instanciate unspecialized Setting class") from None
        return res

    def __init__(self, default : T) -> None:
        self.__default = default
        self.__current = default
        if not isinstance(default, self.cls):
            raise TypeError(f"Expected {self.cls.__name__}, got '{type(default).__name__}'")

    @staticproperty
    @abstractmethod
    def cls() -> type[T]:
        """
        The type of value expected for this Setting.
        """
        raise NotImplementedError

    @property
    def value(self) -> T:
        """
        The value that this Setting currently holds.
        """
        return self.__current
    
    @value.setter
    def value(self, value : T):
        if not isinstance(value, self.cls):
            raise TypeError(f"Expected {self.cls.__name__}, got '{type(value).__name__}'")
        self.__current = value
    
    @value.deleter
    def value(self):
        self.__current = self.__default

    @classmethod
    def list(cls, namespace : type | None = None) -> dict[tuple[type, str], "Setting[T]"]:
        """
        Lists all the available settings in a dict object.
        Keys are tuples in the form (<class>, <name>) for each Vertex or Edge subclass with the name of the attribute which holds the Setting.
        Values are the Setting objects themselves.
        A filter class can be given to filter only the settings present in this class and its subclasses.
        """
        if namespace is not None and not isinstance(namespace, type):
            raise TypeError(f"Expected type or None, got '{type(namespace).__name__}")
        if namespace:
            if cls is Setting:
                return {(c, n) : s for (c, n), s in Setting.__per_class.items() if issubclass(c, namespace)}
            else:
                return {(c, n) : s for (c, n), s in Setting.__per_class.items() if s.cls is cls.cls and issubclass(c, namespace)}
        else:
            if cls is Setting:
                return {(c, n) : s for (c, n), s in Setting.__per_class.items()}
            else:
                return {(c, n) : s for (c, n), s in Setting.__per_class.items() if s.cls is cls.cls}
    
    def specialize_subclass(self, cls : type):
        """
        Copies the setting to a subclass, allowing the subclass to have a different value for this setting.
        Returns the newly created setting.
        """
        if not isinstance(cls, type):
            raise TypeError(f"Expected type, got '{type(cls).__name__}'")
        target_cls = None
        name = None
        for (c, n), s in Setting.__per_class.items():
            if s is self:
                target_cls = c
                name = n
                break
        if target_cls is None or name is None:
            raise ValueError("Cannot specialize anonymous setting (This setting is not in the code of any known class)")
        if not issubclass(cls, target_cls):
            raise ValueError("Expected subclass of the class this setting is registered in.")
        cp = type(self)(self.__default)
        setattr(cls, name, cp)
        cp.__set_name__(cls, name)
        return cp

    def __set_name__(self, owner : type, name : str):
        from ...logger import logger
        logger.debug(f"New '{self.cls.__name__}' setting registered for class '{owner.__name__}' with name '{name}'.")
        Setting.__per_class[(owner, name)] = self

    def __get__(self, instance : Any, owner : type | None = None) -> T:
        return self.__current
    
    def __set__(self, instance : Any, value : T):
        if not isinstance(value, self.cls):
            raise TypeError(f"Expected {self.cls.__name__}, got '{type(value).__name__}'")
        self.__current = value

    def __delete__(self, instance : Any):
        self.__current = self.__default





ColorSetting = Setting[Color]
SizeSetting = Setting[float]
WeightSetting = Setting[float]
SwitchSetting = Setting[bool]
FlagSetting = Setting[bool]





def ajust_for_background_color(background_color : Color, tolerance : float = 0.15):
    """
    This function will change the values of the Color Settings to ajust those that would not be visible for a background of the given color.
    """
    from ...logger import logger

    for (cls, name), color_setting in ColorSetting.list().items():
        if sum((a - b) ** 2 for a, b in zip(color_setting.value, background_color)) ** 0.5 < tolerance:
            logger.info(f"Changing color setting '{cls.__name__}.{name}'.")
            color_setting.value = color_setting.value.negative_to(background_color)






class __CompilationParameters:

    """
    This class holds some compilations parameters that some modules may want to use.
    """

    SkipLevenshteinForDataNodes = FlagSetting(False)
    SkipLevenshteinForDiffNodes = FlagSetting(False)

CompilationParameters = __CompilationParameters()





del abstractmethod, Any, Generic, TypeVar, Color, hybridmethod, staticproperty        # type: ignore