// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef PEP384IMPL_H
#define PEP384IMPL_H

#include "sbkpython.h"
#include "shibokenmacros.h"

extern "C"
{

/*****************************************************************************
 *
 * RESOLVED: object.h
 *
 */
#ifdef Py_LIMITED_API
// Why the hell is this useful debugging function not allowed?
// BTW: When used, it breaks on Windows, intentionally!
LIBSHIBOKEN_API void _PyObject_Dump(PyObject *);
#endif

/*
 * There are a few structures that are needed, but cannot be used without
 * breaking the API. We use some heuristics to get those fields anyway
 * and validate that we really found them, see pep384impl.cpp .
 */

#ifdef Py_LIMITED_API

/*
 * These are the type object fields that we use.
 * We will verify that they never change.
 * The unused fields are intentionally named as "void *Xnn" because
 * the chance is smaller to forget to validate a field.
 * When we need more fields, we replace it back and add it to the
 * validation.
 */
typedef struct _typeobject {
    PyVarObject ob_base;
    const char *tp_name;
    Py_ssize_t tp_basicsize;
    void *X03; // Py_ssize_t tp_itemsize;
#ifdef PEP384_INTERN
    destructor tp_dealloc;
#else
    destructor X04;
#endif
    void *X05; // Py_ssize_t tp_vectorcall_offset;
    void *X06; // getattrfunc tp_getattr;
    void *X07; // setattrfunc tp_setattr;
    void *X08; // PyAsyncMethods *tp_as_async;
#ifdef PEP384_INTERN
    reprfunc tp_repr;
#else
    reprfunc X09;
#endif
    void *X10; // PyNumberMethods *tp_as_number;
    void *X11; // PySequenceMethods *tp_as_sequence;
    void *X12; // PyMappingMethods *tp_as_mapping;
    void *X13; // hashfunc tp_hash;
#ifdef PEP384_INTERN
    ternaryfunc tp_call;
#else
    ternaryfunc X14;
#endif
    reprfunc tp_str; // Only used for PEP384_INTERN and a shiboken test
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    void *X18; // PyBufferProcs *tp_as_buffer;
    unsigned long tp_flags;
    void *X20; // const char *tp_doc;
#ifdef PEP384_INTERN
    traverseproc tp_traverse;
    inquiry tp_clear;
#else
    traverseproc X21;
    inquiry X22;
#endif
    void *X23; // richcmpfunc tp_richcompare;
    Py_ssize_t tp_weaklistoffset;
    void *X25; // getiterfunc tp_iter;
#ifdef PEP384_INTERN
    iternextfunc tp_iternext;
#else
    iternextfunc X26;
#endif
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
#ifdef PEP384_INTERN
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
#else
    void *X31;
    descrgetfunc X32;
    descrsetfunc X33;
#endif
    Py_ssize_t tp_dictoffset;
#ifdef PEP384_INTERN
    initproc tp_init;
    allocfunc tp_alloc;
#else
    initproc X39;
    allocfunc X40;
#endif
    newfunc tp_new;
#ifdef PEP384_INTERN
    freefunc tp_free;
    inquiry tp_is_gc; /* For PyObject_IS_GC */
#else
    freefunc X41;
    inquiry X42; /* For PyObject_IS_GC */
#endif
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */

} PyTypeObject;

#ifndef PyObject_IS_GC
/* Test if an object has a GC head */
#define PyObject_IS_GC(o) \
    (PyType_IS_GC(Py_TYPE(o)) \
     && (Py_TYPE(o)->tp_is_gc == NULL || Py_TYPE(o)->tp_is_gc(o)))
#endif

LIBSHIBOKEN_API PyObject *_PepType_Lookup(PyTypeObject *type, PyObject *name);

#else // Py_LIMITED_API

inline PyObject *_PepType_Lookup(PyTypeObject *type, PyObject *name) { return _PyType_Lookup(type, name); }

#endif // Py_LIMITED_API

/// PYSIDE-939: We need the runtime version, given major << 16 + minor << 8 + micro
LIBSHIBOKEN_API long _PepRuntimeVersion();
/*****************************************************************************
 *
 * PYSIDE-535: Implement a clean type extension for PyPy
 *
 */

struct SbkObjectTypePrivate;

LIBSHIBOKEN_API SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type);
LIBSHIBOKEN_API void PepType_SOTP_delete(PyTypeObject *type);

struct SbkEnumType;
struct SbkEnumTypePrivate;

LIBSHIBOKEN_API SbkEnumTypePrivate *PepType_SETP(SbkEnumType *type);
LIBSHIBOKEN_API void PepType_SETP_delete(SbkEnumType *enumType);

/*****************************************************************************/

// functions used everywhere

/// (convenience) Return the unqualified type name
LIBSHIBOKEN_API const char *PepType_GetNameStr(PyTypeObject *type);

/// (convenience) Return the fully qualified type name(PepType_GetFullyQualifiedNameStr())
/// as C-string
LIBSHIBOKEN_API const char *PepType_GetFullyQualifiedNameStr(PyTypeObject *type);

LIBSHIBOKEN_API PyObject *Pep_GetPartialFunction(void);

/*****************************************************************************
 *
 * RESOLVED: pydebug.h
 *
 */
#ifdef Py_LIMITED_API
/*
 * We have no direct access to Py_VerboseFlag because debugging is not
 * supported. The python developers are partially a bit too rigorous.
 * Instead, we compute the value and use a function call macro.
 * Was before: extern LIBSHIBOKEN_API int Py_VerboseFlag;
 */
LIBSHIBOKEN_API int Pep_GetFlag(const char *name);
LIBSHIBOKEN_API int Pep_GetVerboseFlag(void);
#endif

#if (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030C0000) || PY_VERSION_HEX < 0x030C0000
#  define PEP_OLD_ERR_API
#endif

// pyerrors.h
#ifdef PEP_OLD_ERR_API
LIBSHIBOKEN_API PyObject *PepException_GetArgs(PyObject *ex);
LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args);
#else
inline PyObject *PepException_GetArgs(PyObject *ex) { return PyException_GetArgs(ex); }
inline void PepException_SetArgs(PyObject *ex, PyObject *args)
{ PyException_SetArgs(ex, args); }
#endif

/*****************************************************************************
 *
 * RESOLVED: unicodeobject.h
 *
 */

///////////////////////////////////////////////////////////////////////
//
// PYSIDE-813: About The Length Of Unicode Objects
// -----------------------------------------------
//
// In Python 2 and before Python 3.3, the macro PyUnicode_GET_SIZE
// worked fine and really like a macro.
//
// Meanwhile, the unicode objects have changed their layout very much,
// and the former cheap macro call has become a real function call
// that converts objects and needs PyMemory.
//
// That is not only inefficient, but also requires the GIL!
// This problem was visible by debug Python and qdatastream_test.py .
// It was found while fixing the refcount problem of PYSIDE-813 which
// needed a debug Python.
//

#ifdef Py_LIMITED_API

enum PepUnicode_Kind {
#if PY_VERSION_HEX < 0x030C0000
    PepUnicode_WCHAR_KIND = 0,
#endif
    PepUnicode_1BYTE_KIND = 1,
    PepUnicode_2BYTE_KIND = 2,
    PepUnicode_4BYTE_KIND = 4
};

LIBSHIBOKEN_API int _PepUnicode_KIND(PyObject *);
LIBSHIBOKEN_API int _PepUnicode_IS_ASCII(PyObject *str);
LIBSHIBOKEN_API int _PepUnicode_IS_COMPACT(PyObject *str);

LIBSHIBOKEN_API void *_PepUnicode_DATA(PyObject *str);

#else

enum PepUnicode_Kind : uint8_t {
#if PY_VERSION_HEX < 0x030C0000
    PepUnicode_WCHAR_KIND = PyUnicode_WCHAR_KIND,
#endif
    PepUnicode_1BYTE_KIND = PyUnicode_1BYTE_KIND,
    PepUnicode_2BYTE_KIND = PyUnicode_2BYTE_KIND,
    PepUnicode_4BYTE_KIND =  PyUnicode_4BYTE_KIND
};

#define _PepUnicode_KIND         PyUnicode_KIND
#define _PepUnicode_DATA         PyUnicode_DATA
#define _PepUnicode_IS_COMPACT   PyUnicode_IS_COMPACT
#define _PepUnicode_IS_ASCII     PyUnicode_IS_ASCII
#endif

/*****************************************************************************
 *
 * RESOLVED: methodobject.h
 *
 */

#ifdef Py_LIMITED_API

using PyCFunctionObject = struct _pycfunc;
inline const char *PepCFunction_GET_NAMESTR(PyObject *func)
{
    return PyUnicode_AsUTF8AndSize(PyObject_GetAttrString(func, "__name__"), nullptr);
}
#else
inline const char *PepCFunction_GET_NAMESTR(PyObject *func)
{
    return reinterpret_cast<const PyCFunctionObject *>(func)->m_ml->ml_name;
}
#endif

/*****************************************************************************
 *
 * RESOLVED: pythonrun.h
 *
 */
#ifdef Py_LIMITED_API
LIBSHIBOKEN_API PyObject *PyRun_String(const char *, int, PyObject *, PyObject *);
#endif

/*****************************************************************************
 *
 * RESOLVED: funcobject.h
 *
 */
#ifdef Py_LIMITED_API
using PyFunctionObject = struct _func;

extern LIBSHIBOKEN_API PyTypeObject *PepFunction_TypePtr;
LIBSHIBOKEN_API PyObject *PepFunction_Get(PyObject *, const char *);

inline bool PyFunction_Check(PyObject *op) { return Py_TYPE(op) == PepFunction_TypePtr; }

inline PyObject *PyFunction_GetCode(PyObject *func)  { return  PepFunction_Get(func, "__code__"); }
inline PyObject *PyFunction_GET_CODE(PyObject *func) { return PyFunction_GetCode(func); }
inline PyObject *PepFunction_GetName(PyObject *func) { return PepFunction_Get(func, "__name__"); }
#else
#define PepFunction_TypePtr         (&PyFunction_Type)
inline PyObject *PepFunction_GetName(PyObject *func)  { return reinterpret_cast<PyFunctionObject *>(func)->func_name; }
#endif

/*****************************************************************************
 *
 * RESOLVED: classobject.h
 *
 */
#ifdef Py_LIMITED_API

using PyMethodObject = struct _meth;

extern LIBSHIBOKEN_API PyTypeObject *PepMethod_TypePtr;

LIBSHIBOKEN_API PyObject *PyMethod_New(PyObject *, PyObject *);
LIBSHIBOKEN_API PyObject *PyMethod_Function(PyObject *);
LIBSHIBOKEN_API PyObject *PyMethod_Self(PyObject *);

inline bool PyMethod_Check(PyObject *op) { return op->ob_type == PepMethod_TypePtr; }

inline PyObject *PyMethod_GET_SELF(PyObject *op) { return PyMethod_Self(op); }
inline PyObject *PyMethod_GET_FUNCTION(PyObject *op) { return PyMethod_Function(op); }
#endif

/*****************************************************************************
 *
 * RESOLVED: code.h
 *
 */
#ifdef Py_LIMITED_API
/* Bytecode object */

// we have to grab the code object from python
using PepCodeObject = struct _code;

LIBSHIBOKEN_API int PepCode_Get(PepCodeObject *co, const char *name);
LIBSHIBOKEN_API int PepCode_Check(PyObject *o);

inline int PepCode_GET_FLAGS(PepCodeObject *o) { return PepCode_Get(o, "co_flags"); }
inline int PepCode_GET_ARGCOUNT(PepCodeObject *o) { return PepCode_Get(o, "co_argcount"); }

LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function);

/* Masks for co_flags above */
#  define CO_OPTIMIZED    0x0001
#  define CO_NEWLOCALS    0x0002
#  define CO_VARARGS      0x0004
#  define CO_VARKEYWORDS  0x0008
#  define CO_NESTED       0x0010
#  define CO_GENERATOR    0x0020

#else

using PepCodeObject = PyCodeObject;

inline int PepCode_GET_FLAGS(PepCodeObject *o) { return o->co_flags; }
inline int PepCode_GET_ARGCOUNT(PepCodeObject *o) { return o->co_argcount; }
inline int PepCode_Check(PyObject *o) { return PyCode_Check(o); }

#  ifdef PYPY_VERSION

LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function);

#    else
#    define PepFunction_GetDefaults PyFunction_GetDefaults
#  endif // PYPY_VERSION
#endif

/*****************************************************************************
 *
 * RESOLVED: datetime.h
 *
 */
#ifdef Py_LIMITED_API

LIBSHIBOKEN_API int PyDateTime_Get(PyObject *ob, const char *name);

inline int PyDateTime_GetYear(PyObject *o)        { return PyDateTime_Get(o, "year"); }
inline int PyDateTime_GetMonth(PyObject *o)       { return PyDateTime_Get(o, "month"); }
inline int PyDateTime_GetDay(PyObject *o)         { return PyDateTime_Get(o, "day"); }
inline int PyDateTime_GetHour(PyObject *o)        { return PyDateTime_Get(o, "hour"); }
inline int PyDateTime_GetMinute(PyObject *o)      { return PyDateTime_Get(o, "minute"); }
inline int PyDateTime_GetSecond(PyObject *o)      { return PyDateTime_Get(o, "second"); }
inline int PyDateTime_GetMicrosecond(PyObject *o) { return PyDateTime_Get(o, "microsecond"); }
inline int PyDateTime_GetFold(PyObject *o)        { return PyDateTime_Get(o, "fold"); }

inline int PyDateTime_GET_YEAR(PyObject *o)  { return PyDateTime_GetYear(o); }
inline int PyDateTime_GET_MONTH(PyObject *o) { return PyDateTime_GetMonth(o); }
inline int PyDateTime_GET_DAY(PyObject *o)   { return PyDateTime_GetDay(o); }

inline int PyDateTime_DATE_GET_HOUR(PyObject *o)        { return PyDateTime_GetHour(o); }
inline int PyDateTime_DATE_GET_MINUTE(PyObject *o)      { return PyDateTime_GetMinute(o); }
inline int PyDateTime_DATE_GET_SECOND(PyObject *o)      { return PyDateTime_GetSecond(o); }
inline int PyDateTime_DATE_GET_MICROSECOND(PyObject *o) { return PyDateTime_GetMicrosecond(o); }
inline int PyDateTime_DATE_GET_FOLD(PyObject *o)        { return PyDateTime_GetFold(o); }

inline int PyDateTime_TIME_GET_HOUR(PyObject *o)        { return PyDateTime_GetHour(o); }
inline int PyDateTime_TIME_GET_MINUTE(PyObject *o)      { return PyDateTime_GetMinute(o); }
inline int PyDateTime_TIME_GET_SECOND(PyObject *o)      { return PyDateTime_GetSecond(o); }
inline int PyDateTime_TIME_GET_MICROSECOND(PyObject *o) { return PyDateTime_GetMicrosecond(o); }
inline int PyDateTime_TIME_GET_FOLD(PyObject *o)        { return PyDateTime_GetFold(o); }

/* Define structure slightly similar to C API. */
typedef struct {
    PyObject *module;
    /* type objects */
    PyTypeObject *DateType;
    PyTypeObject *DateTimeType;
    PyTypeObject *TimeType;
    PyTypeObject *DeltaType;
    PyTypeObject *TZInfoType;
} datetime_struc;

LIBSHIBOKEN_API datetime_struc *init_DateTime(void);

#define PyDateTime_IMPORT     PyDateTimeAPI = init_DateTime()

extern LIBSHIBOKEN_API datetime_struc *PyDateTimeAPI;

inline bool PyDate_Check(PyObject *op)      { return PyObject_TypeCheck(op, PyDateTimeAPI->DateType); }
inline bool PyDateTime_Check(PyObject *op)  { return PyObject_TypeCheck(op, PyDateTimeAPI->DateTimeType); }
inline bool PyTime_Check(PyObject *op)      { return PyObject_TypeCheck(op, PyDateTimeAPI->TimeType); }

LIBSHIBOKEN_API PyObject *PyDate_FromDate(int year, int month, int day);
LIBSHIBOKEN_API PyObject *PyDateTime_FromDateAndTime(
    int year, int month, int day, int hour, int min, int sec, int usec);
LIBSHIBOKEN_API PyObject *PyTime_FromTime(
    int hour, int minute, int second, int usecond);

#endif /* Py_LIMITED_API */

/*****************************************************************************
 *
 * Extra support for name mangling
 *
 */

// PYSIDE-772: This function supports the fix, but is not meant as public.
LIBSHIBOKEN_API PyObject *_Pep_PrivateMangle(PyObject *self, PyObject *name);

/*****************************************************************************
 *
 * Extra support for signature.cpp
 *
 */

#ifdef Py_LIMITED_API
extern LIBSHIBOKEN_API PyTypeObject *PepStaticMethod_TypePtr;
LIBSHIBOKEN_API PyObject *PyStaticMethod_New(PyObject *callable);
#else
#define PepStaticMethod_TypePtr &PyStaticMethod_Type
#endif

#ifdef PYPY_VERSION
extern LIBSHIBOKEN_API PyTypeObject *PepBuiltinMethod_TypePtr;
#endif

// Although not PEP specific, we resolve this similar issue, here:
#define PepMethodDescr_TypePtr &PyMethodDescr_Type

/*****************************************************************************
 *
 * Newly introduced convenience functions
 *
 * This is not defined if Py_LIMITED_API is defined.
 */

// Evaluate a script and return the variable `result`
LIBSHIBOKEN_API PyObject *PepRun_GetResult(const char *command);

// Call PyType_Type.tp_new returning a PyType object.
LIBSHIBOKEN_API PyTypeObject *PepType_Type_tp_new(PyTypeObject *metatype,
                                                  PyObject *args,
                                                  PyObject *kwds);

/*****************************************************************************
 *
 * Runtime support for Python 3.8 incompatibilities
 *
 */

#ifndef Py_TPFLAGS_METHOD_DESCRIPTOR
/* Objects behave like an unbound method */
#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17)
#endif

/*****************************************************************************
 *
 * Runtime support for Python 3.12 incompatibility
 *
 */

LIBSHIBOKEN_API PyObject *PepType_GetDict(PyTypeObject *type);

// This function does not exist as PyType_SetDict. But because tp_dict
// is no longer considered to be accessible, we treat it as such.
LIBSHIBOKEN_API int PepType_SetDict(PyTypeObject *type, PyObject *dict);

// Runtime support for Python 3.13 stable ABI

// Return dictionary of the global variables in the current execution frame
LIBSHIBOKEN_API PyObject *PepEval_GetFrameGlobals();

// Return a dictionary of the builtins in the current execution frame
LIBSHIBOKEN_API PyObject *PepEval_GetFrameBuiltins();

LIBSHIBOKEN_API int PepModule_AddType(PyObject *module, PyTypeObject *type);

LIBSHIBOKEN_API int PepModule_Add(PyObject *module, const char *name, PyObject *value);

/*****************************************************************************
 *
 * Module Initialization
 *
 */

LIBSHIBOKEN_API void Pep384_Init(void);

} // extern "C"

#endif // PEP384IMPL_H
