/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt for Python.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <sbkpython.h>
#include "pysidesignal.h"
#include "pysidesignal_p.h"
#include "pysidestaticstrings.h"
#include "signalmanager.h"

#include <shiboken.h>

#include <QtCore/QObject>
#include <QtCore/QMetaMethod>
#include <QtCore/QMetaObject>
#include <signature.h>

#include <algorithm>
#include <utility>

#define QT_SIGNAL_SENTINEL '2'

namespace PySide {
namespace Signal {
    //aux
    class SignalSignature {
    public:
        SignalSignature() = default;
        explicit SignalSignature(QByteArray parameterTypes) :
            m_parameterTypes(std::move(parameterTypes)) {}
        explicit SignalSignature(QByteArray parameterTypes, QMetaMethod::Attributes attributes) :
            m_parameterTypes(std::move(parameterTypes)),
            m_attributes(attributes) {}

        QByteArray m_parameterTypes;
        QMetaMethod::Attributes m_attributes = QMetaMethod::Compatibility;
    };

    static QByteArray buildSignature(const QByteArray &, const QByteArray &);
    static void appendSignature(PySideSignal *, const SignalSignature &);
    static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *, PyObject *, int);
    static QByteArray parseSignature(PyObject *);
    static PyObject *buildQtCompatible(const QByteArray &);
}
}

extern "C"
{

// Signal methods
static int signalTpInit(PyObject *, PyObject *, PyObject *);
static void signalFree(void *);
static void signalInstanceFree(void *);
static PyObject *signalGetItem(PyObject *self, PyObject *key);
static PyObject *signalToString(PyObject *self);
static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject *type);

// Signal Instance methods
static PyObject *signalInstanceConnect(PyObject *, PyObject *, PyObject *);
static PyObject *signalInstanceDisconnect(PyObject *, PyObject *);
static PyObject *signalInstanceEmit(PyObject *, PyObject *);
static PyObject *signalInstanceGetItem(PyObject *, PyObject *);

static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *signalCall(PyObject *, PyObject *, PyObject *);

static PyObject *metaSignalCheck(PyObject *, PyObject *);


static PyMethodDef MetaSignal_methods[] = {
    {"__instancecheck__", (PyCFunction)metaSignalCheck, METH_O|METH_STATIC, NULL},
    {0, 0, 0, 0}
};

static PyType_Slot PySideMetaSignalType_slots[] = {
    {Py_tp_methods, reinterpret_cast<void *>(MetaSignal_methods)},
    {Py_tp_base,    reinterpret_cast<void *>(&PyType_Type)},
    {Py_tp_free,    reinterpret_cast<void *>(PyObject_GC_Del)},
    {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)},
    {0, 0}
};
static PyType_Spec PySideMetaSignalType_spec = {
    "2:PySide2.QtCore.MetaSignal",
    0,
    // sizeof(PyHeapTypeObject) is filled in by SbkType_FromSpecWithBases
    // which calls PyType_Ready which calls inherit_special.
    0,
    Py_TPFLAGS_DEFAULT,
    PySideMetaSignalType_slots,
};


static PyTypeObject *PySideMetaSignalTypeF(void)
{
    static PyTypeObject *type = nullptr;
    if (!type) {
        PyObject *bases = Py_BuildValue("(O)", &PyType_Type);
        type = (PyTypeObject *)SbkType_FromSpecWithBases(&PySideMetaSignalType_spec, bases);
        Py_XDECREF(bases);
    }
    return type;
}

static PyType_Slot PySideSignalType_slots[] = {
    {Py_mp_subscript,   reinterpret_cast<void *>(signalGetItem)},
    {Py_tp_descr_get,   reinterpret_cast<void *>(signalDescrGet)},
    {Py_tp_call,        reinterpret_cast<void *>(signalCall)},
    {Py_tp_str,         reinterpret_cast<void *>(signalToString)},
    {Py_tp_init,        reinterpret_cast<void *>(signalTpInit)},
    {Py_tp_new,         reinterpret_cast<void *>(PyType_GenericNew)},
    {Py_tp_free,        reinterpret_cast<void *>(signalFree)},
    {Py_tp_dealloc,     reinterpret_cast<void *>(Sbk_object_dealloc)},
    {0, 0}
};
static PyType_Spec PySideSignalType_spec = {
    "2:PySide2.QtCore.Signal",
    sizeof(PySideSignal),
    0,
    Py_TPFLAGS_DEFAULT,
    PySideSignalType_slots,
};


PyTypeObject *PySideSignalTypeF(void)
{
    static PyTypeObject *type = nullptr;
    if (!type) {
        type = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalType_spec));
        PyTypeObject *hold = Py_TYPE(type);
        Py_TYPE(type) = PySideMetaSignalTypeF();
        Py_INCREF(Py_TYPE(type));
        Py_DECREF(hold);
    }
    return type;
}

static PyMethodDef SignalInstance_methods[] = {
    {"connect", (PyCFunction)signalInstanceConnect, METH_VARARGS|METH_KEYWORDS, 0},
    {"disconnect", signalInstanceDisconnect, METH_VARARGS, 0},
    {"emit", signalInstanceEmit, METH_VARARGS, 0},
    {0, 0, 0, 0}  /* Sentinel */
};

static PyType_Slot PySideSignalInstanceType_slots[] = {
    {Py_mp_subscript,   reinterpret_cast<void *>(signalInstanceGetItem)},
    {Py_tp_call,        reinterpret_cast<void *>(signalInstanceCall)},
    {Py_tp_methods,     reinterpret_cast<void *>(SignalInstance_methods)},
    {Py_tp_new,         reinterpret_cast<void *>(PyType_GenericNew)},
    {Py_tp_free,        reinterpret_cast<void *>(signalInstanceFree)},
    {Py_tp_dealloc,     reinterpret_cast<void *>(Sbk_object_dealloc)},
    {0, 0}
};
static PyType_Spec PySideSignalInstanceType_spec = {
    "2:PySide2.QtCore.SignalInstance",
    sizeof(PySideSignalInstance),
    0,
    Py_TPFLAGS_DEFAULT,
    PySideSignalInstanceType_slots,
};


PyTypeObject *PySideSignalInstanceTypeF(void)
{
    static PyTypeObject *type =
        reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalInstanceType_spec));
    return type;
}

static int signalTpInit(PyObject *self, PyObject *args, PyObject *kwds)
{
    static PyObject *emptyTuple = nullptr;
    static const char *kwlist[] = {"name", "arguments", nullptr};
    char *argName = nullptr;
    PyObject *argArguments = nullptr;

    if (emptyTuple == 0)
        emptyTuple = PyTuple_New(0);

    if (!PyArg_ParseTupleAndKeywords(emptyTuple, kwds,
        "|sO:QtCore.Signal", const_cast<char **>(kwlist), &argName, &argArguments))
        return -1;

    bool tupledArgs = false;
    PySideSignal *data = reinterpret_cast<PySideSignal *>(self);
    if (!data->data)
        data->data = new PySideSignalData;
    if (argName)
        data->data->signalName = argName;

    data->data->signalArguments = new QByteArrayList();
    if (argArguments && PySequence_Check(argArguments)) {
        Py_ssize_t argument_size = PySequence_Size(argArguments);
        for (Py_ssize_t i = 0; i < argument_size; ++i) {
            PyObject *item = PySequence_GetItem(argArguments, i);
#ifdef IS_PY3K
            PyObject *strObj = PyUnicode_AsUTF8String(item);
            char *s = PyBytes_AsString(strObj);
            Py_DECREF(strObj);
#else
            char *s = PyBytes_AsString(item);
#endif
            Py_DECREF(item);
            if (s != nullptr)
                data->data->signalArguments->append(QByteArray(s));
        }
    }

    for (Py_ssize_t i = 0, i_max = PyTuple_Size(args); i < i_max; i++) {
        PyObject *arg = PyTuple_GET_ITEM(args, i);
        if (PySequence_Check(arg) && !Shiboken::String::check(arg) && !PyEnumMeta_Check(arg)) {
            tupledArgs = true;
            const auto sig = PySide::Signal::parseSignature(arg);
            PySide::Signal::appendSignature(
                        data,
                        PySide::Signal::SignalSignature(sig));
        }
    }

    if (!tupledArgs) {
        const auto sig = PySide::Signal::parseSignature(args);
        PySide::Signal::appendSignature(
                    data,
                    PySide::Signal::SignalSignature(sig));
    }

    return 0;
}

static void signalFree(void *self)
{
    auto pySelf = reinterpret_cast<PyObject *>(self);
    auto data = reinterpret_cast<PySideSignal *>(self);
    delete data->data;
    data->data = nullptr;
    Py_XDECREF(data->homonymousMethod);
    data->homonymousMethod = 0;

    Py_TYPE(pySelf)->tp_base->tp_free(self);
}

static PyObject *signalGetItem(PyObject *self, PyObject *key)
{
    auto data = reinterpret_cast<PySideSignal *>(self);
    QByteArray sigKey;
    if (key) {
        sigKey = PySide::Signal::parseSignature(key);
    } else {
        sigKey = data->data == nullptr || data->data->signatures.isEmpty()
            ? PySide::Signal::voidType() : data->data->signatures.constFirst().signature;
    }
    auto sig = PySide::Signal::buildSignature(data->data->signalName, sigKey);
    return Shiboken::String::fromCString(sig.constData());
}


static PyObject *signalToString(PyObject *self)
{
    return signalGetItem(self, 0);
}

static void signalInstanceFree(void *self)
{
    auto pySelf = reinterpret_cast<PyObject *>(self);
    auto data = reinterpret_cast<PySideSignalInstance *>(self);

    PySideSignalInstancePrivate *dataPvt = data->d;

    Py_XDECREF(dataPvt->homonymousMethod);

    if (dataPvt->next) {
        Py_DECREF(dataPvt->next);
        dataPvt->next = 0;
    }
    delete dataPvt;
    data->d = 0;
    Py_TYPE(pySelf)->tp_base->tp_free(self);
}

static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *slot = nullptr;
    PyObject *type = nullptr;
    static const char *kwlist[] = {"slot", "type", nullptr};

    if (!PyArg_ParseTupleAndKeywords(args, kwds,
        "O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type))
        return 0;

    PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self);
    Shiboken::AutoDecRef pyArgs(PyList_New(0));

    bool match = false;
    if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) {
        PySideSignalInstance *sourceWalk = source;
        PySideSignalInstance *targetWalk;

        //find best match
        while (sourceWalk && !match) {
            targetWalk = reinterpret_cast<PySideSignalInstance *>(slot);
            while (targetWalk && !match) {
                if (QMetaObject::checkConnectArgs(sourceWalk->d->signature, targetWalk->d->signature)) {
                    PyList_Append(pyArgs, sourceWalk->d->source);
                    Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(sourceWalk->d->signature));
                    PyList_Append(pyArgs, sourceSignature);

                    PyList_Append(pyArgs, targetWalk->d->source);
                    Shiboken::AutoDecRef targetSignature(PySide::Signal::buildQtCompatible(targetWalk->d->signature));
                    PyList_Append(pyArgs, targetSignature);

                    match = true;
                }
                targetWalk = reinterpret_cast<PySideSignalInstance *>(targetWalk->d->next);
            }
            sourceWalk = reinterpret_cast<PySideSignalInstance *>(sourceWalk->d->next);
        }
    } else {
        // Check signature of the slot (method or function) to match signal
        int slotArgs = -1;
        bool useSelf = false;
        bool isMethod = PyMethod_Check(slot);
        bool isFunction = PyFunction_Check(slot);
        bool matchedSlot = false;

        QByteArray functionName;
        PySideSignalInstance *it = source;

        if (isMethod || isFunction) {
            PyObject *function = isMethod ? PyMethod_GET_FUNCTION(slot) : slot;
            auto *objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function));
            useSelf = isMethod;
            slotArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode);
            if (useSelf)
                slotArgs -= 1;

            // Get signature args
            bool isShortCircuit = false;
            int signatureArgs = 0;
            QStringList argsSignature;

            argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature,
                &isShortCircuit);
            signatureArgs = argsSignature.length();

            // Iterate the possible types of connection for this signal and compare
            // it with slot arguments
            if (signatureArgs != slotArgs) {
                while (it->d->next != nullptr) {
                    it = it->d->next;
                    argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature,
                        &isShortCircuit);
                    signatureArgs = argsSignature.length();
                    if (signatureArgs == slotArgs) {
                        matchedSlot = true;
                        break;
                    }
                }
            }
        }

        // Adding references to pyArgs
        PyList_Append(pyArgs, source->d->source);

        if (matchedSlot) {
            // If a slot matching the same number of arguments was found,
            // include signature to the pyArgs
            Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature));
            PyList_Append(pyArgs, signature);
        } else {
            // Try the first by default if the slot was not found
            Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature));
            PyList_Append(pyArgs, signature);
        }
        PyList_Append(pyArgs, slot);
        match = true;
    }

    if (type)
        PyList_Append(pyArgs, type);

    if (match) {
        Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
        Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source,
                                                       PySide::PyName::qtConnect()));
        if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists.
            PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!");
            return 0;
        }
        PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
        if (result == Py_True || result == Py_False)
            return result;
        Py_XDECREF(result);
    }
    if (!PyErr_Occurred()) // PYSIDE-79: inverse the logic. A Null return needs an error.
        PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.",
                     source->d->signature.constData());
    return 0;
}

static int argCountInSignature(const char *signature)
{
    return QByteArray(signature).count(",") + 1;
}

static PyObject *signalInstanceEmit(PyObject *self, PyObject *args)
{
    PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self);

    Shiboken::AutoDecRef pyArgs(PyList_New(0));
    int numArgsGiven = PySequence_Fast_GET_SIZE(args);
    int numArgsInSignature = argCountInSignature(source->d->signature);

    // If number of arguments given to emit is smaller than the first source signature expects,
    // it is possible it's a case of emitting a signal with default parameters.
    // Search through all the overloaded signals with the same name, and try to find a signature
    // with the same number of arguments as given to emit, and is also marked as a cloned method
    // (which in metaobject parlance means a signal with default parameters).
    // @TODO: This should be improved to take into account argument types as well. The current
    // assumption is there are no signals which are both overloaded on argument types and happen to
    // have signatures with default parameters.
    if (numArgsGiven < numArgsInSignature) {
        PySideSignalInstance *possibleDefaultInstance = source;
        while ((possibleDefaultInstance = possibleDefaultInstance->d->next)) {
            if (possibleDefaultInstance->d->attributes & QMetaMethod::Cloned
                    && argCountInSignature(possibleDefaultInstance->d->signature) == numArgsGiven) {
                source = possibleDefaultInstance;
                break;
            }
        }
    }
    Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(source->d->signature));

    PyList_Append(pyArgs, sourceSignature);
    for (Py_ssize_t i = 0, max = PyTuple_Size(args); i < max; i++)
        PyList_Append(pyArgs, PyTuple_GetItem(args, i));

    Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source,
                                                   PySide::PyName::qtEmit()));

    Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
    return PyObject_CallObject(pyMethod, tupleArgs);
}

static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key)
{
    auto data = reinterpret_cast<PySideSignalInstance *>(self);
    const auto sigName = data->d->signalName;
    const auto sigKey = PySide::Signal::parseSignature(key);
    const auto sig = PySide::Signal::buildSignature(sigName, sigKey);
    while (data) {
        if (data->d->signature == sig) {
            PyObject *result = reinterpret_cast<PyObject *>(data);
            Py_INCREF(result);
            return result;
        }
        data = data->d->next;
    }

    PyErr_Format(PyExc_IndexError, "Signature %s not found for signal: %s",
                 sig.constData(), sigName.constData());
    return 0;
}

static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args)
{
    auto source = reinterpret_cast<PySideSignalInstance *>(self);
    Shiboken::AutoDecRef pyArgs(PyList_New(0));

    PyObject *slot;
    if (PyTuple_Check(args) && PyTuple_GET_SIZE(args))
        slot = PyTuple_GET_ITEM(args, 0);
    else
        slot = Py_None;

    bool match = false;
    if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) {
        PySideSignalInstance *target = reinterpret_cast<PySideSignalInstance *>(slot);
        if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) {
            PyList_Append(pyArgs, source->d->source);
            Shiboken::AutoDecRef source_signature(PySide::Signal::buildQtCompatible(source->d->signature));
            PyList_Append(pyArgs, source_signature);

            PyList_Append(pyArgs, target->d->source);
            Shiboken::AutoDecRef target_signature(PySide::Signal::buildQtCompatible(target->d->signature));
            PyList_Append(pyArgs, target_signature);
            match = true;
        }
    } else {
        //try the first signature
        PyList_Append(pyArgs, source->d->source);
        Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature));
        PyList_Append(pyArgs, signature);

        // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0)
        if (slot == Py_None)
            PyList_Append(pyArgs, slot);
        PyList_Append(pyArgs, slot);
        match = true;
    }

    if (match) {
        Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
        Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source,
                                                       PySide::PyName::qtDisconnect()));
        PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
        if (!result || result == Py_True)
            return result;
        else
            Py_DECREF(result);
    }

    PyErr_Format(PyExc_RuntimeError, "Failed to disconnect signal %s.",
                 source->d->signature.constData());
    return 0;
}

// PYSIDE-68: Supply the missing __get__ function
static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject * /*type*/)
{
    auto signal = reinterpret_cast<PySideSignal *>(self);
    // Return the unbound signal if there is nothing to bind it to.
    if (obj == nullptr || obj == Py_None) {
        Py_INCREF(self);
        return self;
    }
    Shiboken::AutoDecRef name(Py_BuildValue("s", signal->data->signalName.data()));
    return reinterpret_cast<PyObject *>(PySide::Signal::initialize(signal, name, obj));
}

static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw)
{
    auto signal = reinterpret_cast<PySideSignal *>(self);

    // Native C++ signals can't be called like functions, thus we throw an exception.
    // The only way calling a signal can succeed (the Python equivalent of C++'s operator() )
    // is when a method with the same name as the signal is attached to an object.
    // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h).
    if (!signal->homonymousMethod) {
        PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable");
        return 0;
    }

    descrgetfunc getDescriptor = Py_TYPE(signal->homonymousMethod)->tp_descr_get;

    // Check if there exists a method with the same name as the signal, which is also a static
    // method in C++ land.
    Shiboken::AutoDecRef homonymousMethod(getDescriptor(signal->homonymousMethod, 0, 0));
    if (PyCFunction_Check(homonymousMethod)
            && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC)) {
#if PY_VERSION_HEX >=  0x03090000
        return PyObject_Call(homonymousMethod, args, kw);
#else
        return PyCFunction_Call(homonymousMethod, args, kw);
#endif
    }

    // Assumes homonymousMethod is not a static method.
    ternaryfunc callFunc = Py_TYPE(signal->homonymousMethod)->tp_call;
    return callFunc(homonymousMethod, args, kw);
}

static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw)
{
    auto PySideSignal = reinterpret_cast<PySideSignalInstance *>(self);
    if (!PySideSignal->d->homonymousMethod) {
        PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable");
        return 0;
    }

    descrgetfunc getDescriptor = Py_TYPE(PySideSignal->d->homonymousMethod)->tp_descr_get;
    Shiboken::AutoDecRef homonymousMethod(getDescriptor(PySideSignal->d->homonymousMethod, PySideSignal->d->source, 0));
#if PY_VERSION_HEX >=  0x03090000
        return PyObject_Call(homonymousMethod, args, kw);
#else
    return PyCFunction_Call(homonymousMethod, args, kw);
#endif
}

static PyObject *metaSignalCheck(PyObject * /* klass */, PyObject *arg)
{
    if (PyType_IsSubtype(Py_TYPE(arg), PySideSignalInstanceTypeF()))
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

} // extern "C"

namespace PySide {
namespace Signal {

static const char *MetaSignal_SignatureStrings[] = {
    "PySide2.QtCore.MetaSignal.__instancecheck__(self,object:object)->bool",
    nullptr}; // Sentinel

static const char *Signal_SignatureStrings[] = {
    "PySide2.QtCore.Signal(self,*types:type,name:str=nullptr,arguments:str=nullptr)",
    nullptr}; // Sentinel

static const char *SignalInstance_SignatureStrings[] = {
    "PySide2.QtCore.SignalInstance.connect(self,slot:object,type:type=nullptr)",
    "PySide2.QtCore.SignalInstance.disconnect(self,slot:object=nullptr)",
    "PySide2.QtCore.SignalInstance.emit(self,*args:typing.Any)",
    nullptr}; // Sentinel

void init(PyObject *module)
{
    if (InitSignatureStrings(PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0)
        return;
    Py_INCREF(PySideMetaSignalTypeF());
    PyModule_AddObject(module, "MetaSignal", reinterpret_cast<PyObject *>(PySideMetaSignalTypeF()));

    if (InitSignatureStrings(PySideSignalTypeF(), Signal_SignatureStrings) < 0)
        return;
    Py_INCREF(PySideSignalTypeF());
    PyModule_AddObject(module, "Signal", reinterpret_cast<PyObject *>(PySideSignalTypeF()));

    if (InitSignatureStrings(PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0)
        return;
    Py_INCREF(PySideSignalInstanceTypeF());
    PyModule_AddObject(module, "SignalInstance", reinterpret_cast<PyObject *>(PySideSignalInstanceTypeF()));
}

bool checkType(PyObject *pyObj)
{
    if (pyObj)
        return PyType_IsSubtype(Py_TYPE(pyObj), PySideSignalTypeF());
    return false;
}

void updateSourceObject(PyObject *source)
{
    PyTypeObject *objType = reinterpret_cast<PyTypeObject *>(PyObject_Type(source));

    Py_ssize_t pos = 0;
    PyObject *value;
    PyObject *key;

    while (PyDict_Next(objType->tp_dict, &pos, &key, &value)) {
        if (PyObject_TypeCheck(value, PySideSignalTypeF())) {
            Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF())));
            instanceInitialize(signalInstance.cast<PySideSignalInstance *>(), key, reinterpret_cast<PySideSignal *>(value), source, 0);
            PyObject_SetAttr(source, key, signalInstance);
        }
    }

    Py_XDECREF(objType);
}

QByteArray getTypeName(PyObject *type)
{
    if (PyType_Check(type)) {
        if (PyType_IsSubtype(reinterpret_cast<PyTypeObject *>(type),
                             reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()))) {
            auto objType = reinterpret_cast<SbkObjectType *>(type);
            return Shiboken::ObjectType::getOriginalName(objType);
        }
        // Translate python types to Qt names
        auto objType = reinterpret_cast<PyTypeObject *>(type);
        if (Shiboken::String::checkType(objType))
            return QByteArrayLiteral("QString");
        if (objType == &PyInt_Type)
            return QByteArrayLiteral("int");
        if (objType == &PyLong_Type)
            return QByteArrayLiteral("long");
        if (objType == &PyFloat_Type)
            return QByteArrayLiteral("double");
        if (objType == &PyBool_Type)
            return QByteArrayLiteral("bool");
        if (objType == &PyList_Type)
            return QByteArrayLiteral("QVariantList");
        if (Py_TYPE(objType) == SbkEnumType_TypeF())
            return Shiboken::Enum::getCppName(objType);
        return QByteArrayLiteral("PyObject");
    }
    if (type == Py_None) // Must be checked before as Shiboken::String::check accepts Py_None
        return voidType();
    if (Shiboken::String::check(type)) {
        QByteArray result = Shiboken::String::toCString(type);
        if (result == "qreal")
            result = sizeof(qreal) == sizeof(double) ? "double" : "float";
        return result;
    }
    return QByteArray();
}

static QByteArray buildSignature(const QByteArray &name, const QByteArray &signature)
{
    return QMetaObject::normalizedSignature(name + '(' + signature + ')');
}

static QByteArray parseSignature(PyObject *args)
{
    if (args && (Shiboken::String::check(args) || !PySequence_Check(args)))
        return getTypeName(args);

    QByteArray signature;
    for (Py_ssize_t i = 0, i_max = PySequence_Size(args); i < i_max; i++) {
        Shiboken::AutoDecRef arg(PySequence_GetItem(args, i));
        const auto typeName = getTypeName(arg);
        if (!typeName.isEmpty()) {
            if (!signature.isEmpty())
                signature += ',';
            signature += typeName;
        }
    }
    return signature;
}

static void appendSignature(PySideSignal *self, const SignalSignature &signature)
{
    self->data->signatures.append({signature.m_parameterTypes, signature.m_attributes});
}

static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySideSignal *data, PyObject *source, int index)
{
    self->d = new PySideSignalInstancePrivate;
    PySideSignalInstancePrivate *selfPvt = self->d;
    selfPvt->next = nullptr;
    if (data->data->signalName.isEmpty())
        data->data->signalName = Shiboken::String::toCString(name);
    selfPvt->signalName = data->data->signalName;

    selfPvt->source = source;
    const auto &signature = data->data->signatures.at(index);
    selfPvt->signature = buildSignature(self->d->signalName, signature.signature);
    selfPvt->attributes = signature.attributes;
    selfPvt->homonymousMethod = 0;
    if (data->homonymousMethod) {
        selfPvt->homonymousMethod = data->homonymousMethod;
        Py_INCREF(selfPvt->homonymousMethod);
    }
    index++;

    if (index < data->data->signatures.size()) {
        selfPvt->next = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF());
        instanceInitialize(selfPvt->next, name, data, source, index);
    }
}

PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *object)
{
    PySideSignalInstance *instance = PyObject_New(PySideSignalInstance,
                                                  PySideSignalInstanceTypeF());
    instanceInitialize(instance, name, self, object, 0);
    auto sbkObj = reinterpret_cast<SbkObject *>(object);
    if (!Shiboken::Object::wasCreatedByPython(sbkObj))
        Py_INCREF(object); // PYSIDE-79: this flag was crucial for a wrapper call.
    return instance;
}

bool connect(PyObject *source, const char *signal, PyObject *callback)
{
    Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source,
                                                   PySide::PyName::qtConnect()));
    if (pyMethod.isNull())
        return false;

    Shiboken::AutoDecRef pySignature(Shiboken::String::fromCString(signal));
    Shiboken::AutoDecRef pyArgs(PyTuple_Pack(3, source, pySignature.object(), callback));
    PyObject *result =  PyObject_CallObject(pyMethod, pyArgs);
    if (result == Py_False) {
        PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s, to python callable object.", signal);
        Py_DECREF(result);
        result = 0;
    }
    return result;
}

PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod>& methodList)
{
    PySideSignalInstance *root = nullptr;
    PySideSignalInstance *previous = nullptr;
    for (const QMetaMethod &m : methodList) {
        PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF());
        if (!root)
            root = item;

        if (previous)
            previous->d->next = item;

        item->d = new PySideSignalInstancePrivate;
        PySideSignalInstancePrivate *selfPvt = item->d;
        selfPvt->source = source;
        Py_INCREF(selfPvt->source); // PYSIDE-79: an INCREF is missing.
        QByteArray cppName(m.methodSignature());
        cppName.truncate(cppName.indexOf('('));
        // separe SignalName
        selfPvt->signalName = cppName;
        selfPvt->signature = m.methodSignature();
        selfPvt->attributes = m.attributes();
        selfPvt->homonymousMethod = 0;
        selfPvt->next = 0;
    }
    return root;
}

template<typename T>
static typename T::value_type join(T t, const char *sep)
{
    typename T::value_type res;
    if (t.isEmpty())
        return res;

    typename T::const_iterator it = t.begin();
    typename T::const_iterator end = t.end();
    res += *it;
    ++it;

    while (it != end) {
        res += sep;
        res += *it;
        ++it;
    }
    return res;
}

static void _addSignalToWrapper(SbkObjectType *wrapperType, const char *signalName, PySideSignal *signal)
{
    auto typeDict = reinterpret_cast<PyTypeObject *>(wrapperType)->tp_dict;
    PyObject *homonymousMethod;
    if ((homonymousMethod = PyDict_GetItemString(typeDict, signalName))) {
        Py_INCREF(homonymousMethod);
        signal->homonymousMethod = homonymousMethod;
    }
    PyDict_SetItemString(typeDict, signalName, reinterpret_cast<PyObject *>(signal));
}

// This function is used by qStableSort to promote empty signatures
static bool compareSignals(const SignalSignature &sig1, const SignalSignature &)
{
    return sig1.m_parameterTypes.isEmpty();
}

static PyObject *buildQtCompatible(const QByteArray &signature)
{
    const auto ba = QT_SIGNAL_SENTINEL + signature;
    return Shiboken::String::fromStringAndSize(ba, ba.size());
}

void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject)
{
    typedef QHash<QByteArray, QList<SignalSignature> > SignalSigMap;
    SignalSigMap signalsFound;
    for (int i = metaObject->methodOffset(), max = metaObject->methodCount(); i < max; ++i) {
        QMetaMethod method = metaObject->method(i);

        if (method.methodType() == QMetaMethod::Signal) {
            QByteArray methodName(method.methodSignature());
            methodName.chop(methodName.size() - methodName.indexOf('('));
            SignalSignature signature;
            signature.m_parameterTypes = join(method.parameterTypes(), ",");
            if (method.attributes() & QMetaMethod::Cloned)
                signature.m_attributes = QMetaMethod::Cloned;
            signalsFound[methodName] << signature;
        }
    }

    SignalSigMap::Iterator it = signalsFound.begin();
    SignalSigMap::Iterator end = signalsFound.end();
    for (; it != end; ++it) {
        PySideSignal *self = PyObject_New(PySideSignal, PySideSignalTypeF());
        self->data = new PySideSignalData;
        self->data->signalName = it.key();
        self->homonymousMethod = 0;

        // Empty signatures comes first! So they will be the default signal signature
        std::stable_sort(it.value().begin(), it.value().end(), &compareSignals);
        SignalSigMap::mapped_type::const_iterator j = it.value().begin();
        SignalSigMap::mapped_type::const_iterator endJ = it.value().end();
        for (; j != endJ; ++j) {
            const SignalSignature &sig = *j;
            appendSignature(self, sig);
        }

        _addSignalToWrapper(pyObj, it.key(), self);
        Py_DECREF(reinterpret_cast<PyObject *>(self));
    }
}

PyObject *getObject(PySideSignalInstance *signal)
{
    return signal->d->source;
}

const char *getSignature(PySideSignalInstance *signal)
{
    return signal->d->signature;
}

QStringList getArgsFromSignature(const char *signature, bool *isShortCircuit)
{
    QString qsignature = QString::fromLatin1(signature).trimmed();
    QStringList result;

    if (isShortCircuit)
        *isShortCircuit = !qsignature.contains(QLatin1Char('('));
    if (qsignature.contains(QLatin1String("()")) || qsignature.contains(QLatin1String("(void)")))
        return result;
    if (qsignature.endsWith(QLatin1Char(')'))) {
        const int paren = qsignature.indexOf(QLatin1Char('('));
        if (paren >= 0) {
            qsignature.chop(1);
            qsignature.remove(0, paren + 1);
            result = qsignature.split(QLatin1Char(','));
            for (QString &type : result)
                type = type.trimmed();
        }
    }
    return result;
}

QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName)
{
    QByteArray functionName;
    int numArgs = -1;
    bool useSelf = false;
    bool isMethod = PyMethod_Check(callback);
    bool isFunction = PyFunction_Check(callback);

    if (isMethod || isFunction) {
        PyObject *function = isMethod ? PyMethod_GET_FUNCTION(callback) : callback;
        auto objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function));
        functionName = Shiboken::String::toCString(PepFunction_GetName(function));
        useSelf = isMethod;
        numArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode);
    } else if (PyCFunction_Check(callback)) {
        const PyCFunctionObject *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback);
        functionName = PepCFunction_GET_NAMESTR(funcObj);
        useSelf = PyCFunction_GET_SELF(funcObj);
        const int flags = PyCFunction_GET_FLAGS(funcObj);

        if (receiver) {
            //Search for signature on metaobject
            const QMetaObject *mo = receiver->metaObject();
            QByteArray prefix(functionName);
            prefix += '(';
            for (int i = 0; i < mo->methodCount(); i++) {
                QMetaMethod me = mo->method(i);
                if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) &&
                    QMetaObject::checkConnectArgs(signal, me.methodSignature())) {
                    numArgs = me.parameterTypes().size() + useSelf;
                    break;
                }
           }
        }

        if (numArgs == -1) {
            if (flags & METH_VARARGS)
                numArgs = -1;
            else if (flags & METH_NOARGS)
                numArgs = 0;
        }
    } else if (PyCallable_Check(callback)) {
        functionName = "__callback" + QByteArray::number((qlonglong)callback);
    }

    Q_ASSERT(!functionName.isEmpty());

    bool isShortCircuit = false;

    const QString functionNameS = QLatin1String(functionName);
    QString signature = encodeName ? codeCallbackName(callback, functionNameS) : functionNameS;
    QStringList args = getArgsFromSignature(signal, &isShortCircuit);

    if (!isShortCircuit) {
        signature.append(QLatin1Char('('));
        if (numArgs == -1)
            numArgs = std::numeric_limits<int>::max();
        while (args.count() && (args.count() > (numArgs - useSelf))) {
            args.removeLast();
        }
        signature.append(args.join(QLatin1Char(',')));
        signature.append(QLatin1Char(')'));
    }
    return signature;
}

bool isQtSignal(const char *signal)
{
    return (signal && signal[0] == QT_SIGNAL_SENTINEL);
}

bool checkQtSignal(const char *signal)
{
    if (!isQtSignal(signal)) {
        PyErr_SetString(PyExc_TypeError, "Use the function PySide2.QtCore.SIGNAL on signals");
        return false;
    }
    return true;
}

QString codeCallbackName(PyObject *callback, const QString &funcName)
{
    if (PyMethod_Check(callback)) {
        PyObject *self = PyMethod_GET_SELF(callback);
        PyObject *func = PyMethod_GET_FUNCTION(callback);
        return funcName + QString::number(quint64(self), 16) + QString::number(quint64(func), 16);
    }
    return funcName + QString::number(quint64(callback), 16);
}

QByteArray voidType()
{
    return QByteArrayLiteral("void");
}

} //namespace Signal
} //namespace PySide

