@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):
        if context is None:
            context = Context_CopyCurrent()
        self.context = context

    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

        # Since _run is a cdef and there's no BoundMethod,
        # we guard 'self' manually (since the callback
        # might cause GC of the handle.)
        Py_INCREF(self)

        try:
            assert self.context is not None
            Context_Enter(self.context)

            if cb_type == 1:
                callback = self.arg1
                if callback is None:
                    raise RuntimeError(
                        'cannot run Handle; callback is not set')

                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 (KeyboardInterrupt, SystemExit):
            raise
        except BaseException 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)
            Context_Exit(context)

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

    cdef _format_handle(self):
        # Mirrors `asyncio.base_events._format_handle`.
        if self.cb_type == 1 and self.arg1 is not None:
            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 and self.arg1 is not None:
            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)
        elif self.meth_name is not None:
            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 context is None:
            context = Context_CopyCurrent()
        self.context = context

        if loop._debug:
            self._debug_info = (
                format_callback_name(callback),
                extract_stack()
            )
        else:
            self._debug_info = None

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

        self.timer.start()
        self._when = self.timer.get_when() * 1e-3

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

    property _source_traceback:
        def __get__(self):
            if self._debug_info is not None:
                return self._debug_info[1]

    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.callback = None
        self.args = None

        try:
            self.loop._timers.remove(self)
        finally:
            self.timer._close()
            self.timer = None  # let the UVTimer handle GC

    cdef _run(self):
        if self._cancelled == 1:
            return
        if self.callback is None:
            raise RuntimeError('cannot run TimerHandle; callback is not set')

        callback = self.callback
        args = self.args

        # Since _run is a cdef and there's no BoundMethod,
        # we guard 'self' manually.
        Py_INCREF(self)

        if self.loop._debug:
            started = time_monotonic()
        try:
            assert self.context is not None
            Context_Enter(self.context)

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

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

            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)
            Context_Exit(context)
            self._clear()

    # Public API

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

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

        if self._debug_info is not None:
            callback_name = self._debug_info[0]
            source_traceback = self._debug_info[1]
        else:
            callback_name = None
            source_traceback = None

        if callback_name is not None:
            info.append(callback_name)
        elif self.callback is not None:
            info.append(format_callback_name(self.callback))

        if source_traceback is not None:
            frame = 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()

    def when(self):
        return self._when


cdef format_callback_name(func):
    if hasattr(func, '__qualname__'):
        cb_name = getattr(func, '__qualname__')
    elif hasattr(func, '__name__'):
        cb_name = getattr(func, '__name__')
    else:
        cb_name = repr(func)
    return cb_name


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 context,
                      object bound_to):
    cdef Handle handle
    handle = Handle.__new__(Handle)
    handle._set_loop(loop)
    handle._set_context(context)

    handle.cb_type = 2
    handle.meth_name = name

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

    return handle


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

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

    handle.cb_type = 3
    handle.meth_name = name

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

    return handle


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

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

    handle.cb_type = 4
    handle.meth_name = name

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

    return handle


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

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

    handle.cb_type = 5
    handle.meth_name = name

    handle.callback = <void*> callback
    handle.arg1 = bound_to
    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
