# type: ignore
# Typing for this file is better left untold
"""
A module that monkey patches the standard asyncio module to add an
idle_call() method to the main loop. This method is used to execute a
callback whenever the loop is not busy handling anything else. This means
that it is a callback with lower priority than IO, timer, or even
call_soon() ones. These callback are called only once each.
"""

import asyncio
import functools
import collections
from asyncio import events
from inspect import iscoroutinefunction
from typing import Callable, Any

import slixmpp


class BadIdleCallParam(TypeError):
    def __init__(self) -> None:
        super().__init__("coroutines cannot be used with idle_call()")


def monkey_patch_asyncio_slixmpp() -> None:
    def idle_call(self: asyncio.AbstractEventLoop, callback: Callable[[], None]) -> None:
        if iscoroutinefunction(callback):
            raise BadIdleCallParam()
        handle = events.Handle(callback, [], self)
        self._idle.append(handle)

    def my_run_once(self: asyncio.AbstractEventLoop) -> None:
        if self._idle:
            self._ready.append(events.Handle(lambda: None, (), self))
        real_run_once(self)
        if self._idle:
            handle = self._idle.popleft()
            handle._run()

    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = None
    if loop is None:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    cls = loop.__class__
    cls._idle = collections.deque()
    cls.idle_call = idle_call
    real_run_once = cls._run_once
    cls._run_once = my_run_once

    spawn_event = slixmpp.xmlstream.XMLStream._spawn_event

    def patchy(self: slixmpp.xmlstream.XMLStream, xml: Any) -> None:
        self.loop.idle_call(functools.partial(spawn_event, self, xml))

    slixmpp.xmlstream.XMLStream._spawn_event = patchy
