/****************************************************************************
**
** Copyright (C) 2017 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 "basewrapper.h"

extern "C"
{

/*
 * The documentation is located in file signature_doc.rst
 */

#include "signature.h"
#include <structmember.h>

// These constants were needed in former versions of the module:
#define PYTHON_HAS_QUALNAME             (PY_VERSION_HEX >= 0x03030000)
#define PYTHON_HAS_UNICODE              (PY_VERSION_HEX >= 0x03000000)
#define PYTHON_HAS_WEAKREF_PYCFUNCTION  (PY_VERSION_HEX >= 0x030500A0)
#define PYTHON_IS_PYTHON3               (PY_VERSION_HEX >= 0x03000000)
#define PYTHON_HAS_KEYWORDONLY          (PYTHON_IS_PYTHON3)
#define PYTHON_USES_PERCENT_V_FORMAT    (PYTHON_IS_PYTHON3)
#define PYTHON_HAS_DESCR_REDUCE         (PY_VERSION_HEX >= 0x03040000)
#define PYTHON_HAS_METH_REDUCE          (PYTHON_HAS_DESCR_REDUCE)
#define PYTHON_NEEDS_ITERATOR_FLAG      (!PYTHON_IS_PYTHON3)
#define PYTHON_EXPOSES_METHODDESCR      (PYTHON_IS_PYTHON3)
#define PYTHON_NO_TYPE_IN_FUNCTIONS     (!PYTHON_IS_PYTHON3 || Py_LIMITED_API)

// These constants are still in use:
#define PYTHON_USES_D_COMMON            (PY_VERSION_HEX >= 0x03020000)

typedef struct safe_globals_struc {
    // init part 1: get arg_dict
    PyObject *helper_module;
    PyObject *arg_dict;
    PyObject *map_dict;
    // init part 2: run module
    PyObject *sigparse_func;
    PyObject *createsig_func;
} safe_globals_struc, *safe_globals;

static safe_globals pyside_globals = 0;

static PyObject *GetSignature_Function(PyCFunctionObject *);
static PyObject *GetSignature_TypeMod(PyObject *);

static PyObject *PySide_BuildSignatureProps(PyObject *class_mod);

const char helper_module_name[] = "signature_loader";
const char bootstrap_name[] = "bootstrap";
const char arg_name[] = "pyside_arg_dict";
const char func_name[] = "pyside_type_init";

static PyObject *
CreateSignature(PyObject *props, const char *sig_kind)
{
    /*
     * Here is the new function to create all signatures. It simply calls
     * into Python and creates a signature object for a dummy-function.
     * This is so much simpler than using all the attributes explicitly
     * to support '_signature_is_functionlike()'.
     */
    return PyObject_CallFunction(pyside_globals->createsig_func,
                                 (char *)"(Os)", props, sig_kind);
}

static PyObject *
pyside_cf_get___signature__(PyObject *func)
{
    return GetSignature_Function((PyCFunctionObject *)func);
}

static PyObject *
pyside_sm_get___signature__(PyObject *sm)
{
    PyObject *func, *ret;

    func = PyObject_GetAttrString(sm, "__func__");
    ret = GetSignature_Function((PyCFunctionObject *)func);
    Py_XDECREF(func);
    return ret;
}

#ifdef Py_LIMITED_API

static int
build_qualname_to_func(PyObject *obtype)
{
    PyTypeObject *type = (PyTypeObject *)obtype;
    PyMethodDef *meth = type->tp_methods;

    if (meth == 0)
        return 0;

    for (; meth->ml_name != NULL; meth++) {
        PyObject *func = PyCFunction_NewEx(meth, obtype, NULL);
        PyObject *qualname = PyObject_GetAttrString(func, "__qualname__");
        if (func == NULL || qualname == NULL) {
            return -1;
        }
        if (PyDict_SetItem(pyside_globals->map_dict, qualname, func) < 0) {
            return -1;
        }
        Py_DECREF(func);
        Py_DECREF(qualname);
    }
    return 0;
}

static PyObject *
qualname_to_typename(PyObject *qualname)
{
    PyObject *func = PyObject_GetAttrString(qualname, "split");
    PyObject *list = func ? PyObject_CallFunction(func, (char *)"(s)", ".")
                          : NULL;
    PyObject *res = list ? PyList_GetItem(list, 0) : NULL;
    Py_XINCREF(res);
    Py_XDECREF(func);
    Py_XDECREF(list);
    return res;
}

static PyObject *
qualname_to_func(PyObject *ob)
{
    /*
     * If we have __qualname__, then we can easily build a mapping
     * from __qualname__ to PyCFunction. This is necessary when
     * the limited API does not let us go easily from descriptor
     * to PyMethodDef.
     */
    PyObject *ret;
    PyObject *qualname = PyObject_GetAttrString((PyObject *)ob,
                                                "__qualname__");
    if (qualname != NULL) {
        ret = PyDict_GetItem(pyside_globals->map_dict, qualname);
        if (ret == NULL) {
            // do a lazy initialization
            PyObject *type_name = qualname_to_typename(qualname);
            PyObject *type = PyDict_GetItem(pyside_globals->map_dict,
                                            type_name);
            Py_XDECREF(type_name);
            if (type == NULL)
                Py_RETURN_NONE;
            if (build_qualname_to_func(type) < 0)
                return NULL;
            ret = PyDict_GetItem(pyside_globals->map_dict, qualname);
        }
        Py_XINCREF(ret);
        Py_DECREF(qualname);
    }
    else
        Py_RETURN_NONE;
    return ret;
}
#endif

static PyObject *
pyside_md_get___signature__(PyObject *ob)
{
    PyObject *func;
    PyObject *result;
#ifndef Py_LIMITED_API
    PyMethodDescrObject *descr = (PyMethodDescrObject *)ob;

# if PYTHON_USES_D_COMMON
    func = PyCFunction_NewEx(descr->d_method,
                             (PyObject *)descr->d_common.d_type, NULL);
# else
    func = PyCFunction_NewEx(descr->d_method,
                             (PyObject *)descr->d_type, NULL);
# endif
#else
    /*
     * With limited access, we cannot use the fields of a method descriptor,
     * but in Python 3 we have the __qualname__ field which allows us to
     * grab the method object from our registry.
     */
    func = qualname_to_func(ob);
#endif
    if (func == Py_None)
        return Py_None;
    if (func == NULL)
        Py_FatalError("missing mapping in MethodDescriptor");
    result = pyside_cf_get___signature__(func);
    Py_DECREF(func);
    return result;
}

static PyObject *
pyside_tp_get___signature__(PyObject *typemod)
{
    return GetSignature_TypeMod(typemod);
}

static PyObject *
GetSignature_Function(PyCFunctionObject *func)
{
    PyObject *typemod, *type_name, *dict, *props, *value, *selftype;
    PyObject *func_name = PyObject_GetAttrString((PyObject *)func, "__name__");
    const char *sig_kind;
    int flags;

    selftype = PyCFunction_GET_SELF((PyObject *)func);
    if (selftype == NULL)
        selftype = PyDict_GetItem(pyside_globals->map_dict, (PyObject *)func);
    if (selftype == NULL) {
        if (!PyErr_Occurred()) {
            PyErr_Format(PyExc_SystemError,
                "the signature for \"%s\" should exist",
                PepCFunction_GET_NAMESTR(func)
                );
        }
        return NULL;
    }
    if ((PyType_Check(selftype) || PyModule_Check(selftype)))
        typemod = selftype;
    else
        typemod = (PyObject *)Py_TYPE(selftype);
    type_name = PyObject_GetAttrString(typemod, "__name__");
    if (type_name == NULL)
        Py_RETURN_NONE;
    dict = PyDict_GetItem(pyside_globals->arg_dict, type_name);
    Py_DECREF(type_name);
    if (dict == NULL)
        Py_RETURN_NONE;
    if (PyTuple_Check(dict)) {
        /*
         * We do the initialization lazily.
         * This has also the advantage that we can freely import PySide.
         */
        dict = PySide_BuildSignatureProps(typemod);
        if (dict == NULL)
            Py_RETURN_NONE;
    }
    props = PyDict_GetItem(dict, func_name);
    if (props == NULL)
        Py_RETURN_NONE;
    flags = PyCFunction_GET_FLAGS((PyObject *)func);
    if (flags & METH_CLASS)
        sig_kind = "classmethod";
    else if (flags & METH_STATIC)
        sig_kind = "staticmethod";
    else
        sig_kind = "method";
    value = PyDict_GetItemString(props, sig_kind);
    if (value == NULL) {
        // we need to compute a signature object
        value = CreateSignature(props, sig_kind);
        if (value != NULL) {
            if (PyDict_SetItemString(props, sig_kind, value) < 0)
                return NULL;
        }
        else
            Py_RETURN_NONE;
    }
    return Py_INCREF(value), value;
}

static PyObject *
GetSignature_TypeMod(PyObject *ob)
{
    PyObject *ob_name, *dict, *props, *value;
    const char *sig_kind;

    ob_name = PyObject_GetAttrString(ob, "__name__");
    dict = PyDict_GetItem(pyside_globals->arg_dict, ob_name);
    if (dict == NULL)
        Py_RETURN_NONE;
    if (PyTuple_Check(dict)) {
        dict = PySide_BuildSignatureProps(ob);
        if (dict == NULL) {
            Py_RETURN_NONE;
        }
    }
    props = PyDict_GetItem(dict, ob_name);
    Py_DECREF(ob_name);
    if (props == NULL)
        Py_RETURN_NONE;
    sig_kind = "method";
    value = PyDict_GetItemString(props, sig_kind);
    if (value == NULL) {
        // we need to compute a signature object
        value = CreateSignature(props, sig_kind);
        if (value != NULL) {
            if (PyDict_SetItemString(props, sig_kind, value) < 0)
                return NULL;
        }
        else
            Py_RETURN_NONE;
    }
    return Py_INCREF(value), value;
}


static const char PySide_PythonCode[] =
    "from __future__ import print_function, absolute_import\n"
    "import sys, os, traceback\n"

    "pyside_package_dir = os.environ.get('PYSIDE_PACKAGE_DIR', '.')\n"
    "__file__ = os.path.join(pyside_package_dir, 'support', 'signature', 'loader.py')\n"

    "def bootstrap():\n"
    "    try:\n"
    "        with open(__file__) as _f:\n"
    "            exec(compile(_f.read(), __file__, 'exec'))\n"
    "    except Exception as e:\n"
    "        print('Exception:', e)\n"
    "        traceback.print_exc(file=sys.stdout)\n"
    "    globals().update(locals())\n"
     ;

static safe_globals_struc *
init_phase_1(void)
{
    safe_globals_struc *p;
    PyObject *d, *v;

    p = (safe_globals_struc *)malloc(sizeof(safe_globals_struc));
    if (p == NULL)
        goto error;
    p->helper_module = PyImport_AddModule((char *) helper_module_name);
    if (p->helper_module == NULL)
        goto error;

    // Initialize the module
    d = PyModule_GetDict(p->helper_module);
    if (PyDict_SetItemString(d, "__builtins__", PyEval_GetBuiltins()) < 0)
        goto error;
    v = PyRun_String(PySide_PythonCode, Py_file_input, d, d);
    if (v == NULL)
        goto error;
    Py_DECREF(v);

    // build a dict for diverse mappings
    p->map_dict = PyDict_New();
    if (p->map_dict == NULL)
        goto error;

    // Build a dict for the prepared arguments
    p->arg_dict = PyDict_New();
    if (p->arg_dict == NULL)
        goto error;
    if (PyObject_SetAttrString(p->helper_module, arg_name, p->arg_dict) < 0)
        goto error;
    return p;

error:
    PyErr_SetString(PyExc_SystemError, "could not initialize part 1");
    return NULL;
}

static int
init_phase_2(safe_globals_struc *p)
{
    PyObject *bootstrap_func;

    bootstrap_func = PyObject_GetAttrString(p->helper_module, bootstrap_name);
    if (bootstrap_func == NULL)
        goto error;
    if (PyObject_CallFunction(bootstrap_func, (char *)"()") == NULL)
        goto error;
    // now the loader is initialized
    p->sigparse_func = PyObject_GetAttrString(p->helper_module, func_name);
    if (p->sigparse_func == NULL)
        goto error;
    p->createsig_func = PyObject_GetAttrString(p->helper_module, "create_signature");
    if (p->createsig_func == NULL)
        goto error;
    return 0;

error:
    PyErr_SetString(PyExc_SystemError, "could not initialize part 2");
    return -1;
}

static int
add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp)
{
    PyObject *dict = type->tp_dict;

    for (; gsp->name != NULL; gsp++) {
        PyObject *descr;
        if (PyDict_GetItemString(dict, gsp->name))
            continue;
        descr = PyDescr_NewGetSet(type, gsp);
        if (descr == NULL)
            return -1;
        if (PyDict_SetItemString(dict, gsp->name, descr) < 0) {
            Py_DECREF(descr);
            return -1;
        }
        Py_DECREF(descr);
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////
//
// Augmenting builtin types with a __signature__ attribute.
//
// This is a harmless change to Python, similar like __text_signature__.
// We could avoid it, but then we would need to copy quite some module
// initialization functions which are pretty version- and word size
// dependent. I think this little patch is the lesser of the two evils.
//
// Please note that in fact we are modifying 'type', the metaclass of all
// objects, because we add new functionality.
//
static PyGetSetDef new_PyCFunction_getsets[] = {
    {(char *) "__signature__", (getter)pyside_cf_get___signature__},
    {0}
};

static PyGetSetDef new_PyStaticMethod_getsets[] = {
    {(char *) "__signature__", (getter)pyside_sm_get___signature__},
    {0}
};

static PyGetSetDef new_PyMethodDescr_getsets[] = {
    {(char *) "__signature__", (getter)pyside_md_get___signature__},
    {0}
};

static PyGetSetDef new_PyType_getsets[] = {
    {(char *) "__signature__", (getter)pyside_tp_get___signature__},
    {0}
};

////////////////////////////////////////////////////////////////////////////
//
// This special Type_Ready does certain initializations earlier with
// our new version.
//

#ifndef _WIN32
////////////////////////////////////////////////////////////////////////////
// a stack trace for linux-like platforms
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void handler(int sig) {
    void *array[30];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 30);

    // print out all the frames to stderr
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(array, size, STDERR_FILENO);
    exit(1);
}

////////////////////////////////////////////////////////////////////////////
#endif // _WIN32

static int
PySideType_Ready(PyTypeObject *type)
{
    PyObject *md;
    static int init_done = 0;

    if (!init_done) {
        // Python2 does not expose certain types. We look them up:
        // PyMethodDescr_Type       'type(str.__dict__["split"])'
        // PyClassMethodDescr_Type. 'type(dict.__dict__["fromkeys"])'
        // The latter is not needed until we use class methods in PySide.
        md = PyObject_GetAttrString((PyObject *)&PyString_Type, "split");
        if (md == NULL
            || PyType_Ready(Py_TYPE(md)) < 0
            || add_more_getsets(Py_TYPE(md), new_PyMethodDescr_getsets) < 0
            || add_more_getsets(&PyCFunction_Type, new_PyCFunction_getsets) < 0
            || add_more_getsets(PepStaticMethod_TypePtr, new_PyStaticMethod_getsets) < 0
            || add_more_getsets(&PyType_Type, new_PyType_getsets) < 0)
            return -1;
        Py_DECREF(md);
#ifndef _WIN32
        // We enable the stack trace in CI, only.
        const char *testEnv = getenv("QTEST_ENVIRONMENT");
        if (testEnv && strstr(testEnv, "ci"))
            signal(SIGSEGV, handler);   // install our handler
#endif // _WIN32
        init_done = 1;
    }
    return PyType_Ready(type);
}

static int
build_func_to_type(PyObject *obtype)
{
    PyTypeObject *type = (PyTypeObject *)obtype;
    PyObject *dict = type->tp_dict;
    PyMethodDef *meth = type->tp_methods;

    if (meth == 0)
        return 0;

    for (; meth->ml_name != NULL; meth++) {
        if (meth->ml_flags & METH_STATIC) {
            PyObject *descr = PyDict_GetItemString(dict, meth->ml_name);
            if (descr == NULL)
                return -1;
            PyObject *func = PyObject_GetAttrString(descr, "__func__");
            if (func == NULL ||
                PyDict_SetItem(pyside_globals->map_dict, func, obtype) < 0)
                return -1;
            Py_DECREF(func);
        }
    }
    return 0;
}

static int
PySide_BuildSignatureArgs(PyObject *module, PyObject *type,
                          const char *signatures)
{
    PyObject *type_name, *arg_tup;
    const char *name = NULL;
    static int init_done = 0;

    if (!init_done) {
        pyside_globals = init_phase_1();
        if (pyside_globals == NULL)
            return -1;
        init_done = 1;
    }
    arg_tup = Py_BuildValue("(Os)", type, signatures);
    if (arg_tup == NULL)
        return -1;
    if (!PyModule_Check(module))
        return 0;
    name = PyModule_GetName(module);
    if (name == NULL)
        return -1;
    if (strncmp(name, "PySide2.Qt", 10) != 0)
        return 0;
    /*
     * Normally, we would now just call the Python function with the
     * arguments and then continue processing.
     * But it is much better to delay the second part until it is
     * really needed. Why?
     *
     * - by doing it late, we save initialization time when no signatures
     *   are requested,
     * - by calling the python function late, we can freely import PySide
     *   without recursion problems.
     */
    type_name = PyObject_GetAttrString(type, "__name__");
    if (type_name == NULL)
        return -1;
    if (PyDict_SetItem(pyside_globals->arg_dict, type_name, arg_tup) < 0)
        return -1;
    /*
     * We record also a mapping from type name to type. This helps to lazily
     * initialize the Py_LIMITED_API in qualname_to_func().
     */
    if (PyDict_SetItem(pyside_globals->map_dict, type_name, type) < 0)
        return -1;
    return 0;
}

static PyObject *
PySide_BuildSignatureProps(PyObject *classmod)
{
    PyObject *arg_tup, *dict, *type_name;
    static int init_done = 0;

    if (!init_done) {
        if (init_phase_2(pyside_globals) < 0)
            return NULL;
        init_done = 1;
    }
    /*
     * Here is the second part of the function.
     * This part will be called on-demand when needed by some attribute.
     * We simply pick up the arguments that we stored here and replace
     * them by the function result.
     */
    type_name = PyObject_GetAttrString(classmod, "__name__");
    if (type_name == NULL)
        return NULL;
    arg_tup = PyDict_GetItem(pyside_globals->arg_dict, type_name);
    if (arg_tup == NULL)
        return NULL;
    dict = PyObject_CallObject(pyside_globals->sigparse_func, arg_tup);
    if (dict == NULL)
        return NULL;

    // We replace the arguments by the result dict.
    if (PyDict_SetItem(pyside_globals->arg_dict, type_name, dict) < 0)
        return NULL;
    Py_DECREF(type_name);
    return dict;
}

int
SbkSpecial_Type_Ready(PyObject *module, PyTypeObject *type,
                      const char *signatures)
{
    int ret;
    if (PySideType_Ready(type) < 0)
        return -1;
    ret = PySide_BuildSignatureArgs(module, (PyObject *)type, signatures);
    if (ret < 0) {
        PyErr_Print();
        PyErr_SetNone(PyExc_ImportError);
    }
    return ret;
}

static int
PySide_FinishSignatures(PyObject *module, const char *signatures)
{
    const char *name = NULL;

    // CRUCIAL: Do not call this on "testbinding":
    // The module is different and should not get signatures, anyway.
    name = PyModule_GetName(module);
    if (name == NULL)
        return -1;
    if (strncmp(name, "PySide2.Qt", 10) != 0)
        return 0;

    // we abuse the call for types, since they both have a __name__ attribute.
    if (PySide_BuildSignatureArgs(module, module, signatures) < 0)
        return -1;

    /*
     * Python2 does not abuse the 'm_self' field for the type. So we need to
     * supply this for all static methods.
     *
     * Note: This function crashed when called from PySide_BuildSignatureArgs.
     * Probably this was too early.
     *
     * Pep384: We need to switch this always on since we have no access
     * to the PyCFunction attributes. Therefore I simplified things
     * and always use our own mapping.
     */
    {
        PyObject *key, *value;
        Py_ssize_t pos = 0;
        PyObject *dict = PyModule_GetDict(module);

        if (dict == NULL)
            return -1;

        while (PyDict_Next(dict, &pos, &key, &value)) {
            if (PyType_Check(value)) {
                PyObject *type = value;
                if (build_func_to_type(type) < 0)
                    return -1;
            }
        }
    }
    return 0;
}

void
FinishSignatureInitialization(PyObject *module, const char *signatures)
{
    if (PySide_FinishSignatures(module, signatures) < 0) {
        PyErr_Print();
        PyErr_SetNone(PyExc_ImportError);
    }
}

} //extern "C"
