// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

////////////////////////////////////////////////////////////////////////////
//
// signature.cpp
// -------------
//
// This is the main file of the signature module.
// It contains the most important functions and avoids confusion
// by moving many helper functions elsewhere.
//
// General documentation can be found in `signature_doc.rst`.
//

#include "signature.h"
#include "signature_p.h"

#include "basewrapper.h"
#include "autodecref.h"
#include "sbkstring.h"
#include "sbkstaticstrings.h"
#include "sbkstaticstrings_p.h"
#include "sbkfeature_base.h"

#include <structmember.h>

using namespace Shiboken;

extern "C"
{

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

PyObject *GetClassOrModOf(PyObject *ob)
{
    /*
     * Return the type or module of a function or type.
     * The purpose is finally to use the name of the object.
     */
    if (PyType_Check(ob)) {
        // PySide-928: The type case must do refcounting like the others as well.
        Py_INCREF(ob);
        return ob;
    }
#ifdef PYPY_VERSION
    // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction.
    if (Py_TYPE(ob) == PepBuiltinMethod_TypePtr)
        return _get_class_of_bm(ob);
#endif
    if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type))
        return _get_class_of_cf(ob);
    if (Py_TYPE(ob) == PepStaticMethod_TypePtr)
        return _get_class_of_sm(ob);
    if (Py_TYPE(ob) == PepMethodDescr_TypePtr)
        return _get_class_of_descr(ob);
    if (Py_TYPE(ob) == &PyWrapperDescr_Type)
        return _get_class_of_descr(ob);
    Py_FatalError("unexpected type in GetClassOrModOf");
    return nullptr;
}

PyObject *GetTypeKey(PyObject *ob)
{
    assert(PyType_Check(ob) || PyModule_Check(ob));
    /*
     * Obtain a unique key using the module name and the type name.
     *
     * PYSIDE-1286: We use correct __module__ and __qualname__, now.
     */
    AutoDecRef module_name(PyObject_GetAttr(ob, PyMagicName::module()));
    if (module_name.isNull()) {
        // We have no module_name because this is a module ;-)
        PyErr_Clear();
        module_name.reset(PyObject_GetAttr(ob, PyMagicName::name()));
        return Py_BuildValue("O", module_name.object());
    }
    AutoDecRef class_name(PyObject_GetAttr(ob, PyMagicName::qualname()));
    if (class_name.isNull()) {
        Py_FatalError("Signature: missing class name in GetTypeKey");
        return nullptr;
    }
    return Py_BuildValue("(OO)", module_name.object(), class_name.object());
}

static PyObject *empty_dict = nullptr;

PyObject *TypeKey_to_PropsDict(PyObject *type_key)
{
    PyObject *dict = PyDict_GetItem(pyside_globals->arg_dict, type_key);
    if (dict == nullptr) {
        if (empty_dict == nullptr)
            empty_dict = PyDict_New();
        dict = empty_dict;
    }
    if (!PyDict_Check(dict))
        dict = PySide_BuildSignatureProps(type_key);
    return dict;
}

static PyObject *_GetSignature_Cached(PyObject *props, PyObject *func_kind, PyObject *modifier)
{
    // Special case: We want to know the func_kind.
    if (modifier) {
        PyUnicode_InternInPlace(&modifier);
        if (modifier == PyMagicName::func_kind())
            return Py_BuildValue("O", func_kind);
    }

    AutoDecRef key(modifier == nullptr ? Py_BuildValue("O", func_kind)
                                       : Py_BuildValue("(OO)", func_kind, modifier));
    PyObject *value = PyDict_GetItem(props, key);
    if (value == nullptr) {
        // we need to compute a signature object
        value = CreateSignature(props, key);
        if (value != nullptr) {
            if (PyDict_SetItem(props, key, value) < 0)
                // this is an error
                return nullptr;
        }
        else {
            // key not found
            Py_RETURN_NONE;
        }
    }
    return Py_INCREF(value), value;
}

#ifdef PYPY_VERSION
PyObject *GetSignature_Method(PyObject *obfunc, PyObject *modifier)
{
    AutoDecRef obtype_mod(GetClassOrModOf(obfunc));
    AutoDecRef type_key(GetTypeKey(obtype_mod));
    if (type_key.isNull())
        Py_RETURN_NONE;
    PyObject *dict = TypeKey_to_PropsDict(type_key);
    if (dict == nullptr)
        return nullptr;
    AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name()));
    PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr;
    if (props == nullptr)
        Py_RETURN_NONE;
    return _GetSignature_Cached(props, PyName::method(), modifier);
}
#endif

PyObject *GetSignature_Function(PyObject *obfunc, PyObject *modifier)
{
    // make sure that we look into PyCFunction, only...
    if (Py_TYPE(obfunc) == PepFunction_TypePtr)
        Py_RETURN_NONE;
    AutoDecRef obtype_mod(GetClassOrModOf(obfunc));
    AutoDecRef type_key(GetTypeKey(obtype_mod));
    if (type_key.isNull())
        Py_RETURN_NONE;
    PyObject *dict = TypeKey_to_PropsDict(type_key);
    if (dict == nullptr)
        return nullptr;
    AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name()));
    PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr;
    if (props == nullptr)
        Py_RETURN_NONE;

    int flags = PyCFunction_GET_FLAGS(obfunc);
    PyObject *func_kind;
    if (PyModule_Check(obtype_mod.object()))
        func_kind = PyName::function();
    else if (flags & METH_CLASS)
        func_kind = PyName::classmethod();
    else if (flags & METH_STATIC)
        func_kind = PyName::staticmethod();
    else
        func_kind = PyName::method();
    return _GetSignature_Cached(props, func_kind, modifier);
}

PyObject *GetSignature_Wrapper(PyObject *ob, PyObject *modifier)
{
    AutoDecRef func_name(PyObject_GetAttr(ob, PyMagicName::name()));
    AutoDecRef objclass(PyObject_GetAttr(ob, PyMagicName::objclass()));
    AutoDecRef class_key(GetTypeKey(objclass));
    if (func_name.isNull() || objclass.isNull() || class_key.isNull())
        return nullptr;
    PyObject *dict = TypeKey_to_PropsDict(class_key);
    if (dict == nullptr)
        return nullptr;
    PyObject *props = PyDict_GetItem(dict, func_name);
    if (props == nullptr) {
        // handle `__init__` like the class itself
        if (PyUnicode_CompareWithASCIIString(func_name, "__init__") == 0)
            return GetSignature_TypeMod(objclass, modifier);
        Py_RETURN_NONE;
    }
    return _GetSignature_Cached(props, PyName::method(), modifier);
}

PyObject *GetSignature_TypeMod(PyObject *ob, PyObject *modifier)
{
    AutoDecRef ob_name(PyObject_GetAttr(ob, PyMagicName::name()));
    AutoDecRef ob_key(GetTypeKey(ob));

    PyObject *dict = TypeKey_to_PropsDict(ob_key);
    if (dict == nullptr)
        return nullptr;
    PyObject *props = PyDict_GetItem(dict, ob_name);
    if (props == nullptr)
        Py_RETURN_NONE;
    return _GetSignature_Cached(props, PyName::method(), modifier);
}

////////////////////////////////////////////////////////////////////////////
//
// get_signature  --  providing a superior interface
//
// Additional to the interface via `__signature__`, we also provide
// a general function, which allows for different signature layouts.
// The `modifier` argument is a string that is passed in from `loader.py`.
// Configuration what the modifiers mean is completely in Python.
//
// PYSIDE-2101: The __signature__ attribute is gone due to rlcompleter.
//

PyObject *get_signature_intern(PyObject *ob, PyObject *modifier)
{
#ifdef PYPY_VERSION
    // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction.
    if (Py_TYPE(ob) == PepBuiltinMethod_TypePtr) {
        return pyside_bm_get___signature__(ob, modifier);
    }
#endif
    if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type))
        return pyside_cf_get___signature__(ob, modifier);
    if (Py_TYPE(ob) == PepStaticMethod_TypePtr)
        return pyside_sm_get___signature__(ob, modifier);
    if (Py_TYPE(ob) == PepMethodDescr_TypePtr)
        return pyside_md_get___signature__(ob, modifier);
    if (PyType_Check(ob))
        return pyside_tp_get___signature__(ob, modifier);
    if (Py_TYPE(ob) == &PyWrapperDescr_Type)
        return pyside_wd_get___signature__(ob, modifier);
    // For classmethods we use the simple wrapper description implementation.
    if (Py_TYPE(ob) == &PyClassMethodDescr_Type)
        return pyside_wd_get___signature__(ob, modifier);
    return nullptr;
}

static PyObject *get_signature(PyObject * /* self */, PyObject *args)
{
    PyObject *ob;
    PyObject *modifier = nullptr;

    if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier))
        return nullptr;
    if (Py_TYPE(ob) == PepFunction_TypePtr)
        Py_RETURN_NONE;
    PyObject *ret = get_signature_intern(ob, modifier);
    if (ret != nullptr)
        return ret;
    Py_RETURN_NONE;
}

////////////////////////////////////////////////////////////////////////////
//
// feature_import  --  special handling for `from __feature__ import ...`
//
// The actual function is implemented in Python.
// When no features are involved, we redirect to the original import.
// This avoids an extra function level in tracebacks that is irritating.
//

static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject *kwds)
{
    PyObject *ret = PyObject_Call(pyside_globals->feature_import_func, args, kwds);
    if (ret != Py_None)
        return ret;
    // feature_import did not handle it, so call the normal import.
    Py_DECREF(ret);
    static PyObject *builtins = PyEval_GetBuiltins();
    PyObject *import_func = PyDict_GetItemString(builtins, "__orig_import__");
    if (import_func == nullptr) {
        Py_FatalError("builtins has no \"__orig_import__\" function");
    }
    ret = PyObject_Call(import_func, args, kwds);
    if (ret) {
        // PYSIDE-2029: Intercept after the import to search for PySide usage.
        PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func,
                                                      ret, nullptr);
        Py_XDECREF(post);
        if (post == nullptr) {
            Py_DECREF(ret);
            return nullptr;
        }
    }
    return ret;
}

PyMethodDef signature_methods[] = {
    {"__feature_import__", (PyCFunction)feature_import, METH_VARARGS | METH_KEYWORDS, nullptr},
    {"get_signature", (PyCFunction)get_signature, METH_VARARGS,
        "get the signature, passing an optional string parameter"},
    {nullptr, nullptr, 0, nullptr}
};

////////////////////////////////////////////////////////////////////////////
//
// Argument Handling
// -----------------
//
// * PySide_BuildSignatureArgs
//
// Called during class or module initialization.
// The signature strings from the C modules are stored in a dict for
// later use.
//
// * PySide_BuildSignatureProps
//
// Called on demand during signature retieval. This function calls all the way
// through `parser.py` and prepares all properties for the functions of the class.
// The parsed properties can then be used to create signature objects.
//

static int PySide_BuildSignatureArgs(PyObject *obtype_mod, const char *signatures[])
{
    AutoDecRef type_key(GetTypeKey(obtype_mod));
    /*
     * PYSIDE-996: Avoid string overflow in MSVC, which has a limit of
     * 2**15 unicode characters (64 K memory).
     * Instead of one huge string, we take a ssize_t that is the
     * address of a string array. It will not be turned into a real
     * string list until really used by Python. This is quite optimal.
     */
    AutoDecRef numkey(Py_BuildValue("n", signatures));
    if (type_key.isNull() || numkey.isNull()
        || PyDict_SetItem(pyside_globals->arg_dict, type_key, numkey) < 0)
        return -1;
    /*
     * We record also a mapping from type key to type/module. This helps to
     * lazily initialize the Py_LIMITED_API in name_key_to_func().
     */
    return PyDict_SetItem(pyside_globals->map_dict, type_key, obtype_mod) == 0 ? 0 : -1;
}

PyObject *PySide_BuildSignatureProps(PyObject *type_key)
{
    /*
     * 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.
     */
    if (type_key == nullptr)
        return nullptr;
    PyObject *numkey = PyDict_GetItem(pyside_globals->arg_dict, type_key);
    AutoDecRef strings(_address_to_stringlist(numkey));
    if (strings.isNull())
        return nullptr;
    AutoDecRef arg_tup(Py_BuildValue("(OO)", type_key, strings.object()));
    if (arg_tup.isNull())
        return nullptr;
    PyObject *dict = PyObject_CallObject(pyside_globals->pyside_type_init_func, arg_tup);
    if (dict == nullptr) {
        if (PyErr_Occurred())
            return nullptr;
        // No error: return an empty dict.
        if (empty_dict == nullptr)
            empty_dict = PyDict_New();
        return empty_dict;
    }
    // PYSIDE-1019: Build snake case versions of the functions.
    if (insert_snake_case_variants(dict) < 0)
        return nullptr;
    // We replace the arguments by the result dict.
    if (PyDict_SetItem(pyside_globals->arg_dict, type_key, dict) < 0)
        return nullptr;
    return dict;
}
//
////////////////////////////////////////////////////////////////////////////

#ifdef PYPY_VERSION
static bool get_lldebug_flag()
{
    auto *dic = PySys_GetObject("pypy_translation_info");
    int lldebug = PyObject_IsTrue(PyDict_GetItemString(dic, "translation.lldebug"));
    int lldebug0 = PyObject_IsTrue(PyDict_GetItemString(dic, "translation.lldebug0"));
    return lldebug || lldebug0;
}

#endif

static int PySide_FinishSignatures(PyObject *module, const char *signatures[])
{
#ifdef PYPY_VERSION
    static const bool have_problem = get_lldebug_flag();
    if (have_problem)
        return 0; // crash with lldebug at `PyDict_Next`
#endif
    /*
     * Initialization of module functions and resolving of static methods.
     */
    const char *name = PyModule_GetName(module);
    if (name == nullptr)
        return -1;

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

    /*
     * Note: This function crashed when called from PySide_BuildSignatureArgs.
     * Probably this was an import timing problem.
     *
     * 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, *func, *obdict = PyModule_GetDict(module);
    Py_ssize_t pos = 0;

    while (PyDict_Next(obdict, &pos, &key, &func))
        if (PyCFunction_Check(func))
            if (PyDict_SetItem(pyside_globals->map_dict, func, module) < 0)
                return -1;
    // The finish_import function will not work the first time since phase 2
    // was not yet run. But that is ok, because the first import is always for
    // the shiboken module (or a test module).
    if (pyside_globals->finish_import_func == nullptr) {
        assert(strncmp(name, "PySide6.", 8) != 0);
        return 0;
    }
    AutoDecRef ret(PyObject_CallFunction(
        pyside_globals->finish_import_func, "(O)", module));
    return ret.isNull() ? -1 : 0;
}

////////////////////////////////////////////////////////////////////////////
//
// External functions interface
//
// These are exactly the supported functions from `signature.h`.
//

int InitSignatureStrings(PyTypeObject *type, const char *signatures[])
{
    // PYSIDE-2404: This function now also builds the mapping for static methods.
    //              It was one missing spot to let Lazy import work.
    init_shibokensupport_module();
    auto *ob_type = reinterpret_cast<PyObject *>(type);
    int ret = PySide_BuildSignatureArgs(ob_type, signatures);
    if (ret < 0 || _build_func_to_type(ob_type) < 0) {
        PyErr_Print();
        PyErr_SetNone(PyExc_ImportError);
    }
    return ret;
}

void FinishSignatureInitialization(PyObject *module, const char *signatures[])
{
    /*
     * This function is called at the very end of a module initialization.
     * We now patch certain types to support the __signature__ attribute,
     * initialize module functions and resolve static methods.
     *
     * Still, it is not possible to call init phase 2 from here,
     * because the import is still running. Do it from Python!
     */
#ifndef PYPY_VERSION
    static const bool patch_types = true;
#else
    // PYSIDE-535: On PyPy we cannot patch builtin types. This can be
    //             re-implemented later. For now, we use `get_signature`, instead.
    static const bool patch_types = false;
#endif

    if ((patch_types && PySide_PatchTypes() < 0)
        || PySide_FinishSignatures(module, signatures) < 0) {
        PyErr_Print();
        PyErr_SetNone(PyExc_ImportError);
    }
}

static PyObject *adjustFuncName(const char *func_name)
{
    /*
     * PYSIDE-1019: Modify the function name expression according to feature.
     *
     * - snake_case
     *      The function name must be converted.
     * - full_property
     *      The property name must be used and "fset" appended.
     *
     *          modname.subname.classsname.propname.fset
     *
     *      Class properties must use the expression
     *
     *          modname.subname.classsname.__dict__['propname'].fset
     *
     * Note that fget is impossible because there are no parameters.
     */
    static const char mapping_name[] = "shibokensupport.signature.mapping";
    static PyObject *sys_modules = PySys_GetObject("modules");
    static PyObject *mapping = PyDict_GetItemString(sys_modules, mapping_name);
    static PyObject *ns = PyModule_GetDict(mapping);

    char _path[200 + 1] = {};
    const char *_name = strrchr(func_name, '.');
    strncat(_path, func_name, _name - func_name);
    ++_name;

    // This is a very cheap call into `mapping.py`.
    PyObject *update_mapping = PyDict_GetItemString(ns, "update_mapping");
    AutoDecRef res(PyObject_CallFunctionObjArgs(update_mapping, nullptr));
    if (res.isNull())
        return nullptr;

    // Run `eval` on the type string to get the object.
    // PYSIDE-1710: If the eval does not work, return the given string.
    AutoDecRef obtype(PyRun_String(_path, Py_eval_input, ns, ns));
    if (obtype.isNull())
        return String::fromCString(func_name);

    if (PyModule_Check(obtype.object())) {
        // This is a plain function. Return the unmangled name.
        return String::fromCString(func_name);
    }
    assert(PyType_Check(obtype));   // This was not true for __init__!

    // Find the feature flags
    auto type = reinterpret_cast<PyTypeObject *>(obtype.object());
    AutoDecRef dict(PepType_GetDict(type));
    int id = currentSelectId(type);
    id = id < 0 ? 0 : id;   // if undefined, set to zero
    auto lower = id & 0x01;
    auto is_prop = id & 0x02;
    bool is_class_prop = false;

    // Compute all needed info.
    PyObject *name = String::getSnakeCaseName(_name, lower);
    PyObject *prop_name{};
    if (is_prop) {
        PyObject *prop_methods = PyDict_GetItem(dict, PyMagicName::property_methods());
        prop_name = PyDict_GetItem(prop_methods, name);
        if (prop_name != nullptr) {
            PyObject *prop = PyDict_GetItem(dict, prop_name);
            is_class_prop = Py_TYPE(prop) != &PyProperty_Type;
        }
    }

    // Finally, generate the correct path expression.
    char _buf[250 + 1] = {};
    if (prop_name) {
        auto _prop_name = String::toCString(prop_name);
        if (is_class_prop)
            snprintf(_buf, sizeof(_buf), "%s.__dict__['%s'].fset", _path, _prop_name);
        else
            snprintf(_buf, sizeof(_buf), "%s.%s.fset", _path, _prop_name);
    }
    else {
        auto _name = String::toCString(name);
        snprintf(_buf, sizeof(_buf), "%s.%s", _path, _name);
    }
    return String::fromCString(_buf);
}

void SetError_Argument(PyObject *args, const char *func_name, PyObject *info)
{
    init_shibokensupport_module();
    /*
     * This function replaces the type error construction with extra
     * overloads parameter in favor of using the signature module.
     * Error messages are rare, so we do it completely in Python.
     */

    // PYSIDE-1305: Handle errors set by fillQtProperties.
    if (PyErr_Occurred()) {
        PyObject *e, *v, *t;
        // Note: These references are all borrowed.
        PyErr_Fetch(&e, &v, &t);
        Py_DECREF(e);
        info = v;
        Py_XDECREF(t);
    }
    // PYSIDE-1019: Modify the function name expression according to feature.
    AutoDecRef new_func_name(adjustFuncName(func_name));
    if (new_func_name.isNull()) {
        PyErr_Print();
        Py_FatalError("seterror_argument failed to call update_mapping");
    }
    if (info == nullptr)
        info = Py_None;
    AutoDecRef res(PyObject_CallFunctionObjArgs(pyside_globals->seterror_argument_func,
                                                args, new_func_name.object(), info, nullptr));
    if (res.isNull()) {
        PyErr_Print();
        Py_FatalError("seterror_argument did not receive a result");
    }
    PyObject *err, *msg;
    if (!PyArg_UnpackTuple(res, func_name, 2, 2, &err, &msg)) {
        PyErr_Print();
        Py_FatalError("unexpected failure in seterror_argument");
    }
    PyErr_SetObject(err, msg);
}

/*
 * Support for the metatype SbkObjectType_Type's tp_getset.
 *
 * This was not necessary for __signature__, because PyType_Type inherited it.
 * But the __doc__ attribute existed already by inheritance, and calling
 * PyType_Modified() is not supported. So we added the getsets explicitly
 * to the metatype.
 *
 * PYSIDE-2101: The __signature__ attribute is gone due to rlcompleter.
 */

PyObject *Sbk_TypeGet___doc__(PyObject *ob)
{
    init_shibokensupport_module();
    return pyside_tp_get___doc__(ob);
}

PyObject *GetFeatureDict()
{
    init_shibokensupport_module();
    return pyside_globals->feature_dict;
}

} //extern "C"
