"""EZSpace provides the namespace object class for the EZData class."""
#  AGPL-3.0 license
#  Copyright (c) 2024 Asger Jon Vistisen
from __future__ import annotations

from worktoy.base import FastSpace
from worktoy.ezdata import IllegalInitException, IllegalMethodException
from worktoy.ezdata import NoDefaultError, AmbiguousDefaultError
from worktoy.ezdata import DefaultTypeMismatchError
from worktoy.meta import CallMeMaybe
from worktoy.parse import maybe
from worktoy.desc import AttriBox

try:
  from typing import Callable
except ImportError:
  Callable = object

try:
  from typing import TypeAlias
except ImportError:
  TypeAlias = object

try:
  Boxes: TypeAlias = list[tuple[str, AttriBox]]
except ImportError:
  Boxes = object
except TypeError:
  Boxes = object


class EZSpace(FastSpace):
  """Expands the FastSpace class with auto generated '__init__' method.
  Additionally, custom methods are disallowed. Classes that require
  methods should instead use FastObject. """

  __allow_init__ = False  # Allow the '__init__' method to be set.

  def _enableInit(self) -> None:
    """Enables the init method"""
    self.__allow_init__ = True

  def _disableInit(self, ) -> None:
    """Disables the init method"""
    self.__allow_init__ = False

  def _getAllowInit(self, ) -> bool:
    """Getter-function for allow init flag"""
    return True if self.__allow_init__ else False

  def __setitem__(self, key: str, value: object, **kwargs) -> None:
    """This method sets the key, value pair in the namespace."""
    if key == '__trust_me_bro__':
      if value:
        return self._enableInit()
      return self._disableInit()
    if kwargs.get('__trust_me_bro__', False):
      return FastSpace.__setitem__(self, key, value)
    if key == '__init__':
      if self._getAllowInit():
        self._disableInit()
        return FastSpace.__setitem__(self, key, value)
      clsName = self.getClassName()
      raise IllegalInitException(clsName, value)
    if self.isSpecialKey(key):
      return dict.__setitem__(self, key, value)
    if isinstance(value, AttriBox):
      return FastSpace.__setitem__(self, key, value)
    if callable(value):
      clsName = self.getClassName()
      raise IllegalMethodException(clsName, key, value)
    return FastSpace.__setitem__(self, key, value)

  def _unpackBoxes(self) -> tuple[list[str], list[object]]:
    """Unpacks the instances of AttriBox into keys and default values."""
    keys, defVals = [], []
    boxes = self._getAllBoxes()
    n = len(boxes)
    defVal = None
    clsName = self.getClassName()
    for (key, box) in boxes:
      keys.append(key)
      posArgs = AttriBox.getArgs(box, None, _root=True)
      if not posArgs:
        raise NoDefaultError(clsName, key, box)
      if len(posArgs) > 1:
        raise AmbiguousDefaultError(clsName, key, box)
      defVal = posArgs[0]
      fieldClass = AttriBox.getFieldClass(box)
      if not isinstance(defVal, fieldClass):
        raise DefaultTypeMismatchError(clsName, key, box, defVal)
      defVals.append(defVal)
    return keys, defVals

  def _getBoxKeys(self) -> list[str]:
    """Getter-function for the keys to the AttriBox instances."""
    return self._unpackBoxes()[0]

  def _getDefaultValues(self) -> list[object]:
    """Getter-function for the default values of the AttriBox instances."""
    return self._unpackBoxes()[1]

  def ezInitFactory(self, ) -> CallMeMaybe:
    """This factory creates a fast, but inflexible '__init__' method."""
    keys, defVals = self._unpackBoxes()
    n = len(keys)

    def __init__(instance, *args, ) -> None:
      pArgs = [*args, *[None, ] * (max([0, n - len(args)]))]
      for (k, arg, v) in zip(keys, pArgs, defVals):
        setattr(instance, k, maybe(arg, v))

    return __init__

  def ezStrFactory(self, ) -> CallMeMaybe:
    """Factory method for the '__str__' method."""
    keys, defVals = self._unpackBoxes()

    def __str__(instance, ) -> str:
      """String representation"""
      clsName = self.getClassName()
      valStr = [str(getattr(instance, key)) for key in keys]
      return '%s(%s)' % (clsName, ', '.join(valStr))

    return __str__

  def ezEqFactory(self) -> CallMeMaybe:
    """Factory method for the '__eq__' method"""
    keys, defVals = self._unpackBoxes()

    def __eq__(instance, other: object) -> bool:
      for key in keys:
        if getattr(instance, key) - getattr(other, key):
          return False
      else:
        return True

    return __eq__
