"""
Implementation of L{TimeDriver} and L{AsyncDriver} in terms of U{Twisted
<https://twisted.org/>}'s APIs, L{IReactorTime} and L{Deferred}.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, Coroutine, Optional

from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IDelayedCall, IReactorTime
from twisted.logger import Logger

from ..boundaries import (
    AsyncDriver,
    PhysicalScheduler,
    PriorityQueue,
    TimeDriver,
)
from ..heap import Heap
from ..scheduler import ConcreteScheduledCall, schedulerFromDriver

log = Logger()


@dataclass
class TwistedTimeDriver:
    """
    Instantiate a L{TwistedTimeDriver} with an L{IReactorTime}; for example::

        from twisted.internet.task import react

        async def main(reactor):
            driver = TwistedTimeDriver(reactor)
            ...

        task.react(main)
    """

    _reactor: IReactorTime
    _call: Optional[IDelayedCall] = None

    def reschedule(self, desiredTime: float, work: Callable[[], None]) -> None:
        """"""

        def _() -> None:
            self._call = None
            work()

        if self._call is not None:
            self._call.cancel()
        self._call = self._reactor.callLater(
            max(0, desiredTime - self.now()), _
        )

    def unschedule(self) -> None:
        if self._call is not None:
            self._call.cancel()
            self._call = None

    def now(self) -> float:
        return self._reactor.seconds()


_TimeDriverCheck: type[TimeDriver[float]] = TwistedTimeDriver


@dataclass
class TwistedAsyncDriver:
    """
    Driver for Deferred-flavored awaitables.
    """

    def newWithCancel(self, cancel: Callable[[], None]) -> Deferred[None]:
        """
        Create a new future-ish object with the given callback to execute when
        cancelled.
        """
        return Deferred(lambda d: cancel())

    def complete(self, asyncObj: Deferred[None]) -> None:
        """
        The asynchronous operation completed successfully.
        """
        asyncObj.callback(None)

    def runAsync(
        self, coroutine: Coroutine[Deferred[None], Any, Any]
    ) -> Deferred[None]:
        return Deferred.fromCoroutine(coroutine)


_AsyncDriverCheck: type[AsyncDriver[Deferred[None]]] = TwistedAsyncDriver


def scheduler(
    reactor: IReactorTime | None = None,
    queue: (
        PriorityQueue[ConcreteScheduledCall[float, Callable[[], None], int]]
        | None
    ) = None,
) -> PhysicalScheduler:
    """
    Create a scheduler that uses Twisted.
    """
    if reactor is None:
        from twisted.internet import reactor  # type:ignore[assignment]

        assert reactor is not None
    return schedulerFromDriver(
        TwistedTimeDriver(reactor),
        queue=queue if queue is not None else Heap(),
    )
