// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

#ifndef __PYTHON_H__
#define __PYTHON_H__

#include "../common/py_version.hpp"

#include <cstdint>

#ifndef _WIN32
typedef unsigned int DWORD;
typedef ssize_t SSIZE_T;
#endif
typedef SSIZE_T Py_ssize_t;

// defines limited header of Python API for compatible access across a number of Pythons.

class PyTypeObject;
class PyThreadState;

#define PyObject_HEAD           \
    size_t ob_refcnt;           \
    PyTypeObject *ob_type;

#define PyObject_VAR_HEAD       \
    PyObject_HEAD               \
    size_t ob_size; /* Number of items in variable part */

class PyObject {
public:
    PyObject_HEAD
};

class PyVarObject : public PyObject {
public:
    size_t ob_size; /* Number of items in variable part */
};

// 2.5 - 3.7
class PyFunctionObject : public PyObject {
public:
    PyObject *func_code;    /* A code object */
};

// 2.5 - 2.7 compatible
class PyStringObject : public PyVarObject {
public:
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
};

// 2.4 - 3.7 compatible
typedef struct {
    PyObject_HEAD
    size_t length;      /* Length of raw Unicode data in buffer */
    wchar_t *str;       /* Raw Unicode buffer */
    long hash;          /* Hash value; -1 if not set */
} PyUnicodeObject;


class PyFrameObject : public PyObject {
    // After 3.10 we don't really have things we want to reuse common, so,
    // create an empty base (it's not based on PyVarObject because on Python 3.11
    // it's just a PyObject and no longer a PyVarObject -- the part related to
    // the var object must be declared in ech subclass in this case).
};

// 2.4 - 3.7 compatible
class PyFrameObjectBaseUpTo39 : public PyFrameObject {
public:
    size_t ob_size; /* Number of items in variable part -- i.e.: PyVarObject*/
    
    PyFrameObjectBaseUpTo39 *f_back;  /* previous frame, or nullptr */
    PyObject *f_code;           /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    
    // It has more things, but we're only interested in things up to f_trace.

};


// https://github.com/python/cpython/blob/3.10/Include/cpython/frameobject.h
class PyFrameObject310 : public PyFrameObject {
public:
    size_t ob_size; /* Number of items in variable part -- i.e.: PyVarObject*/

    PyFrameObject310 *f_back;      /* previous frame, or NULL */
    PyObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    PyObject *f_trace;          /* Trace function */

    // It has more things, but we're only interested in things up to f_trace.
};

typedef uint16_t _Py_CODEUNIT;

// https://github.com/python/cpython/blob/3.11/Include/internal/pycore_frame.h
typedef struct _PyInterpreterFrame311 {
    /* "Specials" section */
    PyFunctionObject *f_func; /* Strong reference */
    PyObject *f_globals; /* Borrowed reference */
    PyObject *f_builtins; /* Borrowed reference */
    PyObject *f_locals; /* Strong reference, may be NULL */
    void *f_code; /* Strong reference */
    void *frame_obj; /* Strong reference, may be NULL */
    /* Linkage section */
    struct _PyInterpreterFrame311 *previous;
    // NOTE: This is not necessarily the last instruction started in the given
    // frame. Rather, it is the code unit *prior to* the *next* instruction. For
    // example, it may be an inline CACHE entry, an instruction we just jumped
    // over, or (in the case of a newly-created frame) a totally invalid value:
    _Py_CODEUNIT *prev_instr;
    int stacktop;     /* Offset of TOS from localsplus  */
    bool is_entry;  // Whether this is the "root" frame for the current _PyCFrame.
    char owner;
    /* Locals and stack */
    PyObject *localsplus[1];
} _PyInterpreterFrame311;

// https://github.com/python/cpython/blob/3.11/Include/internal/pycore_frame.h
// Note that in 3.11 it's no longer a "PyVarObject".
class PyFrameObject311 : public PyFrameObject {
public:
    PyFrameObject311 *f_back;      /* previous frame, or NULL */
    struct _PyInterpreterFrame311 *f_frame; /* points to the frame data */
    PyObject *f_trace;          /* Trace function */
    int f_lineno;               /* Current line number. Only valid if non-zero */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */
    char f_fast_as_locals;      /* Have the fast locals of this frame been converted to a dict? */
    // It has more things, but we're not interested on those.
};


typedef void (*destructor)(PyObject *);

// 2.4 - 3.7
class PyMethodDef {
public:
    char    *ml_name;    /* The name of the built-in function/method */
};


//
// 2.5 - 3.7
// While these are compatible there are fields only available on later versions.
class PyTypeObject : public PyVarObject {
public:
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    size_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    void *tp_print;
    void *tp_getattr;
    void *tp_setattr;
    union {
        void *tp_compare; /* 2.4 - 3.4 */
        void *tp_as_async; /* 3.5 - 3.7 */
    };
    void *tp_repr;

    /* Method suites for standard classes */

    void *tp_as_number;
    void *tp_as_sequence;
    void *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    void *tp_hash;
    void *tp_call;
    void *tp_str;
    void *tp_getattro;
    void *tp_setattro;

    /* Functions to access object as input/output buffer */
    void *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    void *tp_traverse;

    /* delete references to contained objects */
    void *tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    void *tp_richcompare;

    /* weak reference enabler */
    size_t tp_weaklistoffset;

    /* Added in release 2.2 */
    /* Iterators */
    void *tp_iter;
    void *tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    void *tp_descr_get;
    void *tp_descr_set;
    size_t tp_dictoffset;
    void *tp_init;
    void *tp_alloc;
    void *tp_new;
    void *tp_free; /* Low-level free-memory routine */
    void *tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    void *tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;
};

// 2.4 - 3.7
class PyTupleObject : public PyVarObject {
public:
    PyObject *ob_item[1];

    /* ob_item contains space for 'ob_size' elements.
     * Items must normally not be nullptr, except during construction when
     * the tuple is not yet visible outside the function that builds it.
     */
};

// 2.4 - 3.7
class PyCFunctionObject : public PyObject {
public:
    PyMethodDef *m_ml;      /* Description of the C function to call */
    PyObject    *m_self;    /* Passed as 'self' arg to the C func, can be nullptr */
    PyObject    *m_module;  /* The __module__ attribute, can be anything */
};

typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);

#define PyTrace_CALL 0
#define PyTrace_EXCEPTION 1
#define PyTrace_LINE 2
#define PyTrace_RETURN 3
#define PyTrace_C_CALL 4
#define PyTrace_C_EXCEPTION 5
#define PyTrace_C_RETURN 6

class PyInterpreterState {
};

class PyThreadState { };

class PyThreadState_25_27 : public PyThreadState {
public:
    /* See Python/ceval.c for comments explaining most fields */

    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObjectBaseUpTo39 *frame;
    int recursion_depth;
    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    PyObject *exc_type;
    PyObject *exc_value;
    PyObject *exc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    /* tick_counter is incremented whenever the check_interval ticker
     * reaches zero. The purpose is to give a useful measure of the number
     * of interpreted bytecode instructions in a given thread.  This
     * extremely lightweight statistic collector may be of interest to
     * profilers (like psyco.jit()), although nothing in the core uses it.
     */
    int tick_counter;

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */
    long thread_id; /* Thread id where this tstate was created */

    /* XXX signal handlers should also be here */
    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 2 && (minorVersion >= 5 && minorVersion <= 7);
    }

    static bool IsFor(PythonVersion version) {
        return version >= PythonVersion_25 && version <= PythonVersion_27;
    }
};

class PyThreadState_30_33 : public PyThreadState {
public:
    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObjectBaseUpTo39 *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                        to handle the runtime error. */
    char recursion_critical; /* The current calls must not cause
                                a stack overflow. */
    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    PyObject *exc_type;
    PyObject *exc_value;
    PyObject *exc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    /* tick_counter is incremented whenever the check_interval ticker
     * reaches zero. The purpose is to give a useful measure of the number
     * of interpreted bytecode instructions in a given thread.  This
     * extremely lightweight statistic collector may be of interest to
     * profilers (like psyco.jit()), although nothing in the core uses it.
     */
    int tick_counter;

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */
    long thread_id; /* Thread id where this tstate was created */

    /* XXX signal handlers should also be here */
    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && (minorVersion >= 0 && minorVersion <= 3);
    }

    static bool IsFor(PythonVersion version) {
        return version >= PythonVersion_30 && version <= PythonVersion_33;
    }
};

class PyThreadState_34_36 : public PyThreadState {
public:
    PyThreadState *prev;
    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObjectBaseUpTo39 *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                        to handle the runtime error. */
    char recursion_critical; /* The current calls must not cause
                                a stack overflow. */
    /* 'tracing' keeps track of the execution depth when tracing/profiling.
        This is to prevent the actual trace/profile code from being recorded in
        the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    PyObject *exc_type;
    PyObject *exc_value;
    PyObject *exc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */

    long thread_id; /* Thread id where this tstate was created */
    /* XXX signal handlers should also be here */

    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && minorVersion >= 4 && minorVersion <= 6;
    }

    static bool IsFor(PythonVersion version) {
        return version >= PythonVersion_34 && version <= PythonVersion_36;
    }
};

struct _PyErr_StackItem {
    PyObject *exc_type, *exc_value, *exc_traceback;
    struct _PyErr_StackItem *previous_item;
};


class PyThreadState_37_38 : public PyThreadState {
public:
    PyThreadState *prev;
    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObjectBaseUpTo39 *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                     to handle the runtime error. */
    char recursion_critical; /* The current calls must not cause
                             a stack overflow. */
                             /* 'tracing' keeps track of the execution depth when tracing/profiling.
                             This is to prevent the actual trace/profile code from being recorded in
                             the trace/profile. */
    int stackcheck_counter;

    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    _PyErr_StackItem exc_state;
    _PyErr_StackItem *exc_info;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */

    unsigned long thread_id; /* Thread id where this tstate was created */

    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && (minorVersion == 7 || minorVersion == 8);
    }

    static bool IsFor(PythonVersion version) {
        return version == PythonVersion_37 || version == PythonVersion_38;
    }
};

// i.e.: https://github.com/python/cpython/blob/master/Include/cpython/pystate.h
class PyThreadState_39 : public PyThreadState {
public:
    PyThreadState *prev;
    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObjectBaseUpTo39 *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                     to handle the runtime error. */
    int stackcheck_counter;

    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    _PyErr_StackItem exc_state;
    _PyErr_StackItem *exc_info;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */

    unsigned long thread_id; /* Thread id where this tstate was created */

    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && minorVersion == 9;
    }

    static bool IsFor(PythonVersion version) {
        return version == PythonVersion_39;
    }
};

typedef struct _cframe {
    /* This struct will be threaded through the C stack
     * allowing fast access to per-thread state that needs
     * to be accessed quickly by the interpreter, but can
     * be modified outside of the interpreter.
     *
     * WARNING: This makes data on the C stack accessible from
     * heap objects. Care must be taken to maintain stack
     * discipline and make sure that instances of this struct cannot
     * accessed outside of their lifetime.
     */
    int use_tracing;
    struct _cframe *previous;
} CFrame;

// i.e.: https://github.com/python/cpython/blob/master/Include/cpython/pystate.h
class PyThreadState_310 : public PyThreadState {
public:
    PyThreadState *prev;
    PyThreadState *next;
    PyInterpreterState *interp;

    PyFrameObject310 *frame;
    int recursion_depth;
    int recursion_headroom; /* Allow 50 more calls to handle any errors. */
    int stackcheck_counter;

    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;

    /* Pointer to current CFrame in the C stack frame of the currently,
     * or most recently, executing _PyEval_EvalFrameDefault. */
    CFrame *cframe;


    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    _PyErr_StackItem exc_state;
    _PyErr_StackItem *exc_info;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */

    unsigned long thread_id; /* Thread id where this tstate was created */

    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && minorVersion == 10;
    }

    static bool IsFor(PythonVersion version) {
        return version == PythonVersion_310;
    }
};

// i.e.: https://github.com/python/cpython/blob/3.11/Include/cpython/pystate.h
class PyThreadState_311 : public PyThreadState {
public:
    PyThreadState *prev;
    PyThreadState *next;
    PyInterpreterState *interp;

    int _initialized;

    int _static;
    
    int recursion_remaining;
    int recursion_limit;
    int recursion_headroom; /* Allow 50 more calls to handle any errors. */

    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int tracing_what;

    /* Pointer to current CFrame in the C stack frame of the currently,
     * or most recently, executing _PyEval_EvalFrameDefault. */
    CFrame *cframe;


    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    _PyErr_StackItem *exc_info;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */

    unsigned long thread_id; /* Thread id where this tstate was created */

    static bool IsFor(int majorVersion, int minorVersion) {
        return majorVersion == 3 && minorVersion == 11;
    }

    static bool IsFor(PythonVersion version) {
        return version == PythonVersion_311;
    }
};

class PyIntObject : public PyObject {
public:
    long ob_ival;
};

class Py3kLongObject : public PyVarObject {
public:
    DWORD ob_digit[1];
};

class PyOldStyleClassObject : public PyObject {
public:
    PyObject *cl_bases; /* A tuple of class objects */
    PyObject *cl_dict; /* A dictionary */
    PyObject *cl_name; /* A string */
    /* The following three are functions or nullptr */
    PyObject *cl_getattr;
    PyObject *cl_setattr;
    PyObject *cl_delattr;
};

class PyInstanceObject : public PyObject {
public:
    PyOldStyleClassObject *in_class; /* The class object */
    PyObject *in_dict; /* A dictionary */
    PyObject *in_weakreflist; /* List of weak references */
};

#endif
