import logging

from typing import Optional

from rx.core import typing
from rx.internal.constants import DELTA_ZERO

from .scheduleditem import ScheduledItem
from .scheduler import Scheduler
from .trampoline import Trampoline


log = logging.getLogger('Rx')


class TrampolineScheduler(Scheduler):
    """Represents an object that schedules units of work on the trampoline.
    You should never schedule timeouts using the *TrampolineScheduler*, as
    it will block the thread while waiting.

    Each instance has its own trampoline (and queue), and you can schedule work
    on it from different threads. Beware though, that the first thread to call
    a *schedule* method while the trampoline is idle will then remain occupied
    until the queue is empty.
    """

    def __init__(self) -> None:
        """Creates a scheduler that bounces its work off the trampoline."""

        self._tramp = Trampoline()

    def get_trampoline(self) -> Trampoline:
        return self._tramp

    def schedule(self,
                 action: typing.ScheduledAction,
                 state: Optional[typing.TState] = None
                 ) -> typing.Disposable:
        """Schedules an action to be executed.

        Args:
            action: Action to be executed.
            state: [Optional] state to be given to the action function.

        Returns:
            The disposable object used to cancel the scheduled action
            (best effort).
        """

        return self.schedule_absolute(self.now, action, state=state)

    def schedule_relative(self,
                          duetime: typing.RelativeTime,
                          action: typing.ScheduledAction,
                          state: Optional[typing.TState] = None
                          ) -> typing.Disposable:
        """Schedules an action to be executed after duetime.

        Args:
            duetime: Relative time after which to execute the action.
            action: Action to be executed.
            state: [Optional] state to be given to the action function.

        Returns:
            The disposable object used to cancel the scheduled action
            (best effort).
        """

        duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
        return self.schedule_absolute(self.now + duetime, action, state=state)

    def schedule_absolute(self,
                          duetime: typing.AbsoluteTime,
                          action: typing.ScheduledAction,
                          state: Optional[typing.TState] = None
                          ) -> typing.Disposable:
        """Schedules an action to be executed at duetime.

        Args:
            duetime: Absolute time at which to execute the action.
            action: Action to be executed.
            state: [Optional] state to be given to the action function.

        Returns:
            The disposable object used to cancel the scheduled action
            (best effort).
        """

        dt = self.to_datetime(duetime)
        if dt > self.now:
            log.warning('Do not schedule blocking work!')
        item: ScheduledItem = ScheduledItem(self, state, action, dt)

        self.get_trampoline().run(item)

        return item.disposable

    def schedule_required(self) -> bool:
        """Test if scheduling is required.

        Gets a value indicating whether the caller must call a
        schedule method. If the trampoline is active, then it returns
        False; otherwise, if the trampoline is not active, then it
        returns True.
        """
        return self.get_trampoline().idle()

    def ensure_trampoline(self, action):
        """Method for testing the TrampolineScheduler."""

        if self.schedule_required():
            return self.schedule(action)

        return action(self, None)
