import type_enforced
from type_enforced.utils import WithSubclasses
from fizgrid.entities import Entity, StaticEntity
from fizgrid.queue import TimeQueue


@type_enforced.Enforcer(enabled=True)
class Grid:
    def __init__(
        self,
        name: str,
        x_size: int,
        y_size: int,
        max_time: int = 1000,
        add_exterior_walls: bool = True,
    ):
        """
        Initializes a grid with the specified parameters.

        Args:

        - name (str): The name of the grid.
        - x_size (int): The width of the grid.
        - y_size (int): The height of the grid.
        - max_time (int): The maximum time for the grid simulation.
        - add_exterior_walls (bool): Whether to add exterior walls to the grid.
            - Default: True
        """
        # Passed Attributes
        self.name: str = name
        """The name of the grid."""
        self.__x_size__ = x_size
        self.__y_size__ = y_size
        self.__max_time__ = max_time

        # Calculated Attributes
        self.__entities__ = {}
        self.__queue__ = TimeQueue()
        self.__cells__ = [[{} for _ in range(x_size)] for _ in range(y_size)]

        if add_exterior_walls:
            self.add_exterior_walls()

    def __repr__(self):
        return f"Grid({self.name} {self.__x_size__}x{self.__y_size__})"

    def add_entity(
        self,
        entity: WithSubclasses(Entity),
        time: int | float | None = None,
    ):
        """
        Adds an entity to the grid.

        Args:

        - entity (Entity): The entity to be added to the grid.
            - Must be an Entity or a subclass of Entity.
        - time (int|float|None): The time at which the entity should be added to the grid.
            - If None, the entity is added immediately.

        Returns:

        - Entity: The added entity.
        """
        entity.__assoc_grid__(self)
        self.__entities__[entity.id] = entity
        if time is None:
            entity.__place_on_grid__()
        else:
            self.add_event(
                time=time,
                object=entity,
                method="__place_on_grid__",
                kwargs={},
            )
        return entity

    def remove_entity(
        self, entity: WithSubclasses(Entity), time: int | float | None = None
    ):
        """
        Removes an entity from the grid.

        Args:

        - entity (Entity): The entity to be removed from the grid.
            - Must be an Entity or a subclass of Entity.
        - time (int|float|None): The time at which the entity should be removed from the grid.
            - If None, the entity is removed immediately.
        """
        if time is not None:
            self.add_event(
                time=time,
                object=self,
                method="remove_entity",
                kwargs={"entity": entity},
            )
            return entity
        if entity.id in self.__entities__:
            entity.__dissoc_grid__()
            self.__entities__.pop(entity.id, None)

    def add_event(
        self, time: int | float, object, method: str, kwargs: dict = dict()
    ) -> int:
        """
        Adds an event to the queue.
        This method schedules an event for a specific object at a specific time.
        Essentially, it allows you to schedule a method call on an object at a specific time.

        Args:

        - time (int|float): The time at which the event should occur.
        - object: The object on which the event will occur.
        - method (str): The name of the method to be called on the object.
        - kwargs (dict): The keyword arguments to be passed to the method.

        Returns:

        - int: The ID of the added event as generated by the queue.
        """
        return self.__queue__.add_event(
            time=time,
            event={"object": object, "method": method, "kwargs": kwargs},
        )

    def get_time(self) -> int | float:
        """
        Returns the current time of the grid.
        This method retrieves the current time from the queue.

        Returns:

        - int|float: The current time of the grid.
        """
        return self.__queue__.__time__

    def resolve_next_state(self) -> list[dict]:
        """
        Resolves the next state of the grid.
        This method processes the next event in the queue and updates the grid accordingly.

        Returns:

        - list[dict]: A list of events that were processed in this step.
            - Each event is a dictionary containing a time, id and event.
            - The event dictionary contains the object, method and kwargs.
                - id (int): The ID of the event as generated by the queue.
                - time (int|float): The time at which the event occurred.
                - event (dict): The event that was processed.
                    - object: The object on which the event occurred.
                    - method (str): The name of the method that was called.
                    - kwargs (dict): The keyword arguments that were passed to the method.
        """
        event_items = self.__queue__.get_next_events()
        for event_item in event_items:
            event = event_item.get("event")
            object = event.get("object")
            method = event.get("method")
            kwargs = event.get("kwargs")
            if object is None:
                continue
            getattr(object, method)(**kwargs)
        return event_items

    def add_exterior_walls(self) -> None:
        """
        Adds exterior walls to the grid.
        This method creates walls around the grid to prevent entities from moving outside the grid.
        """
        self.add_entity(
            StaticEntity(
                name="Left Wall",
                shape=[
                    [0, 0],
                    [0, self.__y_size__],
                    [1, self.__y_size__],
                    [1, 0],
                ],
                x_coord=0,
                y_coord=0,
            )
        )

        self.add_entity(
            StaticEntity(
                name="Right Wall",
                shape=[
                    [0, 0],
                    [0, self.__y_size__],
                    [-1, self.__y_size__],
                    [-1, 0],
                ],
                x_coord=self.__x_size__,
                y_coord=0,
            )
        )

        self.add_entity(
            StaticEntity(
                name="Top Wall",
                shape=[
                    [0, 0],
                    [self.__x_size__ - 2, 0],
                    [self.__x_size__ - 2, -1],
                    [0, -1],
                ],
                x_coord=1,
                y_coord=self.__y_size__,
            )
        )

        self.add_entity(
            StaticEntity(
                name="Bottom Wall",
                shape=[
                    [0, 0],
                    [self.__x_size__ - 2, 0],
                    [self.__x_size__ - 2, 1],
                    [0, 1],
                ],
                x_coord=1,
                y_coord=0,
            )
        )

    def simulate(self) -> None:
        """
        Runs the simulation for the grid.
        This method processes events in the queue until all events are resolved or the maximum time is reached.
        """
        next_state_events = True
        while next_state_events:
            next_state_events = self.resolve_next_state()
