import logging

log = logging.getLogger()

MIN_DELAY = 0.0
MAX_DELAY = 0.01
DELAY_INCREMENT = 0.0001
QUEUE_SIZE_THRESHOLD = 100


class CallLaterManager:
    def __init__(self, callLater):
        """
        :param callLater: (IReactorTime.callLater)
        """

        self._callLater = callLater
        self._pendingCallLaters = []
        self._delay = MIN_DELAY

    def get_min_delay(self):
        self._pendingCallLaters = [cl for cl in self._pendingCallLaters if cl.active()]
        queue_size = len(self._pendingCallLaters)
        if queue_size > QUEUE_SIZE_THRESHOLD:
            self._delay = min((self._delay + DELAY_INCREMENT), MAX_DELAY)
        else:
            self._delay = max((self._delay - 2.0 * DELAY_INCREMENT), MIN_DELAY)
        return self._delay

    def _cancel(self, call_later):
        """
        :param call_later: DelayedCall
        :return: (callable) canceller function
        """

        def cancel(reason=None):
            """
            :param reason: reason for cancellation, this is returned after cancelling the DelayedCall
            :return: reason
            """

            if call_later.active():
                call_later.cancel()
            if call_later in self._pendingCallLaters:
                self._pendingCallLaters.remove(call_later)
            return reason
        return cancel

    def stop(self):
        """
        Cancel any callLaters that are still running
        """

        from twisted.internet import defer
        while self._pendingCallLaters:
            canceller = self._cancel(self._pendingCallLaters[0])
            try:
                canceller()
            except (defer.CancelledError, defer.AlreadyCalledError, ValueError):
                pass

    def call_later(self, when, what, *args, **kwargs):
        """
        Schedule a call later and get a canceller callback function

        :param when: (float) delay in seconds
        :param what: (callable)
        :param args: (*tuple) args to be passed to the callable
        :param kwargs: (**dict) kwargs to be passed to the callable

        :return: (tuple) twisted.internet.base.DelayedCall object, canceller function
        """

        call_later = self._callLater(when, what, *args, **kwargs)
        canceller = self._cancel(call_later)
        self._pendingCallLaters.append(call_later)
        return call_later, canceller

    def call_soon(self, what, *args, **kwargs):
        delay = self.get_min_delay()
        return self.call_later(delay, what, *args, **kwargs)