// Copyright (C) 2019 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 BASEWRAPPER_H
#define BASEWRAPPER_H

#include "sbkpython.h"
#include "shibokenmacros.h"
#include "sbkmodule.h"
#include "gilstate.h"

#include <vector>
#include <string>

extern "C"
{

struct SbkConverter;
struct SbkObjectPrivate;

/// Base Python object for all the wrapped C++ classes.
struct LIBSHIBOKEN_API SbkObject
{
    PyObject_HEAD
    /// Instance dictionary.
    PyObject *ob_dict;
    /// List of weak references
    PyObject *weakreflist;
    SbkObjectPrivate *d;
};


/// PYSIDE-939: A general replacement for object_dealloc.
LIBSHIBOKEN_API void Sbk_object_dealloc(PyObject *self);

/// Dealloc the python object \p pyObj and the C++ object represented by it.
LIBSHIBOKEN_API void SbkDeallocWrapper(PyObject *pyObj);
LIBSHIBOKEN_API void SbkDeallocQAppWrapper(PyObject *pyObj);
LIBSHIBOKEN_API void SbkDeallocWrapperWithPrivateDtor(PyObject *self);

/// Function signature for the multiple inheritance information initializers that should be provided by classes with multiple inheritance.
using MultipleInheritanceInitFunction = int *(*)(const void *);

/**
 *   Special cast function is used to correctly cast an object when it's
 *   part of a multiple inheritance hierarchy.
 *   The implementation of this function is auto generated by the generator and you don't need to care about it.
 */
using SpecialCastFunction = void *(*)(void *, PyTypeObject *);
using TypeDiscoveryFunc = PyTypeObject *(*)(void *, PyTypeObject *);
using TypeDiscoveryFuncV2 = void *(*)(void *, PyTypeObject *);

// Used in userdata dealloc function
using DeleteUserDataFunc = void (*)(void *);

using ObjectDestructor = void (*)(void *);

using SubTypeInitHook = void (*)(PyTypeObject *, PyObject *, PyObject *);

/// PYSIDE-1019: Set the function to select the current feature.
/// Return value is the previous content.
using SelectableFeatureHook = void (*)(PyTypeObject *);
using SelectableFeatureCallback = void (*)(bool);
LIBSHIBOKEN_API SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func);
LIBSHIBOKEN_API void setSelectableFeatureCallback(SelectableFeatureCallback func);

/// PYSIDE-1626: Enforcing a context switch without further action.
LIBSHIBOKEN_API void SbkObjectType_UpdateFeature(PyTypeObject *type);

/// PYSIDE-1019: Get access to PySide property strings.
LIBSHIBOKEN_API const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type);
LIBSHIBOKEN_API void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings);

/// PYSIDE-1735: Store the enumFlagInfo.
LIBSHIBOKEN_API void SbkObjectType_SetEnumFlagInfo(PyTypeObject *type, const char **strings);

/// PYSIDE-1470: Set the function to kill a Q*Application.
using DestroyQAppHook = void(*)();
LIBSHIBOKEN_API void setDestroyQApplication(DestroyQAppHook func);

/// PYSIDE-535: Use the C API in PyPy instead of `op->ob_dict`, directly (borrowed ref)
LIBSHIBOKEN_API PyObject *SbkObject_GetDict_NoRef(PyObject *op);

extern LIBSHIBOKEN_API PyTypeObject *SbkObjectType_TypeF(void);
extern LIBSHIBOKEN_API PyTypeObject *SbkObject_TypeF(void);


struct SbkObjectTypePrivate;
/// PyTypeObject extended with C++ multiple inheritance information.

LIBSHIBOKEN_API PyObject *SbkObject_tp_new(PyTypeObject *subtype, PyObject *, PyObject *);

/// The special case of a switchable singleton Q*Application.
LIBSHIBOKEN_API PyObject *SbkQApp_tp_new(PyTypeObject *subtype, PyObject *, PyObject *);

/// Create a new Q*Application wrapper and monitor it.
LIBSHIBOKEN_API PyObject *MakeQAppWrapper(PyTypeObject *type);

/**
 *  PYSIDE-832: Use object_dealloc instead of nullptr.
 *
 *  When moving to heaptypes, we were struck by a special default behavior of
 *  PyType_FromSpec that inserts subtype_dealloc when tp_dealloc is
 *  nullptr. But the default before conversion to heaptypes was to assign
 *  object_dealloc. This seems to be a bug in the Limited API.
 */
/// PYSIDE-939: Replaced by Sbk_object_dealloc.
LIBSHIBOKEN_API PyObject *SbkDummyNew(PyTypeObject *type, PyObject *, PyObject *);

/// PYSIDE-74: Fallback used in all types now.
LIBSHIBOKEN_API PyObject *FallbackRichCompare(PyObject *self, PyObject *other, int op);

/// PYSIDE-1970: Be easily able to see what is happening in the running code.
LIBSHIBOKEN_API void disassembleFrame(const char *marker);

/// PYSIDE-2230: Check if an object is an SbkObject.
LIBSHIBOKEN_API bool SbkObjectType_Check(PyTypeObject *type);

/// PYSIDE-2701: Some improvements from folding optimizations.
LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_None();
LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult);
LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self);
} // extern "C"

LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject,
                                            Shiboken::GilState &gil, const char *funcName,
                                            PyObject *&resultCache, PyObject **nameCache);

namespace Shiboken
{

/**
*   Init shiboken library.
*/
LIBSHIBOKEN_API void init();

/// PYSIDE-1415: Publish Shiboken objects.
LIBSHIBOKEN_API void initShibokenSupport(PyObject *module);

/// Delete the class T allocated on \p cptr.
template<typename T>
void callCppDestructor(void *cptr)
{
    delete reinterpret_cast<T *>(cptr);
}

/// setErrorAboutWrongArguments now gets overload information from the signature module.
/// The extra info argument can contain additional data about the error.
LIBSHIBOKEN_API void setErrorAboutWrongArguments(PyObject *args, const char *funcName,
                                                 PyObject *info, const char *className = nullptr);

/// Return values for the different return variants.
/// This is used instead of goto.
/// Either funcname should contain the full function name, or the module and class
/// are taken from the TypeInitStruct.
LIBSHIBOKEN_API PyObject *returnWrongArguments(PyObject *args, const char *funcName, PyObject *info,
                                               Module::TypeInitStruct initStruct = {nullptr, nullptr});

LIBSHIBOKEN_API int returnWrongArguments_Zero(PyObject *args, const char *funcName, PyObject *info,
                                              Module::TypeInitStruct initStruct = {nullptr, nullptr});

LIBSHIBOKEN_API int returnWrongArguments_MinusOne(PyObject *args, const char *funcName, PyObject *info,
                                                  Module::TypeInitStruct initStruct = {nullptr, nullptr});

/// A simple special version for the end of rich comparison.
LIBSHIBOKEN_API PyObject *returnFromRichCompare(PyObject *result);

// Return error information object if the argument count is wrong
LIBSHIBOKEN_API PyObject *checkInvalidArgumentCount(Py_ssize_t numArgs,
                                                    Py_ssize_t minArgs,
                                                    Py_ssize_t maxArgs);

namespace ObjectType {

/**
*   Returns true if the object is an instance of a type created by the Shiboken generator.
*/
LIBSHIBOKEN_API bool checkType(PyTypeObject *pyObj);

/**
*   Returns true if this object is an instance of an user defined type derived from an Shiboken type.
*/
LIBSHIBOKEN_API bool isUserType(PyTypeObject *pyObj);

/**
*   Returns true if the constructor of \p ctorType can be called for a instance of type \p myType.
*   \note This function set a python error when returning false.
*/
LIBSHIBOKEN_API bool canCallConstructor(PyTypeObject *myType, PyTypeObject *ctorType);

/**
 *  Tells if the \p type represents an object of a class with multiple inheritance in C++.
 *  When this occurs, the C++ pointer held by the Python wrapper will need to be cast when
 *  passed as a parameter that expects a type of its ancestry.
 *  \returns    true if a call to ObjectType::cast() is needed to obtain the correct
 *              C++ pointer for Python objects of type \p type.
 */
LIBSHIBOKEN_API bool hasCast(PyTypeObject *type);
/**
 *  Cast the C++ pointer held by a Python object \p obj of type \p sourceType,
 *  to a C++ pointer of a C++ class indicated by type \p targetType.
 *  \returns    The cast C++ pointer.
 */
LIBSHIBOKEN_API void *cast(PyTypeObject *sourceType, SbkObject *obj, PyTypeObject *targetType);
/// Set the C++ cast function for \p type.
LIBSHIBOKEN_API void setCastFunction(PyTypeObject *type, SpecialCastFunction func);

LIBSHIBOKEN_API void setOriginalName(PyTypeObject *self, const char *name);
LIBSHIBOKEN_API const char *getOriginalName(PyTypeObject *self);

LIBSHIBOKEN_API void setTypeDiscoveryFunctionV2(PyTypeObject *self, TypeDiscoveryFuncV2 func);
LIBSHIBOKEN_API void copyMultipleInheritance(PyTypeObject *self, PyTypeObject *other);
LIBSHIBOKEN_API void setMultipleInheritanceFunction(PyTypeObject *self, MultipleInheritanceInitFunction func);
LIBSHIBOKEN_API MultipleInheritanceInitFunction getMultipleInheritanceFunction(PyTypeObject *type);

LIBSHIBOKEN_API void setDestructorFunction(PyTypeObject *self, ObjectDestructor func);

enum WrapperFlags
{
    InnerClass = 0x1,
    DeleteInMainThread = 0x2,
    Value = 0x4,
    InternalWrapper = 0x8
};

/**
 *  Initializes a Shiboken wrapper type and adds it to the module,
 *  or to the enclosing class if the type is an inner class.
 *  This function also calls setDestructorFunction.
 *  \param enclosingObject  The module or enclosing class to where the new \p type will be added.
 *  \param typeName         Name by which the type will be known in Python.
 *  \param originalName     Original C++ name of the type.
 *  \param type             The new type to be initialized and added to the module.
 *  \param cppObjDtor       Memory deallocation function for the C++ object held by \p type.
 *                          Should not be used if the underlying C++ class has a private destructor.
 *  \param baseType         Base type from whom the new \p type inherits.
 *  \param baseTypes        Other base types from whom the new \p type inherits.
 *  \param isInnerClass     Tells if the new \p type is an inner class (the default is that it isn't).
 *                          If false then the \p enclosingObject is a module, otherwise it is another
 *                          wrapper type.
 *  \returns                true if the initialization went fine, false otherwise.
 */
LIBSHIBOKEN_API PyTypeObject *introduceWrapperType(PyObject *enclosingObject,
                                                   const char *typeName,
                                                   const char *originalName,
                                                   PyType_Spec *typeSpec,
                                                   ObjectDestructor cppObjDtor,
                                                   PyObject *bases,
                                                   unsigned wrapperFlags = 0);

/**
 *  Set the subtype init hook for a type.
 *
 *  This hook will be invoked every time the user creates a sub-type inherited from a Shiboken based type.
 *  The hook gets 3 params, they are: The new type being created, args and kwds. The last two are the very
 *  same got from tp_new.
 */
LIBSHIBOKEN_API void setSubTypeInitHook(PyTypeObject *self, SubTypeInitHook func);

/**
 *  Get the user data previously set by Shiboken::Object::setTypeUserData
 */
LIBSHIBOKEN_API void *getTypeUserData(PyTypeObject *self);
LIBSHIBOKEN_API void setTypeUserData(PyTypeObject *self, void *userData, DeleteUserDataFunc d_func);

/**
 * Return an instance of PyTypeObject for a C++ type name as determined by
 * typeinfo().name().
 * \param typeName Type name
 * \since 5.12
 */
LIBSHIBOKEN_API PyTypeObject *typeForTypeName(const char *typeName);

/**
 *  Returns whether PyTypeObject has a special cast function (multiple inheritance)
 * \param sbkType Sbk type
 * \since 5.12
 */
LIBSHIBOKEN_API bool hasSpecialCastFunction(PyTypeObject *sbkType);

/// Returns whether a C++ pointer of \p baseType can be safely downcast
/// to \p targetType (base is a direct, single line base class of targetType).
/// (is a direct, single-line inheritance)
/// \param baseType   Python type of base class
/// \param targetType Python type of derived class
/// \since 6.8
LIBSHIBOKEN_API bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType);
}

namespace Object {

/**
 *  Returns a string with information about the internal state of the instance object, useful for debug purposes.
 */
LIBSHIBOKEN_API std::string info(SbkObject *self);

/**
*   Returns true if the object is an instance of a type created by the Shiboken generator.
*/
LIBSHIBOKEN_API bool checkType(PyObject *pyObj);

/**
 *  Returns true if this object type is an instance of an user defined type derived from an Shiboken type.
 *  \see Shiboken::ObjectType::isUserType
 */
LIBSHIBOKEN_API bool isUserType(PyObject *pyObj);

/**
 *  Generic function used to make ObjectType hashable, the C++ pointer is used as hash value.
 */
LIBSHIBOKEN_API Py_hash_t hash(PyObject *pyObj);

/**
 * Find a child of given wrapper having same address having the specified type.
 */
LIBSHIBOKEN_API SbkObject *findColocatedChild(SbkObject *wrapper,
                                              const PyTypeObject *instanceType);

/**
 *  Bind a C++ object to Python. Forwards to
 *  newObjectWithHeuristics(), newObjectForType() depending on \p isExactType.
 * \param instanceType equivalent Python type for the C++ object.
 * \param hasOwnership if true, Python will try to delete the underlying C++ object when there's no more refs.
 * \param isExactType if false, Shiboken will use some heuristics to detect the correct Python type of this C++
 *                    object, in any case you must provide \p instanceType, it'll be used as search starting point
 *                    and as fallback.
 * \param typeName    If non-null, this will be used as helper to find the correct Python type for this object.
 */
LIBSHIBOKEN_API PyObject *newObject(PyTypeObject *instanceType,
                                    void *cptr,
                                    bool hasOwnership = true,
                                    bool isExactType = false,
                                    const char *typeName = nullptr);

/// Bind a C++ object to Python for polymorphic pointers. Calls
/// newObjectWithHeuristics() with an additional check for multiple
/// inheritance, in which case it will fall back to instanceType.
/// \param instanceType Equivalent Python type for the C++ object.
/// \param hasOwnership if true, Python will try to delete the underlying C++ object
///                     when there's no more refs.
/// \param typeName     If non-null, this will be used as helper to find the correct
///                     Python type for this object (obtained by typeid().name().
LIBSHIBOKEN_API PyObject *newObjectForPointer(PyTypeObject *instanceType,
                                              void *cptr,
                                              bool hasOwnership = true,
                                              const char *typeName = nullptr);

/// Bind a C++ object to Python using some heuristics to detect the correct
/// Python type of this C++ object. In any case \p instanceType must be provided;
/// it'll be used as search starting point and as fallback.
/// \param instanceType Equivalent Python type for the C++ object.
/// \param hasOwnership if true, Python will try to delete the underlying C++ object
///                     C++ object when there are no more references.
///                     when there's no more refs.
/// \param typeName     If non-null, this will be used as helper to find the correct
///                     Python type for this object (obtained by typeid().name().
LIBSHIBOKEN_API PyObject *newObjectWithHeuristics(PyTypeObject *instanceType,
                                                  void *cptr,
                                                  bool hasOwnership = true,
                                                  const char *typeName = nullptr);

/// Bind a C++ object to Python using the given type.
/// \param instanceType Equivalent Python type for the C++ object.
/// \param hasOwnership if true, Python will try to delete the underlying
///                     C++ object when there are no more references.
LIBSHIBOKEN_API PyObject *newObjectForType(PyTypeObject *instanceType,
                                           void *cptr, bool hasOwnership = true);

/**
 *  Changes the valid flag of a PyObject, invalid objects will raise an exception when someone tries to access it.
 */
LIBSHIBOKEN_API void setValidCpp(SbkObject *pyObj, bool value);
/**
 *  Tells shiboken the Python object \p pyObj has a C++ wrapper used to intercept virtual method calls.
 */
LIBSHIBOKEN_API void setHasCppWrapper(SbkObject *pyObj, bool value);
/**
 *  Return true if the Python object \p pyObj has a C++ wrapper used to intercept virtual method calls.
 */
LIBSHIBOKEN_API bool hasCppWrapper(SbkObject *pyObj);

/**
 *  Return true if the Python object was created by Python, false otherwise.
 *  \note This function was added to libshiboken only to be used by shiboken.wasCreatedByPython()
 */
LIBSHIBOKEN_API bool wasCreatedByPython(SbkObject *pyObj);

/**
 *  Call the C++ object destructor and invalidates the Python object.
 *  \note This function was added to libshiboken only to be used by shiboken.delete()
 */
LIBSHIBOKEN_API void callCppDestructors(SbkObject *pyObj);

/**
 *  Return true if the Python is responsible for deleting the underlying C++ object.
 */
LIBSHIBOKEN_API bool hasOwnership(SbkObject *pyObj);

/**
 *  Sets python as responsible to delete the underlying C++ object.
 *  \note You this overload only when the PyObject can be a sequence and you want to
 *  call this function for every item in the sequence.
 *  \see getOwnership(SbkObject *)
 */
LIBSHIBOKEN_API void getOwnership(PyObject *pyObj);

/**
 *  Sets python as responsible to delete the underlying C++ object.
 */
LIBSHIBOKEN_API void getOwnership(SbkObject *pyObj);

/**
 *  Release the ownership, so Python will not delete the underlying C++ object.
 *  \note You this overload only when the PyObject can be a sequence and you want to
 *  call this function for every item in the sequence.
 *  \see releaseOwnership(SbkObject *)
 */
LIBSHIBOKEN_API void releaseOwnership(PyObject *pyObj);
/**
 *  Release the ownership, so Python will not delete the underlying C++ object.
 */
LIBSHIBOKEN_API void releaseOwnership(SbkObject *pyObj);

/**
 *   Get the C++ pointer of type \p desiredType from a Python object.
 */
LIBSHIBOKEN_API void *cppPointer(SbkObject *pyObj, PyTypeObject *desiredType);

/**
 *   Return a list with all C++ pointers held from a Python object.
 *   \note This function was added to libshiboken only to be used by shiboken.getCppPointer()
 */
LIBSHIBOKEN_API std::vector<void *>cppPointers(SbkObject *pyObj);

/**
 *   Set the C++ pointer of type \p desiredType of a Python object.
 */
LIBSHIBOKEN_API bool setCppPointer(SbkObject *sbkObj, PyTypeObject *desiredType, void *cptr);

/**
 * Returns false and sets a Python RuntimeError if the Python wrapper is not marked as valid.
 */
LIBSHIBOKEN_API bool isValid(PyObject *pyObj);

/**
 * Returns false if the Python wrapper is not marked as valid.
 * \param pyObj the object.
 * \param throwPyError sets a Python RuntimeError when the object isn't valid.
 */
LIBSHIBOKEN_API bool isValid(SbkObject *pyObj, bool throwPyError = true);

/**
 * Returns false if the Python wrapper is not marked as valid.
 * \param pyObj the object.
 * \param throwPyError sets a Python RuntimeError when the object isn't valid.
 */
LIBSHIBOKEN_API bool isValid(PyObject *pyObj, bool throwPyError);

/**
*   Set the parent of \p child to \p parent.
*   When an object dies, all their children, grandchildren, etc, are tagged as invalid.
*   \param parent the parent object, if null, the child will have no parents.
*   \param child the child.
*/
LIBSHIBOKEN_API void setParent(PyObject *parent, PyObject *child);

/**
*   Remove this child from their parent, if any.
*   \param child the child.
*/
LIBSHIBOKEN_API void removeParent(SbkObject *child, bool giveOwnershipBack = true, bool keepReferenc = false);

/**
 * Mark the object as invalid
 */
LIBSHIBOKEN_API void invalidate(SbkObject *self);

/**
 * Help function can be used to invalidate a sequence of object
 **/
LIBSHIBOKEN_API void invalidate(PyObject *pyobj);

/**
 * Make the object valid again
 */
LIBSHIBOKEN_API void makeValid(SbkObject *self);

/**
 * Destroy any data in Shiboken structure and c++ pointer if the pyboject has the ownership
 */
LIBSHIBOKEN_API void destroy(SbkObject *self, void *cppData);

/**
 *  Set user data on type of \p wrapper.
 *  \param wrapper instance object, the user data will be set on his type
 *  \param userData the user data
 *  \param d_func a function used to delete the user data
 */
LIBSHIBOKEN_API void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_func);
/**
 *  Get the user data previously set by Shiboken::Object::setTypeUserData
 */
LIBSHIBOKEN_API void *getTypeUserData(SbkObject *wrapper);

/**
 *   Increments the reference count of the referred Python object.
 *   A previous Python object in the same position identified by the 'key' parameter
 *   will have its reference counter decremented automatically when replaced.
 *   All the kept references should be decremented when the Python wrapper indicated by
 *   'self' dies.
 *   No checking is done for any of the passed arguments, since it is meant to be used
 *   by generated code it is supposed that the generator is correct.
 *   \param self            the wrapper instance that keeps references to other objects.
 *   \param key             a key that identifies the C++ method signature and argument where the referred Object came from.
 *   \param referredObject  the object whose reference is used by the self object.
 */
LIBSHIBOKEN_API void keepReference(SbkObject *self, const char *key, PyObject *referredObject, bool append = false);

/**
 *   Removes any reference previously added by keepReference function
 *   \param self            the wrapper instance that keeps references to other objects.
 *   \param key             a key that identifies the C++ method signature and argument from where the referred Object came.
 *   \param referredObject  the object whose reference is used by the self object.
 */
LIBSHIBOKEN_API void removeReference(SbkObject *self, const char *key, PyObject *referredObject);

} // namespace Object

} // namespace Shiboken

#endif // BASEWRAPPER_H
