"""
Taken verbatim from Jinja2.
https://github.com/mitsuhiko/jinja2/blob/master/jinja2/debug.py#L267
"""
import platform
import sys


def _init_ugly_crap():
    """This function implements a few ugly things so that we can patch the
    traceback objects.  The function returned allows resetting `tb_next` on
    any python traceback object.  Do not attempt to use this on non cpython
    interpreters
    """
    import ctypes
    from types import TracebackType

    # figure out side of _Py_ssize_t
    if hasattr(ctypes.pythonapi, "Py_InitModule4_64"):
        _Py_ssize_t = ctypes.c_int64
    else:
        _Py_ssize_t = ctypes.c_int

    # regular python
    class _PyObject(ctypes.Structure):
        pass

    _PyObject._fields_ = [
        ("ob_refcnt", _Py_ssize_t),
        ("ob_type", ctypes.POINTER(_PyObject)),
    ]

    # python with trace
    if hasattr(sys, "getobjects"):

        class _PyObject(ctypes.Structure):
            pass

        _PyObject._fields_ = [
            ("_ob_next", ctypes.POINTER(_PyObject)),
            ("_ob_prev", ctypes.POINTER(_PyObject)),
            ("ob_refcnt", _Py_ssize_t),
            ("ob_type", ctypes.POINTER(_PyObject)),
        ]

    class _Traceback(_PyObject):
        pass

    _Traceback._fields_ = [
        ("tb_next", ctypes.POINTER(_Traceback)),
        ("tb_frame", ctypes.POINTER(_PyObject)),
        ("tb_lasti", ctypes.c_int),
        ("tb_lineno", ctypes.c_int),
    ]

    def tb_set_next(tb, next):
        """Set the tb_next attribute of a traceback object."""
        if not (
            isinstance(tb, TracebackType)
            and (next is None or isinstance(next, TracebackType))
        ):
            raise TypeError("tb_set_next arguments must be traceback objects")
        obj = _Traceback.from_address(id(tb))
        if tb.tb_next is not None:
            old = _Traceback.from_address(id(tb.tb_next))
            old.ob_refcnt -= 1
        if next is None:
            obj.tb_next = ctypes.POINTER(_Traceback)()
        else:
            next = _Traceback.from_address(id(next))
            next.ob_refcnt += 1
            obj.tb_next = ctypes.pointer(next)

    return tb_set_next


tb_set_next = None
try:
    if sys.version_info[0] >= 3:
        tb_set_next = lambda tb, nxt: setattr(tb, "tb_next", nxt)
    elif platform.python_implementation() == "CPython":
        tb_set_next = _init_ugly_crap()
except Exception as exc:
    sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc))
del _init_ugly_crap
