"""Future-returning APIs for tornado coroutines.

.. seealso::

    :mod:`zmq.asyncio`

"""

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.

import asyncio
import warnings
from typing import Any, Type

from tornado.concurrent import Future
from tornado.ioloop import IOLoop

import zmq as _zmq
from zmq._future import _AsyncPoller, _AsyncSocket


class CancelledError(Exception):
    pass


class _TornadoFuture(Future):
    """Subclass Tornado Future, reinstating cancellation."""

    def cancel(self):
        if self.done():
            return False
        self.set_exception(CancelledError())
        return True

    def cancelled(self):
        return self.done() and isinstance(self.exception(), CancelledError)


class _CancellableTornadoTimeout:
    def __init__(self, loop, timeout):
        self.loop = loop
        self.timeout = timeout

    def cancel(self):
        self.loop.remove_timeout(self.timeout)


# mixin for tornado/asyncio compatibility


class _AsyncTornado:
    _Future: Type[asyncio.Future] = _TornadoFuture
    _READ = IOLoop.READ
    _WRITE = IOLoop.WRITE

    def _default_loop(self):
        return IOLoop.current()

    def _call_later(self, delay, callback):
        io_loop = self._get_loop()
        timeout = io_loop.call_later(delay, callback)
        return _CancellableTornadoTimeout(io_loop, timeout)


class Poller(_AsyncTornado, _AsyncPoller):
    def _watch_raw_socket(self, loop, socket, evt, f):
        """Schedule callback for a raw socket"""
        loop.add_handler(socket, lambda *args: f(), evt)

    def _unwatch_raw_sockets(self, loop, *sockets):
        """Unschedule callback for a raw socket"""
        for socket in sockets:
            loop.remove_handler(socket)


class Socket(_AsyncTornado, _AsyncSocket):
    _poller_class = Poller


Poller._socket_class = Socket


class Context(_zmq.Context[Socket]):

    # avoid sharing instance with base Context class
    _instance = None

    io_loop = None

    @staticmethod
    def _socket_class(self, socket_type):
        return Socket(self, socket_type)

    def __init__(self: "Context", *args: Any, **kwargs: Any) -> None:
        io_loop = kwargs.pop('io_loop', None)
        if io_loop is not None:
            warnings.warn(
                f"{self.__class__.__name__}(io_loop) argument is deprecated in pyzmq 22.2."
                " The currently active loop will always be used.",
                DeprecationWarning,
                stacklevel=2,
            )
        super().__init__(*args, **kwargs)
