# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)

import time
from abc import ABC, abstractmethod
from queue import Queue

from pydantic import BaseModel, Field
from typing import Optional

class InvocationQueueItem(BaseModel):
    graph_execution_state_id: str = Field(description="The ID of the graph execution state")
    invocation_id: str = Field(description="The ID of the node being invoked")
    invoke_all: bool = Field(default=False)
    timestamp: float = Field(default_factory=time.time)


class InvocationQueueABC(ABC):
    """Abstract base class for all invocation queues"""

    @abstractmethod
    def get(self) -> InvocationQueueItem:
        pass

    @abstractmethod
    def put(self, item: Optional[InvocationQueueItem]) -> None:
        pass

    @abstractmethod
    def cancel(self, graph_execution_state_id: str) -> None:
        pass

    @abstractmethod
    def is_canceled(self, graph_execution_state_id: str) -> bool:
        pass


class MemoryInvocationQueue(InvocationQueueABC):
    __queue: Queue
    __cancellations: dict[str, float]

    def __init__(self):
        self.__queue = Queue()
        self.__cancellations = dict()

    def get(self) -> InvocationQueueItem:
        item = self.__queue.get()

        while isinstance(item, InvocationQueueItem) \
            and item.graph_execution_state_id in self.__cancellations \
            and self.__cancellations[item.graph_execution_state_id] > item.timestamp:
            item = self.__queue.get()

        # Clear old items
        for graph_execution_state_id in list(self.__cancellations.keys()):
            if self.__cancellations[graph_execution_state_id] < item.timestamp:
                del self.__cancellations[graph_execution_state_id]

        return item

    def put(self, item: Optional[InvocationQueueItem]) -> None:
        self.__queue.put(item)

    def cancel(self, graph_execution_state_id: str) -> None:
        if graph_execution_state_id not in self.__cancellations:
            self.__cancellations[graph_execution_state_id] = time.time()

    def is_canceled(self, graph_execution_state_id: str) -> bool:
        return graph_execution_state_id in self.__cancellations
