// Collects code that was copied in from cpython, for a couple of different reasons:
//  * We wanted to modify it to produce a more efficient version for our uses
//  * We needed to call it and it was static :(
//  * We wanted to call it and needed to backport it

#include "pythonsupport.h"

#ifdef Py_GIL_DISABLED
// Cold slow path of CPy_GetAttrRef (declared in pythonsupport.h). Reached only
// when the inline fast-path try-incref fails: the value is owned by another
// thread, so taking a reference requires an atomic shared-refcount operation.
// Kept out-of-line so the inline fast path stays small.
//
// First try the lock-free shared-refcount CAS. If the value has not had
// maybe-weakref set yet (for example, it was published by CPy_InitAttrRef), force
// a cross-thread reference via _Py_NewRefWithLock, which cannot fail and sets
// maybe-weakref so subsequent reads take the fast path. The value was already
// observed in the field by CPy_GetAttrRef; CPy_SetAttrRef's QSBR-delayed decref
// keeps any replaced value alive long enough for this reader.
CPy_NOINLINE
PyObject *CPy_GetAttrRefSlow(PyObject *v) {
    if (_Py_TryIncRefShared(v)) {
        return v;
    }
    return _Py_NewRefWithLock(v);  // sets maybe-weakref; cannot fail
}
#endif

/////////////////////////////////////////
// Adapted from bltinmodule.c in Python 3.7.0
PyObject*
update_bases(PyObject *bases)
{
    Py_ssize_t i, j;
    PyObject *base, *meth, *new_base, *result, *new_bases = NULL;
    PyObject *stack[1] = {bases};
    assert(PyTuple_Check(bases));

    Py_ssize_t nargs = PyTuple_GET_SIZE(bases);
    for (i = 0; i < nargs; i++) {
        base = PyTuple_GET_ITEM(bases, i);
        if (PyType_Check(base)) {
            if (new_bases) {
                /* If we already have made a replacement, then we append every normal base,
                   otherwise just skip it. */
                if (PyList_Append(new_bases, base) < 0) {
                    goto error;
                }
            }
            continue;
        }
        if (PyObject_GetOptionalAttr(base, mypyc_interned_str.__mro_entries__, &meth) < 0) {
            goto error;
        }
        if (!meth) {
            if (new_bases) {
                if (PyList_Append(new_bases, base) < 0) {
                    goto error;
                }
            }
            continue;
        }
        new_base = PyObject_Vectorcall(meth, stack, 1, NULL);
        Py_DECREF(meth);
        if (!new_base) {
            goto error;
        }
        if (!PyTuple_Check(new_base)) {
            PyErr_SetString(PyExc_TypeError,
                            "__mro_entries__ must return a tuple");
            Py_DECREF(new_base);
            goto error;
        }
        if (!new_bases) {
            /* If this is a first successful replacement, create new_bases list and
               copy previously encountered bases. */
            if (!(new_bases = PyList_New(i))) {
                goto error;
            }
            for (j = 0; j < i; j++) {
                base = PyTuple_GET_ITEM(bases, j);
                PyList_SET_ITEM(new_bases, j, base);
                Py_INCREF(base);
            }
        }
        j = PyList_GET_SIZE(new_bases);
        if (PyList_SetSlice(new_bases, j, j, new_base) < 0) {
            goto error;
        }
        Py_DECREF(new_base);
    }
    if (!new_bases) {
        return bases;
    }
    result = PyList_AsTuple(new_bases);
    Py_DECREF(new_bases);
    return result;

error:
    Py_XDECREF(new_bases);
    return NULL;
}

// From Python 3.7's typeobject.c
int
init_subclass(PyTypeObject *type, PyObject *kwds)
{
    PyObject *super, *func, *result;
    PyObject *args[2] = {(PyObject *)type, (PyObject *)type};

    super = PyObject_Vectorcall((PyObject *)&PySuper_Type, args, 2, NULL);
    if (super == NULL) {
        return -1;
    }

    func = PyObject_GetAttr(super, mypyc_interned_str.__init_subclass__);
    Py_DECREF(super);
    if (func == NULL) {
        return -1;
    }

    result = _PyObject_FastCallDict(func, NULL, 0, kwds);
    Py_DECREF(func);
    if (result == NULL) {
        return -1;
    }

    Py_DECREF(result);
    return 0;
}

#if CPY_3_12_FEATURES

// Slow path of CPyLong_AsSsize_tAndOverflow (non-inlined)
Py_ssize_t
CPyLong_AsSsize_tAndOverflow_(PyObject *vv, int *overflow)
{
    PyLongObject *v = (PyLongObject *)vv;
    size_t x, prev;
    Py_ssize_t res;
    Py_ssize_t i;
    int sign;

    *overflow = 0;

    res = -1;
    i = CPY_LONG_TAG(v);

    sign = 1;
    x = 0;
    if (i & CPY_SIGN_NEGATIVE) {
        sign = -1;
    }
    i >>= CPY_NON_SIZE_BITS;
    while (--i >= 0) {
        prev = x;
        x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i);
        if ((x >> PyLong_SHIFT) != prev) {
            *overflow = sign;
            goto exit;
        }
    }
    /* Haven't lost any bits, but casting to long requires extra
     * care.
     */
    if (x <= (size_t)CPY_TAGGED_MAX) {
        res = (Py_ssize_t)x * sign;
    }
    else if (sign < 0 && x == CPY_TAGGED_ABS_MIN) {
        res = CPY_TAGGED_MIN;
    }
    else {
        *overflow = sign;
        /* res is already set to -1 */
    }
  exit:
    return res;
}

#else

// Slow path of CPyLong_AsSsize_tAndOverflow (non-inlined, Python 3.11 and earlier)
Py_ssize_t
CPyLong_AsSsize_tAndOverflow_(PyObject *vv, int *overflow)
{
    /* This version by Tim Peters */
    PyLongObject *v = (PyLongObject *)vv;
    size_t x, prev;
    Py_ssize_t res;
    Py_ssize_t i;
    int sign;

    *overflow = 0;

    res = -1;
    i = Py_SIZE(v);

    sign = 1;
    x = 0;
    if (i < 0) {
        sign = -1;
        i = -(i);
    }
    while (--i >= 0) {
        prev = x;
        x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i);
        if ((x >> PyLong_SHIFT) != prev) {
            *overflow = sign;
            goto exit;
        }
    }
    /* Haven't lost any bits, but casting to long requires extra
     * care.
     */
    if (x <= (size_t)CPY_TAGGED_MAX) {
        res = (Py_ssize_t)x * sign;
    }
    else if (sign < 0 && x == CPY_TAGGED_ABS_MIN) {
        res = CPY_TAGGED_MIN;
    }
    else {
        *overflow = sign;
        /* res is already set to -1 */
    }
  exit:
    return res;
}


#endif
