from mesa.time import BaseScheduler
from queue import Empty
from heapq import heappush, heappop
import math
from .utils import logger
from mesa import Agent


INFINITY = float('inf')

class When:
    def __init__(self, time):
        if isinstance(time, When):
            return time
        self._time = time

    def abs(self, time):
        return self._time

NEVER = When(INFINITY)


class Delta(When):
    def __init__(self, delta):
        self._delta = delta

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

    def abs(self, time):
        return time + self._delta


class TimedActivation(BaseScheduler):
    """A scheduler which activates each agent when the agent requests.
    In each activation, each agent will update its 'next_time'.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(self)
        self._queue = []
        self.next_time = 0

    def add(self, agent: Agent):
        if agent.unique_id not in self._agents:
            heappush(self._queue, (self.time, agent.unique_id))
            super().add(agent)

    def step(self) -> None:
        """
        Executes agents in order, one at a time. After each step,
        an agent will signal when it wants to be scheduled next.
        """

        if self.next_time == INFINITY:
            return

        self.time = self.next_time
        when = self.time

        while self._queue and self._queue[0][0] == self.time:
            (when, agent_id) = heappop(self._queue)
            logger.debug(f'Stepping agent {agent_id}')

            returned = self._agents[agent_id].step()
            when = (returned or Delta(1)).abs(self.time)
            if when < self.time:
                raise Exception("Cannot schedule an agent for a time in the past ({} < {})".format(when, self.time))

            heappush(self._queue, (when, agent_id))

        self.steps += 1

        if not self._queue:
            self.time = INFINITY
            self.next_time = INFINITY
            return

        self.next_time = self._queue[0][0]

