import pathlib
import json
import json.decoder

from .exception import config_exception
from .app_resources import app_resources

class config:
    def __init__(self, defaults: dict) -> None:
        self.__defaults = defaults.copy()
        self.__config = defaults.copy()
    
    def _get(self, name: str) -> str | int | float | dict | list:
        if not name in self.__config:
            raise TypeError("Can not found " + name + " in config.")

        return self.__config[name]

    def __get_default(self, name: str) -> str | int | float | dict | list:
        return self.__defaults[name]

    @property
    def defaults(self) -> dict:
        return self.__defaults.copy()

    def exists(self, name: str) -> bool:
        return name in self.__defaults

    def load(self, config: dict) -> object:
        for key in config:
            if not self.exists(key):
                content = "When parsing config file found key " + key + " "
                content = content + "which is not recognized. Correct it."

                raise config_exception(content)

            if type(config[key]) is not type(self.__get_default(key)):
                required_type = type(self.__get_default).__name__
                detected_type = type(config[key]).__name__

                content = "When processing \"" + key + "\" key in config "
                content = content + "detected types mismatch. It would be an "
                content = content + required_type + ", but it is an " 
                content = content + detected_type + "."

                raise config_exception(content)

            self.__config[key] = config[key]

        return self

    def __str__(self) -> str:
        dump = "Config dump: \n"
        
        for count in self.defaults.keys():
            key = "Key \"" + count + "\": "
            key = key + "\"" + self._get(count) + "\", "
            key = key + "default = \"" + self.__get_default(count) + "\""
            dump = dump + key + "\n"

        return dump

class config_generator:
    def __init__(self, target: type) -> None:
        if target is config:
            raise TypeError("Config is skeleton class for configs.")

        self.__config = target().defaults
    
    def modify(self, key: str, value: str | int | float) -> object:
        if not key in self.__config:
            raise config_exception("Key \"" + key + "\" not exists.")

        self.__config[key] = value 

        return self

    def save(self, where: pathlib.Path) -> None:
        json_config = json.dumps(self.__config)

        if where.exists():
            content = "Can not create config file \"" + str(where) + "\" "
            content = content + "because it already exists in filesystem."

            raise config_exception(content)

        with where.open("w") as handler:
            handler.write(json_config)

class config_loader:
    def __init__(self, target: type) -> None:
        if target is config:
            raise TypeError("Config is skeleton class for configs.")

        self.__type = target
        self.__target = None

    @property
    def result(self) -> config:
        if self.__target is None:
            raise RuntimeError("Config is not loaded yet.")

        return self.__target

    @property
    def resources(self) -> app_resources:
        return app_resources(self.result)

    def load(self, where: pathlib.Path) -> object:
        if not where.is_file():
            content = "Can not found required config file \""
            content = content + str(where.absolute()) + "\"."

            raise config_exception(content)
        
        preparing = self.__type()

        with where.open() as config:
            try:
                config_object = json.loads(config.read())
                preparing.load(config_object)
                self.__target = preparing

            except json.decoder.JSONDecodeError as code_error:
                content = "Can not load config file, syntax error: "
                content = content + str(code_error) + "."

                raise config_error(content)

        return self
