# Copyright (C) 2024 Giovanni Fulco
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

from context_key import ContextKey


class Context:

    def __init__(self):
        self._dict: dict[str, any] = dict()

    def __not_null(self, key: ContextKey):
        if not key:
            raise Exception("Provided key is None")

    def __raise_missing_key(self, key_str: str):
        raise Exception(f"Missing key [{key_str}]")

    def __not_in(self, key: ContextKey):
        self.__not_null(key)
        if key in self._dict:
            raise Exception(f"Duplicate key [{key.name}]")

    def contains(self, key: ContextKey) -> bool:
        self.__not_null(key)
        return key.name in self._dict

    def add(self, key: ContextKey, value: any, allow_update: bool = False):
        if allow_update:
            self.__not_null(key)
        else:
            self.__not_in(key)
        self._dict[key.name] = value

    def update(self, key: ContextKey, value: any):
        self.add(key=key, value=value, allow_update=True)

    def increment(self, key: ContextKey):
        v: any = self.get(key=key)
        if not isinstance(v, int):
            raise Exception(f"Item at key [{key.name}] is not a int")
        self.update(key=key, value=int(v) + 1)

    def get(self, key: ContextKey, allow_empty: bool = True):
        self.__not_null(key)
        if not allow_empty and key.name not in self._dict:
            self.__raise_missing_key(key.name)
        return self._dict[key.name] if key.name in self._dict else key.default_value

    def remove(self, key: ContextKey, allow_empty: bool = True):
        if key.name in self.__dict:
            del self._dict[key.name]
        else:
            if not allow_empty:
                raise Exception(f"Missing key [{key.name}]")

    def __get_dict(self, dict_key: ContextKey, allow_empty: bool = True) -> dict[str, any]:
        select_dict: dict[str, any] = self.get(key=dict_key)
        if not select_dict:
            if not allow_empty:
                raise Exception(f"Key [{dict_key.name}] not found")
            select_dict = dict()
            self.add(key=dict_key, value=select_dict)
        else:
            # is it a dictionary?
            if not isinstance(select_dict, dict):
                raise Exception(f"Element by key [{dict_key.name}] is not a dictionary")
        return select_dict

    def dict_add(
            self,
            dict_key: ContextKey,
            entry_key: str,
            entry_value: any,
            allow_empty: bool = True,
            allow_update: bool = False):
        # check if key exists
        select_dict: dict[str, any] = self.__get_dict(dict_key=dict_key, allow_empty=allow_empty)
        if entry_key in select_dict and not allow_update:
            raise Exception(f"Duplicate key [{entry_key}]")
        select_dict[entry_key] = entry_value

    def dict_update(
            self,
            dict_key: ContextKey,
            entry_key: str,
            entry_value: any,
            allow_empty: bool = True):
        self.dict_add(
            dict_key=dict_key,
            entry_key=entry_key,
            entry_value=entry_value,
            allow_empty=allow_empty,
            allow_update=True)

    def dict_remove(
            self,
            dict_key: ContextKey,
            entry_key: str,
            allow_empty: bool = True,
            allow_missing: bool = True):
        select_dict: dict[str, any] = self.__get_dict(dict_key=dict_key, allow_empty=allow_empty)
        if entry_key in select_dict:
            del select_dict[entry_key]
        else:
            if not allow_missing:
                raise Exception(f"Key [{entry_key}] not found")

    def dict_get(
            self,
            dict_key: ContextKey,
            entry_key: str,
            allow_empty: bool = True,
            allow_missing: bool = True,
            default_value: any = None) -> bool:
        select_dict: dict[str, any] = self.__get_dict(dict_key=dict_key, allow_empty=allow_empty)
        if entry_key not in select_dict:
            if not allow_missing:
                raise Exception(f"Key [{entry_key}] not found")
            return default_value
        else:
            return select_dict[entry_key]

    def dict_contains(
            self,
            dict_key: ContextKey,
            entry_key: str,
            allow_empty: bool = True) -> bool:
        select_dict: dict[str, any] = self.__get_dict(dict_key=dict_key, allow_empty=allow_empty)
        return entry_key in select_dict
