// Copyright (C) 2016 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

#include "pyside.h"
#include "pysideinit.h"
#include "pysidecleanup.h"
#include "pysidemetatype.h"
#include "pysideqapp.h"
#include "pysideqobject.h"
#include "pysideutils.h"
#include "pyside_p.h"
#include "signalmanager.h"
#include "pysideclassinfo_p.h"
#include "pysideproperty_p.h"
#include "class_property.h"
#include "pysideproperty.h"
#include "pysidesignal.h"
#include "pysidesignal_p.h"
#include "pysidestaticstrings.h"
#include "pysideslot_p.h"
#include "pysidemetafunction_p.h"
#include "pysidemetafunction.h"
#include "dynamicqmetaobject.h"
#include "feature_select.h"
#include "pysidelogging_p.h"

#include <autodecref.h>
#include <basewrapper.h>
#include <bindingmanager.h>
#include <gilstate.h>
#include <sbkconverter.h>
#include <sbkstring.h>
#include <sbkstaticstrings.h>
#include <sbkfeature_base.h>
#include <sbkmodule.h>

#include <QtCore/QByteArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QMetaMethod>
#include <QtCore/QMutex>
#include <QtCore/QStack>
#include <QtCore/QThread>
#include <QtCore/private/qobject_p.h>

#include <algorithm>
#include <cstring>
#include <cctype>
#include <memory>
#include <optional>
#include <typeinfo>

#ifdef Q_OS_WIN
#  include <conio.h>
#else
#  include <QtCore/QDeadlineTimer>
#  include <QtCore/private/qcore_unix_p.h>
#endif

using namespace Qt::StringLiterals;

static QStack<PySide::CleanupFunction> cleanupFunctionList;
static void *qobjectNextAddr;

QT_BEGIN_NAMESPACE
extern bool qRegisterResourceData(int, const unsigned char *, const unsigned char *,
        const unsigned char *);
QT_END_NAMESPACE

Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg)

static QObjectData *qt_object_private(const QObject *o)
{
    class FriendlyQObject : public QObject {
    public:
        using QObject::d_ptr;
    };
    return static_cast<const FriendlyQObject *>(o)->d_ptr.data();
}

static bool hasDynamicMetaObject(const QObject *o)
{
    return qt_object_private(o)->metaObject != nullptr;
}

namespace PySide
{

void init(PyObject *module)
{
    qobjectNextAddr = nullptr;
    ClassInfo::init(module);
    Signal::init(module);
    Slot::init(module);
    Property::init(module);
    ClassProperty::init(module);
    MetaFunction::init(module);
    // Init signal manager, so it will register some meta types used by QVariant.
    SignalManager::init();
    initQApp();
}

static const QByteArray _sigWithMangledName(const QByteArray &signature, bool mangle)
{
    if (!mangle)
        return signature;
    auto bracePos = signature.indexOf('(');
    auto limit = bracePos >= 0 ? bracePos : signature.size();
    if (limit < 3)
        return signature;
    QByteArray result;
    result.reserve(signature.size() + 4);
    for (auto i = 0; i < limit; ++i) {
        const char c = signature.at(i);
        if (std::isupper(c)) {
            if (i > 0) {
                if (std::isupper(signature.at(i - 1)))
                    return signature; // Give up at consecutive upper chars
                 result.append('_');
            }
            result.append(std::tolower(c));
        } else {
            result.append(c);
        }
    }
    // Copy the rest after the opening brace (if any)
    result.append(signature.mid(limit));
    return result;
}

static const QByteArray _sigWithOrigName(const QByteArray &signature, bool mangle)
{
    if (!mangle)
        return signature;
    auto bracePos = signature.indexOf('(');
    auto limit = bracePos >= 0 ? bracePos : signature.size();
    QByteArray result;
    result.reserve(signature.size());
    for (auto i = 0; i < limit; ++i) {
        const char c = signature.at(i);
        if (std::isupper(c)) {
            if (i > 0) {
                if (std::isupper(signature.at(i - 1)))
                    return signature;       // Give up at consecutive upper chars
                return QByteArray{};        // Error, this was not converted!
            }
        }
        if (std::islower(c) && i > 0 && signature.at(i - 1) == '_') {
            result.chop(1);
            result.append(std::toupper(c));
        } else {
            result.append(c);
        }
    }
    // Copy the rest after the opening brace (if any)
    result.append(signature.mid(limit));
    return result;
}

/*****************************************************************************
 *
 * How do we find a property?
 * --------------------------
 *
 * There are methods which are truly parts of properties, and there are
 * other property-like methods which are not. True properties can be
 * found by inspecting `SbkObjectType_GetPropertyStrings(type)`.
 *
 * Pseudo-properties have only a getter and a setter, and we must assume that
 * the name of the getter is the property name, and the name of the setter
 * is the uppercase of the getter with "set" prepended.
 *
 * We first walk the mro and search the property name and get the setter
 * name. If that doesn't work, we use the heuristics for the setter.
 * We then do the final mro lookup.
 *
 * Note that the true property lists have the original names, while the
 * dict entries in the mro are already mangled.
 */

static const QByteArrayList parseFields(const char *propStr, int flags, bool *stdWrite)
{
    /*
     * Break the string into subfields at ':' and add defaults.
     */
    if (stdWrite)
        *stdWrite = true;
    QByteArray s = QByteArray(propStr);
    auto list = s.split(':');
    assert(list.size() == 2 || list.size() == 3);
    auto name = list[0];
    auto read = list[1];
    if (read.isEmpty())
        list[1] = name;
    if (list.size() == 2)
        return list;
    auto write = list[2];
    if (stdWrite)
        *stdWrite = write.isEmpty();
    if (write.isEmpty()) {
        auto snake_flag = flags & 0x01;
        if (snake_flag) {
            list[2] = ("set_") + name;
        } else {
            list[2] = QByteArray("set") + name;
            list[2][3] = std::toupper(list[2][3]);
        }
    }
    return list;
}

static QByteArrayList _SbkType_LookupProperty(PyTypeObject *type,
                                              const QByteArray &name, int flags)
{
    /*
     * Looks up a property and returns all fields.
     */
    int snake_flag = flags & 0x01;
    QByteArray origName(_sigWithOrigName(name, snake_flag));
    if (origName.isEmpty())
        return QByteArrayList{};
    PyObject *mro = type->tp_mro;
    auto n = PyTuple_Size(mro);
    auto len = std::strlen(origName);
    for (Py_ssize_t idx = 0; idx < n; idx++) {
        auto *base = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
        if (!SbkObjectType_Check(base))
            continue;
        auto *props = SbkObjectType_GetPropertyStrings(base);
        if (props == nullptr || *props == nullptr)
            continue;
        for (; *props != nullptr; ++props) {
            QByteArray propStr(*props);
            if (std::strncmp(propStr, origName, len) == 0) {
                if (propStr[len] != ':')
                    continue;
                // We found the property. Return the parsed fields.
                propStr = _sigWithMangledName(propStr, snake_flag);
                return parseFields(propStr, flags, nullptr);
            }
        }
    }
    return QByteArrayList{};
}

static QByteArrayList _SbkType_FakeProperty(const QByteArray &name, int flags)
{
    /*
     * Handle a pseudo.property and return all fields.
     */
    int snake_flag = flags & 0x01;
    QByteArray propStr(name);
    propStr += "::";
    propStr = _sigWithMangledName(propStr, snake_flag);
    return parseFields(propStr, snake_flag, nullptr);
}

static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool *accept)
{
    using Shiboken::AutoDecRef;

    QByteArray propName(Shiboken::String::toCString(name));
    auto *type = Py_TYPE(qObj);
    int flags = currentSelectId(type);
    int prop_flag = flags & 0x02;
    auto found = false;
    QByteArray getterName;
    QByteArray setterName;

    auto fields = _SbkType_LookupProperty(type, propName, flags);
    if (!fields.isEmpty()) {
        found = true;
        bool haveWrite = fields.size() == 3;
        if (!haveWrite)
            return false;
    } else {
        fields = _SbkType_FakeProperty(propName, flags);
    }

    propName = fields[0];
    getterName = fields[1];
    setterName = fields[2];

    // PYSIDE-1702: We do not use getattr, since that could trigger an action
    //              if we have a true property. Better to look inside the mro.
    //              That should return a descriptor or a property.
    PyObject *look{};

    if (found && prop_flag) {
        // We have a property, and true_property is active.
        // There must be a property object and we use it's fset.
        AutoDecRef pyPropName(Shiboken::String::fromCString(propName.constData()));
        look = _PepType_Lookup(Py_TYPE(qObj), pyPropName);
    } else {
        // We have a pseudo property or true_property is off, looking for a setter.
        AutoDecRef pySetterName(Shiboken::String::fromCString(setterName.constData()));
        look = _PepType_Lookup(Py_TYPE(qObj), pySetterName);
    }

    if (look) {
        AutoDecRef propSetter{};
        static PyObject *magicGet = Shiboken::PyMagicName::get();
        if (found && prop_flag) {
            // the indirection of the setter descriptor in a true property
            AutoDecRef descr(PyObject_GetAttr(look, PySideName::fset()));
            propSetter.reset(PyObject_CallMethodObjArgs(descr, magicGet, qObj, nullptr));
        } else {
            // look is already the descriptor
            propSetter.reset(PyObject_CallMethodObjArgs(look, magicGet, qObj, nullptr));
        }
        *accept = true;
        AutoDecRef args(PyTuple_Pack(1, value));
        AutoDecRef retval(PyObject_CallObject(propSetter, args));
        if (retval.isNull())
            return false;
    } else {
        PyErr_Clear();
        AutoDecRef attr(PyObject_GenericGetAttr(qObj, name));
        if (PySide::Property::checkType(attr)) {
            *accept = true;
            if (PySide::Property::setValue(reinterpret_cast<PySideProperty *>(
                    attr.object()), qObj, value) < 0)
                return false;
        }
    }
    return true;
}

// PYSIDE-2329: Search a signal by name (Note: QMetaObject::indexOfSignal()
// searches by signature).
static std::optional<QMetaMethod> findSignal(const QMetaObject *mo,
                                             const QByteArray &name)
{
    const auto count = mo->methodCount();
    for (int i = mo->methodOffset(); i < count; ++i) {
        const auto method = mo->method(i);
        if (method.methodType() == QMetaMethod::Signal && method.name() == name)
            return method;
    }
    const auto *base = mo->superClass();
    return base != nullptr ? findSignal(base, name) : std::nullopt;
}

bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj,
                      PyObject *kwds, bool allowErrors)
{

    PyObject *key{};
    PyObject *value{};
    Py_ssize_t pos = 0;
    int flags = currentSelectId(Py_TYPE(qObj));
    int snake_flag = flags & 0x01;

    while (PyDict_Next(kwds, &pos, &key, &value)) {
        const QByteArray propName = Shiboken::String::toCString(key);
        QByteArray unmangledName = _sigWithOrigName(propName, snake_flag);
        bool accept = false;
        // PYSIDE-1705: Make sure that un-mangled names are not recognized in snake_case mode.
        if (!unmangledName.isEmpty()) {
            if (metaObj->indexOfProperty(unmangledName) != -1) {
                if (!_setProperty(qObj, key, value, &accept))
                    return false;
            } else {
                const auto methodO = findSignal(metaObj, propName);
                if (methodO.has_value()) {
                    const auto signature = "2"_ba + methodO->methodSignature();
                    accept = true;
                    if (!PySide::Signal::connect(qObj, signature, value))
                        return false;
                }
            }
            if (!accept) {
                // PYSIDE-1019: Allow any existing attribute in the constructor.
                if (!_setProperty(qObj, key, value, &accept))
                    return false;
            }
        }
        if (allowErrors) {
            PyErr_Clear();
            continue;
        }
        if (!accept) {
            PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal",
                         propName.constData());
            return false;
        }
    }
    return true;
}

void registerCleanupFunction(CleanupFunction func)
{
    cleanupFunctionList.push(func);
}

void runCleanupFunctions()
{
    while (!cleanupFunctionList.isEmpty()) {
        CleanupFunction f = cleanupFunctionList.pop();
        f();
    }
}

static void destructionVisitor(SbkObject *pyObj, void *data)
{
    auto *realData = reinterpret_cast<void **>(data);
    auto *pyQApp = reinterpret_cast<SbkObject *>(realData[0]);
    auto *pyQObjectType = reinterpret_cast<PyTypeObject *>(realData[1]);

    if (pyObj != pyQApp && PyObject_TypeCheck(pyObj, pyQObjectType)) {
        if (Shiboken::Object::hasOwnership(pyObj) && Shiboken::Object::isValid(pyObj, false)) {
            Shiboken::Object::setValidCpp(pyObj, false);

            Py_BEGIN_ALLOW_THREADS
            Shiboken::callCppDestructor<QObject>(Shiboken::Object::cppPointer(pyObj, pyQObjectType));
            Py_END_ALLOW_THREADS
        }
    }

};

void destroyQCoreApplication()
{
    QCoreApplication *app = QCoreApplication::instance();
    if (!app)
        return;

    Shiboken::BindingManager &bm = Shiboken::BindingManager::instance();
    SbkObject *pyQApp = bm.retrieveWrapper(app);
    PyTypeObject *pyQObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*");
    assert(pyQObjectType);

    void *data[2] = {pyQApp, pyQObjectType};
    bm.visitAllPyObjects(&destructionVisitor, &data);

    // in the end destroy app
    // Allow threads because the destructor calls
    // QThreadPool::globalInstance().waitForDone() which may deadlock on the GIL
    // if there is a worker working with python objects.
    Py_BEGIN_ALLOW_THREADS
    delete app;
    Py_END_ALLOW_THREADS
    // PYSIDE-571: make sure to create a singleton deleted qApp.
    Py_DECREF(MakeQAppWrapper(nullptr));
}

std::size_t getSizeOfQObject(PyTypeObject *type)
{
    return retrieveTypeUserData(type)->cppObjSize;
}

void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::size_t cppObjSize)
{
    //create DynamicMetaObject based on python type
    auto *userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize);
    userData->mo.update();
    Shiboken::ObjectType::setTypeUserData(type, userData, Shiboken::callCppDestructor<TypeUserData>);

    //initialize staticQMetaObject property
    void *metaObjectPtr = const_cast<QMetaObject *>(userData->mo.update());
    static SbkConverter *converter = Shiboken::Conversions::getConverter("QMetaObject");
    if (!converter)
        return;
    Shiboken::AutoDecRef pyMetaObject(Shiboken::Conversions::pointerToPython(converter, metaObjectPtr));
    PyObject_SetAttr(reinterpret_cast<PyObject *>(type),
                     Shiboken::PyName::qtStaticMetaObject(), pyMetaObject);
}

TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj)
{
    if (!SbkObjectType_Check(pyTypeObj))
        return nullptr;
    return reinterpret_cast<TypeUserData *>(Shiboken::ObjectType::getTypeUserData(pyTypeObj));
}

TypeUserData *retrieveTypeUserData(PyObject *pyObj)
{
    auto *pyTypeObj = PyType_Check(pyObj)
        ? reinterpret_cast<PyTypeObject *>(pyObj) : Py_TYPE(pyObj);
    return retrieveTypeUserData(pyTypeObj);
}

const QMetaObject *retrieveMetaObject(PyTypeObject *pyTypeObj)
{
    TypeUserData *userData = retrieveTypeUserData(pyTypeObj);
    return userData ? userData->mo.update() : nullptr;
}

const QMetaObject *retrieveMetaObject(PyObject *pyObj)
{
    auto *pyTypeObj = PyType_Check(pyObj)
        ? reinterpret_cast<PyTypeObject *>(pyObj) : Py_TYPE(pyObj);
    return retrieveMetaObject(pyTypeObj);
}

void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds */)
{
    PyTypeObject *qObjType = Shiboken::Conversions::getPythonTypeObject("QObject*");

    PyObject *bases = PyTuple_GetItem(args, 1);

    TypeUserData *userData = nullptr;

    for (Py_ssize_t i = 0, numBases = PyTuple_Size(bases); i < numBases; ++i) {
        auto *base = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(bases, i));
        if (PyType_IsSubtype(base, qObjType)) {
            userData = retrieveTypeUserData(base);
            break;
        }
    }
    if (!userData) {
        const char *className = Shiboken::String::toCString(PyTuple_GetItem(args, 0));
        qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.",
                 className);
        return;
    }
    // PYSIDE-1463: Don't change feature selection durin subtype initialization.
    // This behavior is observed with PySide 6.
    PySide::Feature::Enable(false);
    initDynamicMetaObject(type, userData->mo.update(), userData->cppObjSize);
    PySide::Feature::Enable(true);
}

extern "C" {
static int qAppInputHook()
{
    auto *app = QCoreApplication::instance();
    if (app == nullptr || app->thread() != QThread::currentThread())
        return 0;
#ifndef Q_OS_WIN
    // Check for press on stdin (file descriptor 0)
    pollfd stdinPfd =  qt_make_pollfd(0, POLLIN);
    while (qt_safe_poll(&stdinPfd, 1, QDeadlineTimer{1}) == 0)
        QCoreApplication::processEvents({}, 50000);
#else
    while (_kbhit() == 0)
        QCoreApplication::processEvents({}, 50000);
#endif
    return 0;
}
} // extern "C"

static void unregisterQAppInputHook()
{
    PyOS_InputHook = nullptr;
}

static void registerQAppInputHook()
{
    PyOS_InputHook = qAppInputHook;
    qAddPostRoutine(unregisterQAppInputHook);
}

void initQApp()
{
    /*
     * qApp will not be initialized when embedding is active.
     * That means that qApp exists already when PySide is initialized.
     * We could solve that by creating a qApp variable, but in embedded
     * mode, we also have the effect that the first assignment to qApp
     * is persistent! Therefore, we can never be sure to have created
     * qApp late enough to get the right type for the instance.
     *
     * I would appreciate very much if someone could explain or even fix
     * this issue. It exists only when a pre-existing application exists.
     */
    if (qApp == nullptr) {
        registerQAppInputHook();
        Py_DECREF(MakeQAppWrapper(nullptr));
    }

    // PYSIDE-1470: Register a function to destroy an application from shiboken.
    setDestroyQApplication(destroyQCoreApplication);
}

PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name)
{
    using Shiboken::AutoDecRef;

    // PYSIDE-68-bis: This getattr finds signals early by `signalDescrGet`.
    PyObject *attr = PyObject_GenericGetAttr(self, name);
    if (!Shiboken::Object::isValid(reinterpret_cast<SbkObject *>(self), false))
        return attr;

    if (attr && Property::checkType(attr)) {
        PyObject *value = Property::getValue(reinterpret_cast<PySideProperty *>(attr), self);
        Py_DECREF(attr);
        if (!value)
            return nullptr;
        attr = value;
    }

    // Search on metaobject (avoid internal attributes started with '__')
    if (!attr) {
        PyObject *type{};
        PyObject *value{};
        PyObject *traceback{};
        PyErr_Fetch(&type, &value, &traceback);     // This was omitted for a loong time.

        int flags = currentSelectId(Py_TYPE(self));
        int snake_flag = flags & 0x01;
        int propFlag = flags & 0x02;

        if (propFlag) {
            // PYSIDE-1889: If we have actually a Python property, return f(get|set|del).
            //              Do not store this attribute in the instance dict, because this
            //              would create confusion with overload.
            // Note: before implementing this property handling, the meta function code
            // below created meta functions which was quite wrong.
            auto *subdict = _PepType_Lookup(Py_TYPE(self), PySideMagicName::property_methods());
            if (PyObject *propName = PyDict_GetItem(subdict, name)) {
                // We really have a property name and need to fetch the fget or fset function.
                static PyObject *const _fget = Shiboken::String::createStaticString("fget");
                static PyObject *const _fset = Shiboken::String::createStaticString("fset");
                static PyObject *const _fdel = Shiboken::String::createStaticString("fdel");
                static PyObject *const arr[3] = {_fget, _fset, _fdel};
                auto *prop = _PepType_Lookup(Py_TYPE(self), propName);
                for (auto *trial : arr) {
                    auto *res = PyObject_GetAttr(prop, trial);
                    if (res) {
                        AutoDecRef elemName(PyObject_GetAttr(res, PySideMagicName::name()));
                        // Note: This comparison works because of interned strings.
                        if (elemName == name)
                            return res;
                        Py_DECREF(res);
                    }
                    PyErr_Clear();
                }
            }
        }

        const char *cname = Shiboken::String::toCString(name);
        uint cnameLen = qstrlen(cname);
        if (std::strncmp("__", cname, 2) != 0) {
            const QMetaObject *metaObject = cppSelf->metaObject();
            QList<QMetaMethod> signalList;
            // Caution: This inserts a meta function or a signal into the instance dict.
            for (int i=0, imax = metaObject->methodCount(); i < imax; i++) {
                QMetaMethod method = metaObject->method(i);
                // PYSIDE-1753: Snake case names must be renamed here too, or they will be
                // found unexpectedly when forgetting to rename them.
                auto origSignature = method.methodSignature();
                // Currently, we rename only methods but no signals. This might change.
                bool use_lower = snake_flag and method.methodType() != QMetaMethod::Signal;
                const QByteArray methSig_ = _sigWithMangledName(origSignature, use_lower);
                const char *methSig = methSig_.constData();
                bool methMatch = std::strncmp(cname, methSig, cnameLen) == 0
                                 && methSig[cnameLen] == '(';
                if (methMatch) {
                    if (method.methodType() == QMetaMethod::Signal) {
                        signalList.append(method);
                    } else if (auto *func = MetaFunction::newObject(cppSelf, i)) {
                        auto *result = reinterpret_cast<PyObject *>(func);
                        PyObject_SetAttr(self, name, result);
                        return result;
                    }
                }
            }
            if (!signalList.isEmpty()) {
                auto *pySignal = reinterpret_cast<PyObject *>(
                    Signal::newObjectFromMethod(self, signalList));
                PyObject_SetAttr(self, name, pySignal);
                return pySignal;
            }
        }
        PyErr_Restore(type, value, traceback);
    }
    return attr;
}

bool inherits(PyTypeObject *objType, const char *class_name)
{
    if (strcmp(objType->tp_name, class_name) == 0)
        return true;

    PyTypeObject *base = objType->tp_base;
    if (base == nullptr)
        return false;

    return inherits(base, class_name);
}

QMutex &nextQObjectMemoryAddrMutex()
{
    static QMutex mutex;
    return mutex;
}

void *nextQObjectMemoryAddr()
{
    return qobjectNextAddr;
}

void setNextQObjectMemoryAddr(void *addr)
{
    qobjectNextAddr = addr;
}

} // namespace PySide

// A std::shared_ptr is used with a deletion function to invalidate a pointer
// when the property value is cleared.  This should be a QSharedPointer with
// a void *pointer, but that isn't allowed
using any_t = char;
Q_DECLARE_METATYPE(std::shared_ptr<any_t>);


namespace PySide
{

static void invalidatePtr(any_t *object)
{
    // PYSIDE-2254: Guard against QObjects outliving Python, for example the
    // adopted main thread as returned by QObjects::thread().
    if (Py_IsInitialized() == 0)
        return;

    Shiboken::GilState state;

    SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(object);
    if (wrapper != nullptr)
        Shiboken::BindingManager::instance().releaseWrapper(wrapper);
}

static const char invalidatePropertyName[] = "_PySideInvalidatePtr";

// PYSIDE-2749: Skip over internal QML classes and classes
// with dynamic meta objects when looking for the best matching
// type to avoid unnessarily triggering the lazy load mechanism
// for classes that do not have a binding from things like eventFilter().
static inline bool isInternalObject(const char *name)
{
    return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr;
}

static const QMetaObject *metaObjectCandidate(const QObject *o)
{
    const auto *metaObject = o->metaObject();
    // Skip QML helper types and Python objects
    if (hasDynamicMetaObject(o)) {
        if (const auto *super = metaObject->superClass())
            metaObject = super;
    }
    for (const auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) {
        if (!isInternalObject(candidate->className())) {
            metaObject = candidate;
            break;
        }
    }
    return metaObject;
}

// PYSIDE-1214, when creating new wrappers for classes inheriting QObject but
// not exposed to Python, try to find the best-matching (most-derived) Qt
// class by walking up the meta objects.
static const char *typeName(const QObject *cppSelf)
{
    const char *typeName = typeid(*cppSelf).name();
    if (!Shiboken::Conversions::getConverter(typeName)) {
        const auto *metaObject = metaObjectCandidate(cppSelf);
        for (; metaObject != nullptr; metaObject = metaObject->superClass()) {
            const char *name = metaObject->className();
            if (Shiboken::Conversions::getConverter(name)) {
                typeName = name;
                break;
            }
        }
    }
    return typeName;
}

PyTypeObject *getTypeForQObject(const QObject *cppSelf)
{
    // First check if there are any instances of Python implementations
    // inheriting a PySide class.
    auto *existing = Shiboken::BindingManager::instance().retrieveWrapper(cppSelf);
    if (existing != nullptr)
        return reinterpret_cast<PyObject *>(existing)->ob_type;
    // Find the best match (will return a PySide type)
    auto *sbkObjectType = Shiboken::ObjectType::typeForTypeName(typeName(cppSelf));
    if (sbkObjectType != nullptr)
        return reinterpret_cast<PyTypeObject *>(sbkObjectType);
    return nullptr;
}

PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type)
{
    auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf));
    if (pyOut) {
        Py_INCREF(pyOut);
        return pyOut;
    }

    // Setting the property will trigger an QEvent notification, which may call into
    // code that creates the wrapper so only set the property if it isn't already
    // set and check if it's created after the set call
    QVariant existing = cppSelf->property(invalidatePropertyName);
    if (!existing.isValid()) {
        if (cppSelf->thread() == QThread::currentThread()) {
            std::shared_ptr<any_t> shared_with_del(reinterpret_cast<any_t *>(cppSelf), invalidatePtr);
            cppSelf->setProperty(invalidatePropertyName, QVariant::fromValue(shared_with_del));
        }
        pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf));
        if (pyOut) {
            Py_INCREF(pyOut);
            return pyOut;
        }
    }

    pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf));

    return pyOut;
}

QString pyUnicodeToQString(PyObject *str)
{
    Q_ASSERT(PyUnicode_Check(str) != 0);

    const void *data = _PepUnicode_DATA(str);
    const Py_ssize_t len = PyUnicode_GetLength(str);
    switch (_PepUnicode_KIND(str)) {
    case PepUnicode_1BYTE_KIND:
        return QString::fromLatin1(reinterpret_cast<const char *>(data), len);
    case PepUnicode_2BYTE_KIND:
        return QString::fromUtf16(reinterpret_cast<const char16_t *>(data), len);
    case PepUnicode_4BYTE_KIND:
        break;
    }
    return QString::fromUcs4(reinterpret_cast<const char32_t *>(data), len);
}

PyObject *qStringToPyUnicode(QStringView s)
{
    const QByteArray ba = s.toUtf8();
    return PyUnicode_FromStringAndSize(ba.constData(), ba.size());
}

// Inspired by Shiboken::String::toCString;
QString pyStringToQString(PyObject *str)
{
    if (str == Py_None)
        return {};

    if (PyUnicode_Check(str) != 0)
        return pyUnicodeToQString(str);

    if (PyBytes_Check(str)) {
        const char *asciiBuffer = PyBytes_AsString(str);
        if (asciiBuffer)
            return QString::fromLatin1(asciiBuffer);
    }
    return {};
}

// PySide-1499: Provide an efficient, correct PathLike interface
QString pyPathToQString(PyObject *path)
{
    // For empty constructors path can be nullptr
    // fallback to an empty QString in that case.
    if (!path)
        return {};

    // str or bytes pass through
    if (PyUnicode_Check(path) || PyBytes_Check(path))
        return pyStringToQString(path);

    // Let PyOS_FSPath do its work and then fix the result for Windows.
    Shiboken::AutoDecRef strPath(PyOS_FSPath(path));
    if (strPath.isNull())
        return {};
    return QDir::fromNativeSeparators(pyStringToQString(strPath));
}

bool isCompiledMethod(PyObject *callback)
{
    return PyObject_HasAttr(callback, PySide::PySideName::im_func())
           && PyObject_HasAttr(callback, PySide::PySideName::im_self())
           && PyObject_HasAttr(callback, PySide::PySideMagicName::code());
}

static const unsigned char qt_resource_name[] = {
  // qt
  0x0,0x2,
  0x0,0x0,0x7,0x84,
  0x0,0x71,
  0x0,0x74,
    // etc
  0x0,0x3,
  0x0,0x0,0x6c,0xa3,
  0x0,0x65,
  0x0,0x74,0x0,0x63,
    // qt.conf
  0x0,0x7,
  0x8,0x74,0xa6,0xa6,
  0x0,0x71,
  0x0,0x74,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x66
};

static const unsigned char qt_resource_struct[] = {
  // :
  0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,
  // :/qt
  0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2,
  // :/qt/etc
  0x0,0x0,0x0,0xa,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3,
  // :/qt/etc/qt.conf
  0x0,0x0,0x0,0x16,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0
};

bool registerInternalQtConf()
{
    // Guard to ensure single registration.
#ifdef PYSIDE_QT_CONF_PREFIX
    static bool registrationAttempted = false;
#else
    static bool registrationAttempted = true;
#endif
    static bool isRegistered = false;
    if (registrationAttempted)
        return isRegistered;
    registrationAttempted = true;

    // Support PyInstaller case when a qt.conf file might be provided next to the generated
    // PyInstaller executable.
    // This will disable the internal qt.conf which points to the PySide6 subdirectory (due to the
    // subdirectory not existing anymore).
#ifndef PYPY_VERSION
    QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath());
#else
    // PYSIDE-535: FIXME: Add this function when available.
    QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath");
#endif // PYPY_VERSION

    QString appDirPath = QFileInfo(executablePath).absolutePath();

    QString maybeQtConfPath = QDir(appDirPath).filePath(u"qt.conf"_s);
    maybeQtConfPath = QDir::toNativeSeparators(maybeQtConfPath);
    bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath);

    QString maybeQt6ConfPath = QDir(appDirPath).filePath(u"qt6.conf"_s);
    maybeQt6ConfPath = QDir::toNativeSeparators(maybeQt6ConfPath);
    bool executableQt6ConfAvailable = QFileInfo::exists(maybeQt6ConfPath);

    // Allow disabling the usage of the internal qt.conf. This is necessary for tests to work,
    // because tests are executed before the package is installed, and thus the Prefix specified
    // in qt.conf would point to a not yet existing location.
    bool disableInternalQtConf =
        qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0;
    bool runsInConda =
        qEnvironmentVariableIsSet("CONDA_DEFAULT_ENV") || qEnvironmentVariableIsSet("CONDA_PREFIX");

    if ((!runsInConda && (disableInternalQtConf || executableQtConfAvailable))
        || (runsInConda && executableQt6ConfAvailable)) {
        registrationAttempted = true;
        return false;
    }

    PyObject *pysideModule = PyImport_ImportModule("PySide6");
    if (!pysideModule)
        return false;

    // Querying __file__ should be done only for modules that have finished their initialization.
    // Thus querying for the top-level PySide6 package works for us whenever any Qt-wrapped module
    // is loaded.
    PyObject *pysideInitFilePath = PyObject_GetAttr(pysideModule, Shiboken::PyMagicName::file());
    Py_DECREF(pysideModule);
    if (!pysideInitFilePath)
        return false;

    QString initPath = pyStringToQString(pysideInitFilePath);
    Py_DECREF(pysideInitFilePath);
    if (initPath.isEmpty())
        return false;

    // pysideDir - absolute path to the directory containing the init file, which also contains
    // the rest of the PySide6 modules.
    // prefixPath - absolute path to the directory containing the installed Qt (prefix).
    QDir pysideDir = QFileInfo(QDir::fromNativeSeparators(initPath)).absoluteDir();
    QString setupPrefix;
#ifdef PYSIDE_QT_CONF_PREFIX
    setupPrefix = QStringLiteral(PYSIDE_QT_CONF_PREFIX);
#endif
    const QByteArray prefixPath = pysideDir.absoluteFilePath(setupPrefix).toUtf8();

    // rccData needs to be static, otherwise when it goes out of scope, the Qt resource system
    // will point to invalid memory.
    static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath + "\n";
#ifdef Q_OS_WIN
    // LibraryExecutables needs to point to Prefix instead of ./bin because we don't
    // currently conform to the Qt default directory layout on Windows. This is necessary
    // for QtWebEngineCore to find the location of QtWebEngineProcess.exe.
    rccData += QByteArrayLiteral("LibraryExecutables = ") + prefixPath + "\n";
#endif

    // The RCC data structure expects a 4-byte size value representing the actual data.
    qsizetype size = rccData.size();

    for (int i = 0; i < 4; ++i) {
        rccData.prepend((size & 0xff));
        size >>= 8;
    }

    const int version = 0x01;
    isRegistered = qRegisterResourceData(version, qt_resource_struct, qt_resource_name,
                                         reinterpret_cast<const unsigned char *>(
                                             rccData.constData()));

    return isRegistered;
}

static PyTypeObject *qobjectType()
{
    static PyTypeObject * const result = Shiboken::Conversions::getPythonTypeObject("QObject*");
    return result;
}

bool isQObjectDerived(PyTypeObject *pyType, bool raiseError)
{
    const bool result = PyType_IsSubtype(pyType, qobjectType());
    if (!result && raiseError) {
        PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.",
                     qobjectType()->tp_name, pyType->tp_name);
    }
    return result;
}

QObject *convertToQObject(PyObject *object, bool raiseError)
{
    if (object == nullptr) {
        if (raiseError)
            PyErr_Format(PyExc_TypeError, "None passed for QObject");
        return nullptr;
    }

    if (!isQObjectDerived(Py_TYPE(object), raiseError))
        return nullptr;

    auto *sbkObject = reinterpret_cast<SbkObject*>(object);
    auto *ptr = Shiboken::Object::cppPointer(sbkObject, qobjectType());
    if (ptr == nullptr) {
        if (raiseError) {
            PyErr_Format(PyExc_TypeError, "Conversion of %s to QObject failed.",
                         Py_TYPE(object)->tp_name);
        }
        return nullptr;
    }
    return reinterpret_cast<QObject*>(ptr);
}

QMetaType qMetaTypeFromPyType(PyTypeObject *pyType)
{
    if (Shiboken::String::checkType(pyType))
        return QMetaType(QMetaType::QString);
    if (pyType == &PyFloat_Type)
        return QMetaType(QMetaType::Double);
    if (pyType == &PyLong_Type)
        return QMetaType(QMetaType::Int);
    if (Shiboken::ObjectType::checkType(pyType))
        return QMetaType::fromName(Shiboken::ObjectType::getOriginalName(pyType));
    return QMetaType::fromName(pyType->tp_name);
}

debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) noexcept
    : m_object(o)
{
}

QDebug operator<<(QDebug debug, const debugPyTypeObject &o)
{
    QDebugStateSaver saver(debug);
    debug.noquote();
    debug.nospace();
    debug << "PyTypeObject(";
    if (o.m_object)
        debug << '"' << o.m_object->tp_name << '"';
    else
        debug << '0';
    debug << ')';
    return debug;
}

static void formatPyObject(PyObject *obj, QDebug &debug);

static void formatPySequence(PyObject *obj, QDebug &debug)
{
    const Py_ssize_t size = PySequence_Size(obj);
    debug << size << " [";
    for (Py_ssize_t i = 0; i < size; ++i) {
        if (i)
            debug << ", ";
        Shiboken::AutoDecRef item(PySequence_GetItem(obj, i));
        formatPyObject(item.object(), debug);
    }
    debug << ']';
}

static void formatPyDict(PyObject *obj, QDebug &debug)
{
    PyObject *key{};
    PyObject *value{};
    Py_ssize_t pos = 0;
    bool first = true;
    debug << '{';
    while (PyDict_Next(obj, &pos, &key, &value) != 0) {
        if (first)
            first = false;
        else
            debug << ", ";
        formatPyObject(key, debug);
        debug << '=';
        formatPyObject(value, debug);
    }
    debug << '}';
}

static inline const char *pyTypeName(PyObject *obj)
{
    return Py_TYPE(obj)->tp_name;
}

static QString getQualName(PyObject *obj)
{
    Shiboken::AutoDecRef result(PyObject_GetAttr(obj, Shiboken::PyMagicName::qualname()));
    return result.object() != nullptr
        ? pyStringToQString(result.object()) : QString{};
}

static void formatPyFunction(PyObject *obj, QDebug &debug)
{
    debug << '"' << getQualName(obj) << "()\"";
}

static void formatPyMethod(PyObject *obj, QDebug &debug)
{
    if (auto *func = PyMethod_Function(obj))
        formatPyFunction(func, debug);
    debug << ", instance=" << PyMethod_Self(obj);
}

static void formatPyObjectValue(PyObject *obj, QDebug &debug)
{
    if (PyType_Check(obj) != 0)
        debug << "type: \"" << pyTypeName(obj) << '"';
    else if (PyLong_Check(obj) != 0) {
        const auto llv = PyLong_AsLongLong(obj);
        if (PyErr_Occurred() != PyExc_OverflowError) {
            debug << llv;
        } else {
            PyErr_Clear();
            debug << "0x" << Qt::hex << PyLong_AsUnsignedLongLong(obj) << Qt::dec;
        }
    } else if (PyFloat_Check(obj) != 0)
        debug << PyFloat_AsDouble(obj);
    else if (PyUnicode_Check(obj) != 0)
        debug << '"' << pyStringToQString(obj) << '"';
    else if (PyFunction_Check(obj) != 0)
        formatPyFunction(obj, debug);
    else if (PyMethod_Check(obj) != 0)
        formatPyMethod(obj, debug);
    else if (PySequence_Check(obj) != 0)
        formatPySequence(obj, debug);
    else if (PyDict_Check(obj) != 0)
        formatPyDict(obj, debug);
    else
        debug << obj;
}

static void formatPyObject(PyObject *obj, QDebug &debug)
{
    if (obj == nullptr) {
        debug << '0';
        return;
    }
    if (obj == Py_None) {
        debug << "None";
        return;
    }
    if (obj == Py_True) {
        debug << "True";
        return;
    }
    if (obj == Py_False) {
        debug << "False";
        return;
    }
    if (PyType_Check(obj) == 0)
        debug << pyTypeName(obj);
    const auto refs = Py_REFCNT(obj);
    if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT
        debug << ", immortal";
    else
        debug << ", refs=" << refs;
    debug << ": ";
    formatPyObjectValue(obj, debug);
}

debugPyObject::debugPyObject(PyObject *o) noexcept : m_object(o)
{
}

QDebug operator<<(QDebug debug, const debugPyObject &o)
{
    QDebugStateSaver saver(debug);
    debug.noquote();
    debug.nospace();
    debug << "PyObject(";
    formatPyObject(o.m_object, debug);
    debug << ')';
    return debug;
}

debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b)
{
}

static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_t *array, int len)
{
    debug << ", " << name << '=';
    if (array != nullptr) {
        debug << '[';
        for (int i = 0; i < len; ++i)
            debug << array[i] << ' ';
        debug << ']';
    } else {
        debug << '0';
    }
}

PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b)
{
    QDebugStateSaver saver(debug);
    debug.noquote();
    debug.nospace();
    debug << "Py_buffer(";
    if (b.m_buffer != nullptr) {
        debug << "obj=" << b.m_buffer->obj
              << ", buf=" << b.m_buffer->buf << ", len=" << b.m_buffer->len
              << ", readonly=" <<  b.m_buffer->readonly
              << ", itemsize=" <<  b.m_buffer->itemsize << ", format=";
        if (b.m_buffer->format != nullptr)
            debug << '"' << b.m_buffer->format << '"';
        else
            debug << '0';
        debug << ", ndim=" << b.m_buffer->ndim;
        formatPy_ssizeArray(debug, "shape", b.m_buffer->shape, b.m_buffer->ndim);
        formatPy_ssizeArray(debug, "strides", b.m_buffer->strides, b.m_buffer->ndim);
        formatPy_ssizeArray(debug, "suboffsets", b.m_buffer->suboffsets, b.m_buffer->ndim);
    } else {
        debug << '0';
    }
    debug << ')';
    return debug;
}

} // namespace PySide
