@cython.no_gc_clear
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class Handle:
    def __cinit__(self):
        self._cancelled = 0
        self.cb_type = 0
        self._source_traceback = None

    cdef inline _set_loop(self, Loop loop):
        self.loop = loop
        if UVLOOP_DEBUG:
            loop._debug_cb_handles_total += 1
            loop._debug_cb_handles_count += 1
        if loop._debug:
            self._source_traceback = extract_stack()

    cdef inline _set_context(self, object context):
        cdef PyContext* current_context

        if PY37:
            if context is None:
                context = copy_current_context()
            self.context = context
        else:
            if context is not None:
                raise NotImplementedError(
                    '"context" argument requires Python 3.7')
            self.context = None

    def __dealloc__(self):
        if UVLOOP_DEBUG and self.loop is not None:
            self.loop._debug_cb_handles_count -= 1
        if self.loop is None:
            raise RuntimeError('Handle.loop is None in Handle.__dealloc__')

    def __init__(self):
        raise TypeError(
            '{} is not supposed to be instantiated from Python'.format(
                self.__class__.__name__))

    cdef inline _run(self):
        cdef:
            int cb_type
            object callback

        if self._cancelled:
            return

        cb_type = self.cb_type

        Py_INCREF(self)   # Since _run is a cdef and there's no BoundMethod,
                          # we guard 'self' manually (since the callback
                          # might cause GC of the handle.)
        try:
            if PY37:
                assert self.context is not None
                PyContext_Enter(<PyContext*>self.context)

            if cb_type == 1:
                callback = self.arg1
                args = self.arg2

                if args is None:
                    callback()
                else:
                    callback(*args)

            elif cb_type == 2:
                (<method_t>self.callback)(self.arg1)

            elif cb_type == 3:
                (<method1_t>self.callback)(self.arg1, self.arg2)

            elif cb_type == 4:
                (<method2_t>self.callback)(self.arg1, self.arg2, self.arg3)

            elif cb_type == 5:
                (<method3_t>self.callback)(
                    self.arg1, self.arg2, self.arg3, self.arg4)

            else:
                raise RuntimeError('invalid Handle.cb_type: {}'.format(
                    cb_type))

        except Exception as ex:
            if cb_type == 1:
                msg = 'Exception in callback {}'.format(callback)
            else:
                msg = 'Exception in callback {}'.format(self.meth_name)

            context = {
                'message': msg,
                'exception': ex,
                'handle': self,
            }

            if self._source_traceback is not None:
                context['source_traceback'] = self._source_traceback

            self.loop.call_exception_handler(context)

        finally:
            context = self.context
            Py_DECREF(self)
            if PY37:
                PyContext_Exit(<PyContext*>context)

    cdef _cancel(self):
        self._cancelled = 1
        self.callback = NULL
        self.arg2 = self.arg3 = self.arg4 = None

    cdef _format_handle(self):
        # Mirrors `asyncio.base_events._format_handle`.
        if self.cb_type == 1:
            cb = self.arg1
            if isinstance(getattr(cb, '__self__', None), aio_Task):
                try:
                    return repr(cb.__self__)
                except (AttributeError, TypeError, ValueError) as ex:
                    # Cython generates empty __code__ objects for coroutines
                    # that can crash asyncio.Task.__repr__ with an
                    # AttributeError etc.  Guard against that.
                    self.loop.call_exception_handler({
                        'message': 'exception in Task.__repr__',
                        'task': cb.__self__,
                        'exception': ex,
                        'handle': self,
                    })
        return repr(self)

    # Public API

    def __repr__(self):
        info = [self.__class__.__name__]

        if self._cancelled:
            info.append('cancelled')

        if self.cb_type == 1:
            func = self.arg1
            # Cython can unset func.__qualname__/__name__, hence the checks.
            if hasattr(func, '__qualname__') and func.__qualname__:
                cb_name = func.__qualname__
            elif hasattr(func, '__name__') and func.__name__:
                cb_name = func.__name__
            else:
                cb_name = repr(func)

            info.append(cb_name)
        else:
            info.append(self.meth_name)

        if self._source_traceback is not None:
            frame = self._source_traceback[-1]
            info.append('created at {}:{}'.format(frame[0], frame[1]))

        return '<' + ' '.join(info) + '>'

    def cancel(self):
        self._cancel()

    def cancelled(self):
        return self._cancelled


@cython.no_gc_clear
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class TimerHandle:
    def __cinit__(self, Loop loop, object callback, object args,
                  uint64_t delay, object context):

        self.loop = loop
        self.callback = callback
        self.args = args
        self._cancelled = 0

        if UVLOOP_DEBUG:
            self.loop._debug_cb_timer_handles_total += 1
            self.loop._debug_cb_timer_handles_count += 1

        if PY37:
            if context is None:
                context = copy_current_context()
            self.context = context
        else:
            if context is not None:
                raise NotImplementedError(
                    '"context" argument requires Python 3.7')
            self.context = None

        if loop._debug:
            self._source_traceback = extract_stack()

        self.timer = UVTimer.new(
            loop, <method_t>self._run, self, delay)

        self.timer.start()

        # Only add to loop._timers when `self.timer` is successfully created
        loop._timers.add(self)

    def __dealloc__(self):
        if UVLOOP_DEBUG:
            self.loop._debug_cb_timer_handles_count -= 1
        if self.timer is not None:
            raise RuntimeError('active TimerHandle is deallacating')

    cdef _cancel(self):
        if self._cancelled == 1:
            return
        self._cancelled = 1
        self._clear()

    cdef inline _clear(self):
        if self.timer is None:
            return

        self.args = None

        try:
            self.loop._timers.remove(self)
        finally:
            self.timer._close()
            self.timer = None  # let it die asap

    cdef _run(self):
        if self._cancelled == 1:
            return

        callback = self.callback
        args = self.args
        self._clear()

        Py_INCREF(self)  # Since _run is a cdef and there's no BoundMethod,
                         # we guard 'self' manually.
        if self.loop._debug:
            started = time_monotonic()
        try:
            if PY37:
                assert self.context is not None
                PyContext_Enter(<PyContext*>self.context)

            if args is not None:
                callback(*args)
            else:
                callback()
        except Exception as ex:
            context = {
                'message': 'Exception in callback {}'.format(callback),
                'exception': ex,
                'handle': self,
            }

            if self._source_traceback is not None:
                context['source_traceback'] = self._source_traceback

            self.loop.call_exception_handler(context)
        else:
            if self.loop._debug:
                delta = time_monotonic() - started
                if delta > self.loop.slow_callback_duration:
                    aio_logger.warning(
                        'Executing %r took %.3f seconds',
                        self, delta)
        finally:
            context = self.context
            Py_DECREF(self)
            if PY37:
                PyContext_Exit(<PyContext*>context)

    # Public API

    def __repr__(self):
        info = [self.__class__.__name__]

        if self._cancelled:
            info.append('cancelled')

        func = self.callback
        if hasattr(func, '__qualname__'):
            cb_name = getattr(func, '__qualname__')
        elif hasattr(func, '__name__'):
            cb_name = getattr(func, '__name__')
        else:
            cb_name = repr(func)

        info.append(cb_name)

        if self._source_traceback is not None:
            frame = self._source_traceback[-1]
            info.append('created at {}:{}'.format(frame[0], frame[1]))

        return '<' + ' '.join(info) + '>'

    def cancelled(self):
        return self._cancelled

    def cancel(self):
        self._cancel()



cdef new_Handle(Loop loop, object callback, object args, object context):
    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(context)

    handle.cb_type = 1

    handle.arg1 = callback
    handle.arg2 = args

    return handle


cdef new_MethodHandle(Loop loop, str name, method_t callback, object ctx):
    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(None)

    handle.cb_type = 2
    handle.meth_name = name

    handle.callback = <void*> callback
    handle.arg1 = ctx

    return handle


cdef new_MethodHandle1(Loop loop, str name, method1_t callback,
                       object ctx, object arg):

    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(None)

    handle.cb_type = 3
    handle.meth_name = name

    handle.callback = <void*> callback
    handle.arg1 = ctx
    handle.arg2 = arg

    return handle

cdef new_MethodHandle2(Loop loop, str name, method2_t callback, object ctx,
                       object arg1, object arg2):

    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(None)

    handle.cb_type = 4
    handle.meth_name = name

    handle.callback = <void*> callback
    handle.arg1 = ctx
    handle.arg2 = arg1
    handle.arg3 = arg2

    return handle

cdef new_MethodHandle3(Loop loop, str name, method3_t callback, object ctx,
                       object arg1, object arg2, object arg3):

    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(None)

    handle.cb_type = 5
    handle.meth_name = name

    handle.callback = <void*> callback
    handle.arg1 = ctx
    handle.arg2 = arg1
    handle.arg3 = arg2
    handle.arg4 = arg3

    return handle


cdef extract_stack():
    """Replacement for traceback.extract_stack() that only does the
    necessary work for asyncio debug mode.
    """
    f = sys_getframe()
    if f is None:
        return

    try:
        stack = tb_StackSummary.extract(tb_walk_stack(f),
                                        limit=DEBUG_STACK_DEPTH,
                                        lookup_lines=False)
    finally:
        f = None

    stack.reverse()
    return stack


cdef copy_current_context():
    cdef PyContext* current_context

    if PY37:
        current_context = PyContext_CopyCurrent()
        py_context = <object>current_context
        Py_XDECREF(<PyObject*>current_context)
        return py_context

    raise NotImplementedError('"contextvars" support requires Python 3.7+')
