# pylint: disable=bare-except,c-extension-no-member
from __future__ import annotations
from ast import Tuple

import math
from types import SimpleNamespace
from typing import Dict, List
from nmmo.datastore.datastore import Datastore, DatastoreRecord
try:
  import nmmo.lib.cython_helper as chp
  USE_CYTHON = True
except:
  USE_CYTHON = False

"""
This code defines classes for serializing and deserializing data
in a structured way.

The SerializedAttribute class represents a single attribute of a
record and provides methods for updating and querying its value,
as well as enforcing minimum and maximum bounds on the value.

The SerializedState class serves as a base class for creating
serialized representations of specific types of data, using a
list of attribute names to define the structure of the data.
The subclass method is a factory method for creating subclasses
of SerializedState that are tailored to specific types of data.
"""

class SerializedAttribute():
  def __init__(self,
      name: str,
      datastore_record: DatastoreRecord,
      column: int, min_val=-math.inf, max_val=math.inf) -> None:
    self._name = name
    self.datastore_record = datastore_record
    self._column = column
    self._min = min_val
    self._max = max_val
    self._val = 0

  @property
  def val(self):
    return self._val

  def update(self, value):
    if value > self._max:
      value = self._max
    elif value < self._min:
      value = self._min
    self.datastore_record.update(self._column, value)
    self._val = value

  @property
  def min(self):
    return self._min

  @property
  def max(self):
    return self._max

  def increment(self, val=1, max_v=math.inf):
    self.update(min(max_v, self.val + val))
    return self

  def decrement(self, val=1, min_v=-math.inf):
    self.update(max(min_v, self.val - val))
    return self

  @property
  def empty(self):
    return self.val == 0

  def __eq__(self, other):
    return self.val == other

  def __ne__(self, other):
    return self.val != other

  def __lt__(self, other):
    return self.val < other

  def __le__(self, other):
    return self.val <= other

  def __gt__(self, other):
    return self.val > other

  def __ge__(self, other):
    return self.val >= other

class SerializedState():
  @staticmethod
  def subclass(name: str, attributes: List[str]):
    class Subclass(SerializedState):
      _name = name
      State = SimpleNamespace(
        attr_name_to_col = {a: i for i, a in enumerate(attributes)},
        num_attributes = len(attributes),
        table = lambda ds: ds.table(name)
      )

      def __init__(self, datastore: Datastore,
                   limits: Dict[str, Tuple[float, float]] = None):

        limits = limits or {}
        self.datastore_record = datastore.create_record(name)

        for attr, col in self.State.attr_name_to_col.items():
          try:
            setattr(self, attr,
              SerializedAttribute(attr, self.datastore_record, col,
                *limits.get(attr, (-math.inf, math.inf))))
          except Exception as exc:
            raise RuntimeError('Failed to set attribute "' + attr + '"') from exc

      @classmethod
      def parse_array(cls, data) -> SimpleNamespace:
        # Takes in a data array and returns a SimpleNamespace object with
        # attribute names as keys and corresponding values from the input
        # data array.
        assert len(data) == cls.State.num_attributes, \
          f"Expected {cls.State.num_attributes} attributes, got {len(data)}"

        if USE_CYTHON:
          return chp.parse_array(data, cls.State.attr_name_to_col)

        return SimpleNamespace(**{
          attr: data[col] for attr, col in cls.State.attr_name_to_col.items()
        })

    return Subclass
