// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include <abstractmetafunction.h>
#include <apiextractorresult.h>
#include <abstractmetalang.h>
#include <dotview.h>
#include <reporthandler.h>
#include <complextypeentry.h>
#include <containertypeentry.h>
#include <primitivetypeentry.h>
#include <graph.h>
#include "overloaddata.h"
#include "messages.h"
#include "ctypenames.h"
#include "pytypenames.h"
#include "textstream.h"
#include "exception.h"

#include "qtcompat.h"

#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qtemporaryfile.h>

#include <algorithm>
#include <utility>

using namespace Qt::StringLiterals;

static QString getTypeName(const AbstractMetaType &type)
{
    TypeEntryCPtr typeEntry = type.typeEntry();
    QString typeName = type.basicPrimitiveName();
    if (typeEntry->isContainer()) {
        QStringList types;
        for (const auto &cType : type.instantiations())
            types << cType.basicPrimitiveName();
        typeName += u'<' + types.join(u',') + u" >"_s;
    }
    return typeName;
}

static bool typesAreEqual(const AbstractMetaType &typeA, const AbstractMetaType &typeB)
{
    if (typeA.typeEntry() == typeB.typeEntry()) {
        if (typeA.isContainer() || typeA.isSmartPointer()) {
            if (typeA.instantiations().size() != typeB.instantiations().size())
                return false;

            for (qsizetype i = 0; i < typeA.instantiations().size(); ++i) {
                if (!typesAreEqual(typeA.instantiations().at(i), typeB.instantiations().at(i)))
                    return false;
            }
            return true;
        }

        return !(typeA.isCString() ^ typeB.isCString());
    }
    return false;
}

/**
 * Helper function that returns the name of a container get from containerType argument and
 * an instantiation taken either from an implicit conversion expressed by the function argument,
 * or from the string argument implicitConvTypeName.
 */
static QString getImplicitConversionTypeName(const AbstractMetaType &containerType,
                                             const AbstractMetaType &instantiation,
                                             const AbstractMetaFunctionCPtr &function,
                                             const QString &implicitConvTypeName = QString())
{
    QString impConv;
    if (!implicitConvTypeName.isEmpty())
        impConv = implicitConvTypeName;
    else if (function->isConversionOperator())
        impConv = function->ownerClass()->typeEntry()->name();
    else
        impConv = getTypeName(function->arguments().constFirst().type());

    QStringList types;
    for (const auto &otherType : containerType.instantiations())
        types << (otherType == instantiation ? impConv : getTypeName(otherType));

    return containerType.typeEntry()->qualifiedCppName() + u'<'
           + types.join(u", "_s) + u" >"_s;
}

static inline int overloadNumber(const OverloadDataNodePtr &o)
{
    return o->referenceFunction()->overloadNumber();
}

static bool sortByOverloadNumberModification(OverloadDataList &list)
{
    if (std::all_of(list.cbegin(), list.cend(),
                    [](const OverloadDataNodePtr &o) { return overloadNumber(o) == TypeSystem::OverloadNumberDefault; })) {
        return false;
    }
    std::stable_sort(list.begin(), list.end(),
                     [] (const OverloadDataNodePtr &o1, const OverloadDataNodePtr &o2) {
                         return overloadNumber(o1) < overloadNumber(o2);
                     });
    return true;
}

using OverloadGraph = Graph<QString>;

/**
 * Topologically sort the overloads by implicit convertion order
 *
 * This avoids using an implicit conversion if there's an explicit
 * overload for the convertible type. So, if there's an implicit convert
 * like TargetType(ConvertibleType foo) and both are in the overload list,
 * ConvertibleType is checked before TargetType.
 *
 * Side effects: Modifies m_nextOverloadData
 */
void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api)
{
    QHash<QString, OverloadDataList> typeToOverloads;
    using Edge = std::pair<QString, QString>;

    bool checkPyObject = false;
    bool checkPySequence = false;
    bool checkQString = false;
    bool checkQVariant = false;
    bool checkPyBuffer = false;

    // Primitive types that are not int, long, short,
    // char and their respective unsigned counterparts.
    static const QStringList nonIntegerPrimitives{floatT, doubleT, boolT};

    // Signed integer primitive types.
    static const QStringList signedIntegerPrimitives{intT, shortT, longT, longLongT};

    // sort the children overloads
    for (const auto &ov : std::as_const(m_children))
        ov->sortNextOverloads(api);

    if (m_children.size() <= 1 || sortByOverloadNumberModification(m_children))
        return;

    // Populates the OverloadSortData object containing map and reverseMap, to map type names to ids,
    // these ids will be used by the topological sort algorithm, because is easier and faster to work
    // with graph sorting using integers.

    OverloadGraph graph;
    for (const auto &ov : std::as_const(m_children)) {
        const QString typeName = getTypeName(ov->modifiedArgType());
        auto it = typeToOverloads.find(typeName);
        if (it == typeToOverloads.end()) {
            typeToOverloads.insert(typeName, {ov});
            graph.addNode(typeName);
        } else {
            it.value().append(ov);
        }

        if (!checkPyObject && typeName == cPyObjectT)
            checkPyObject = true;
        else if (!checkPySequence && typeName == cPySequenceT)
            checkPySequence = true;
        else if (!checkPyBuffer && typeName == cPyBufferT)
            checkPyBuffer = true;
        else if (!checkQVariant && typeName == qVariantT)
            checkQVariant = true;
        else if (!checkQString && typeName == qStringT)
            checkQString = true;

        for (const auto &instantiation : ov->argType().instantiations()) {
            // Add dependencies for type instantiation of container.
            graph.addNode(getTypeName(instantiation));

            // Build dependency for implicit conversion types instantiations for base container.
            // For example, considering signatures "method(list<PointF>)" and "method(list<Point>)",
            // and being PointF implicitly convertible from Point, an list<T> instantiation with T
            // as Point must come before the PointF instantiation, or else list<Point> will never
            // be called. In the case of primitive types, list<double> must come before list<int>.
            if (instantiation.isPrimitive()
                && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
                for (const QString &primitive : std::as_const(nonIntegerPrimitives))
                    graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive));
            } else {
                const auto &funcs = api.implicitConversions(instantiation);
                for (const auto &function : funcs)
                    graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, function));
            }
        }
    }


    // Create the graph of type dependencies based on implicit conversions.
    // All C++ primitive types, add any forgotten type AT THE END OF THIS LIST!
    static const QStringList primitiveTypes{intT, unsignedIntT, longT, unsignedLongT,
        shortT, unsignedShortT, boolT, unsignedCharT, charT, floatT,
        doubleT, constCharPtrT};

    QStringList foundPrimitiveTypeIds;
    for (const auto &p : primitiveTypes) {
        if (graph.hasNode(p))
            foundPrimitiveTypeIds.append(p);
    }

    if (checkPySequence && checkPyObject)
        graph.addEdge(cPySequenceT, cPyObjectT);

    QStringList classesWithIntegerImplicitConversion;

    AbstractMetaFunctionCList involvedConversions;

    for (const auto &ov : std::as_const(m_children)) {
        const AbstractMetaType &targetType = ov->argType();
        const QString targetTypeEntryName = getTypeName(ov->modifiedArgType());

        // Process implicit conversions
        const auto &functions = api.implicitConversions(targetType);
        for (const auto &function : functions) {
            QString convertibleType;
            if (function->isConversionOperator())
                convertibleType = function->ownerClass()->typeEntry()->name();
            else
                convertibleType = getTypeName(function->arguments().constFirst().type());

            if (convertibleType == intT || convertibleType == unsignedIntT)
                classesWithIntegerImplicitConversion << targetTypeEntryName;

            if (!graph.hasNode(convertibleType))
                continue;

            // If a reverse pair already exists, remove it. Probably due to the
            // container check (This happened to QVariant and QHash)
            graph.removeEdge(targetTypeEntryName, convertibleType);
            graph.addEdge(convertibleType, targetTypeEntryName);
            involvedConversions.append(function);
        }

        // Process inheritance relationships
        if (targetType.isValue() || targetType.isObject()) {
            const auto te = targetType.typeEntry();
            auto metaClass = AbstractMetaClass::findClass(api.classes(), te);
            if (!metaClass)
                throw Exception(msgArgumentClassNotFound(m_overloads.constFirst(), te));
            const auto &ancestors = metaClass->allTypeSystemAncestors();
            for (const auto &ancestor : ancestors) {
                QString ancestorTypeName = ancestor->typeEntry()->name();
                if (!graph.hasNode(ancestorTypeName))
                    continue;
                graph.removeEdge(ancestorTypeName, targetTypeEntryName);
                graph.addEdge(targetTypeEntryName, ancestorTypeName);
            }
        }

        // Process template instantiations
        for (const auto &instantiation : targetType.instantiations()) {
            const QString convertible = getTypeName(instantiation);
            if (graph.hasNode(convertible)) {
                if (!graph.containsEdge(targetTypeEntryName, convertible)) // Avoid cyclic dependency.
                    graph.addEdge(convertible, targetTypeEntryName);

                if (instantiation.isPrimitive()
                    && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
                    for (const QString &primitive : std::as_const(nonIntegerPrimitives)) {
                        QString convertibleTypeName =
                            getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive);
                        // Avoid cyclic dependency.
                        if (!graph.containsEdge(targetTypeEntryName, convertibleTypeName))
                            graph.addEdge(convertibleTypeName, targetTypeEntryName);
                    }

                } else {
                    const auto &funcs = api.implicitConversions(instantiation);
                    for (const auto &function : funcs) {
                        QString convertibleTypeName =
                            getImplicitConversionTypeName(ov->argType(), instantiation, function);
                        // Avoid cyclic dependency.
                        if (!graph.containsEdge(targetTypeEntryName, convertibleTypeName)) {
                            graph.addEdge(convertibleTypeName, targetTypeEntryName);
                            involvedConversions.append(function);
                        }
                    }
                }
            }
        }


        if ((checkPySequence || checkPyObject || checkPyBuffer)
            && !targetTypeEntryName.contains(cPyObjectT)
            && !targetTypeEntryName.contains(cPyBufferT)
            && !targetTypeEntryName.contains(cPySequenceT)) {
            if (checkPySequence) {
                // PySequence will be checked after all more specific types, but before PyObject.
                graph.addEdge(targetTypeEntryName, cPySequenceT);
            } else if (checkPyBuffer) {
                // PySequence will be checked after all more specific types, but before PyObject.
                graph.addEdge(targetTypeEntryName, cPyBufferT);
            } else {
                // Add dependency on PyObject, so its check is the last one (too generic).
                graph.addEdge(targetTypeEntryName, cPyObjectT);
            }
        } else if (checkQVariant && targetTypeEntryName != qVariantT) {
            if (!graph.containsEdge(qVariantT, targetTypeEntryName)) // Avoid cyclic dependency.
                graph.addEdge(targetTypeEntryName, qVariantT);
        } else if (checkQString && ov->argType().isPointer()
            && targetTypeEntryName != qStringT
            && targetTypeEntryName != qByteArrayT
            && (!checkPyObject || targetTypeEntryName != cPyObjectT)) {
            if (!graph.containsEdge(qStringT, targetTypeEntryName)) // Avoid cyclic dependency.
                graph.addEdge(targetTypeEntryName, qStringT);
        }

        if (targetType.isEnum()) {
            // Enum values must precede primitive types.
            for (const auto &id : foundPrimitiveTypeIds)
                graph.addEdge(targetTypeEntryName, id);
        }
    }

    // QByteArray args need to be checked after QString args
    if (graph.hasNode(qStringT) && graph.hasNode(qByteArrayT))
        graph.addEdge(qStringT, qByteArrayT);

    static const Edge rangeOrder[] =
        {{doubleT, floatT},
         {longLongT, longT}, {longLongT, intT}, {intT, shortT},
         {unsignedLongLongT, unsignedLongT}, {unsignedLongLongT, unsignedT},
         {unsignedLongLongT, unsignedIntT}, {unsignedT, unsignedShortT}
    };
    for (const auto &r : rangeOrder) {
        if (graph.hasNode(r.first) && graph.hasNode(r.second))
            graph.addEdge(r.first, r.second);
    }

    for (const auto &ov : std::as_const(m_children)) {
        const AbstractMetaType &targetType = ov->argType();
        if (!targetType.isEnum())
            continue;

        QString targetTypeEntryName = getTypeName(targetType);
        // Enum values must precede types implicitly convertible from "int" or "unsigned int".
        for (const QString &implicitFromInt : std::as_const(classesWithIntegerImplicitConversion))
            graph.addEdge(targetTypeEntryName, implicitFromInt);
    }


    // Special case for double(int i) (not tracked by m_generator->implicitConversions
    for (const QString &signedIntegerName : std::as_const(signedIntegerPrimitives)) {
        if (graph.hasNode(signedIntegerName)) {
            for (const QString &nonIntegerName : std::as_const(nonIntegerPrimitives)) {
                if (graph.hasNode(nonIntegerName))
                    graph.addEdge(nonIntegerName, signedIntegerName);
            }
        }
    }

    // sort the overloads topologically based on the dependency graph.
    const auto unmappedResult = graph.topologicalSort();
    if (!unmappedResult.isValid()) {
        QString funcName = referenceFunction()->name();
        if (auto owner = referenceFunction()->ownerClass())
            funcName.prepend(owner->name() + u'.');

        // Dump overload graph
        QString graphName = QDir::tempPath() + u'/' + funcName + u".dot"_s;
        graph.dumpDot(graphName, [] (const QString &n) { return n; });
        AbstractMetaFunctionCList cyclic;
        for (const auto &typeName : unmappedResult.cyclic) {
            const auto oit = typeToOverloads.constFind(typeName);
            if (oit != typeToOverloads.cend())
                cyclic.append(oit.value().constFirst()->referenceFunction());
        }
        qCWarning(lcShiboken, "%s", qPrintable(msgCyclicDependency(funcName, graphName, cyclic, involvedConversions)));
    }

    m_children.clear();
    for (const auto &typeName : unmappedResult.result) {
        const auto oit = typeToOverloads.constFind(typeName);
        if (oit != typeToOverloads.cend()) {
            std::copy(oit.value().crbegin(), oit.value().crend(),
                      std::back_inserter(m_children));
        }
    }
}

// Determine the minimum (first default argument)/maximum arguments (size)
// of a function (taking into account the removed arguments).
static std::pair<int, int> getMinMaxArgs(const AbstractMetaFunctionCPtr &func)
{
    int defaultValueIndex = -1;
    const auto &arguments = func->arguments();
    int argIndex = 0;
    for (const auto &arg : arguments) {
        if (!arg.isModifiedRemoved()) {
            if (defaultValueIndex < 0 && arg.hasDefaultValueExpression())
                defaultValueIndex = argIndex;
            ++argIndex;
        }
    }
    const int maxArgs = argIndex;
    const int minArgs = defaultValueIndex >= 0 ? defaultValueIndex : maxArgs;
    return {minArgs, maxArgs};
}

const OverloadDataRootNode *OverloadDataNode::parent() const
{
    return m_parent;
}

/**
 * Root constructor for OverloadData
 *
 * This constructor receives the list of overloads for a given function and iterates generating
 * the graph of OverloadData instances. Each OverloadDataNode instance references an argument/type
 * combination.
 *
 * Example:
 *      addStuff(double, PyObject *)
 *      addStuff(double, int)
 *
 * Given these two overloads, there will be the following graph:
 *
 *   addStuff - double - PyObject *
 *                    \- int
 *
 */
OverloadData::OverloadData(const AbstractMetaFunctionCList &overloads,
                           const ApiExtractorResult &api) :
    OverloadDataRootNode(overloads)
{
    for (const auto &func : overloads) {
        const auto minMaxArgs = getMinMaxArgs(func);
        if (minMaxArgs.first < m_minArgs)
            m_minArgs = minMaxArgs.first;
        if (minMaxArgs.second > m_maxArgs)
            m_maxArgs = minMaxArgs.second;
        OverloadDataRootNode *currentOverloadData = this;
        const AbstractMetaArgumentList &arguments = func->arguments();
        for (const AbstractMetaArgument &arg : arguments) {
            if (!arg.isModifiedRemoved())
                currentOverloadData = currentOverloadData->addOverloadDataNode(func, arg);
        }
    }

    // Sort the overload possibilities so that the overload decisor code goes for the most
    // important cases first, based on the topological order of the implicit conversions
    sortNextOverloads(api);
}

OverloadDataNode::OverloadDataNode(const AbstractMetaFunctionCPtr &func,
                                   OverloadDataRootNode *parent,
                                   const AbstractMetaArgument &argument,
                                   int argPos,
                                   const QString &argTypeReplaced) :
      m_argument(argument),
      m_argTypeReplaced(argTypeReplaced),
      m_parent(parent),
      m_argPos(argPos)
{
    if (func)
        this->addOverload(func);
}

void OverloadDataNode::addOverload(const AbstractMetaFunctionCPtr &func)
{
    m_overloads.append(func);
}

OverloadDataNode *OverloadDataRootNode::addOverloadDataNode(const AbstractMetaFunctionCPtr &func,
                                                            const AbstractMetaArgument &arg)
{
    OverloadDataNodePtr overloadData;
    if (!func->isOperatorOverload()) {
        for (const auto &tmp : std::as_const(m_children)) {
            // TODO: 'const char *', 'char *' and 'char' will have the same TypeEntry?

            // If an argument have a type replacement, then we should create a new overloaddata
            // for it, unless the next argument also have a identical type replacement.
            if (typesAreEqual(tmp->modifiedArgType(), arg.modifiedType())) {
                tmp->addOverload(func);
                overloadData = tmp;
            }
        }
    }

    if (!overloadData) {
        const int argpos = argPos() + 1;
        overloadData.reset(new OverloadDataNode(func, this, arg, argpos));
        m_children.append(overloadData);
    }

    return overloadData.get();
}

static bool isNonVoidOverload(const AbstractMetaFunctionCPtr &func)
{
    return func->isTypeModified()
        ? func->modifiedTypeName() != "void"_L1
        : !func->argumentRemoved(0) && !func->type().isVoid();
}

bool OverloadData::hasNonVoidReturnType() const
{
    return std::any_of(m_overloads.cbegin(), m_overloads.cend(),
                       isNonVoidOverload);
}

static bool functionHasVarargs(const AbstractMetaFunctionCPtr &func)
{
    const auto &args = func->arguments();
    return args.size() > 1 && args.constLast().type().isVarargs();
}

bool OverloadData::hasVarargs() const
{
    return std::any_of(m_overloads.cbegin(), m_overloads.cend(),
                       functionHasVarargs);
}

static bool isStaticFunction(const AbstractMetaFunctionCPtr &func)
{
    return func->isStatic();
}

bool OverloadData::hasStaticFunction(const AbstractMetaFunctionCList &overloads)
{
    return std::any_of(overloads.cbegin(), overloads.cend(), isStaticFunction);
}

bool OverloadData::hasStaticFunction() const
{
    return std::any_of(m_overloads.cbegin(), m_overloads.cend(), isStaticFunction);
}

static bool isClassMethod(const AbstractMetaFunctionCPtr &func)
{
    return func->isClassMethod();
}

bool OverloadData::hasClassMethod(const AbstractMetaFunctionCList &overloads)
{
    return std::any_of(overloads.cbegin(), overloads.cend(), isClassMethod);
}

bool OverloadData::hasClassMethod() const
{
    return std::any_of(m_overloads.cbegin(), m_overloads.cend(), isClassMethod);
}

bool OverloadData::hasInstanceFunction(const AbstractMetaFunctionCList &overloads)
{
    return !std::all_of(overloads.cbegin(), overloads.cend(), isStaticFunction);
}

bool OverloadData::hasInstanceFunction() const
{
    return !std::all_of(m_overloads.cbegin(), m_overloads.cend(), isStaticFunction);
}

bool OverloadData::hasStaticAndInstanceFunctions(const AbstractMetaFunctionCList &overloads)
{
    return OverloadData::hasStaticFunction(overloads) && OverloadData::hasInstanceFunction(overloads);
}

bool OverloadData::hasStaticAndInstanceFunctions() const
{
    return OverloadData::hasStaticFunction() && OverloadData::hasInstanceFunction();
}

OverloadDataRootNode::OverloadDataRootNode(const AbstractMetaFunctionCList &o) :
    m_overloads(o)
{
}

OverloadDataRootNode::~OverloadDataRootNode() = default;

AbstractMetaFunctionCPtr OverloadDataRootNode::referenceFunction() const
{
    return m_overloads.constFirst();
}

const AbstractMetaArgument *OverloadDataNode::overloadArgument(const AbstractMetaFunctionCPtr &func) const
{
    if (isRoot() || !m_overloads.contains(func))
        return nullptr;

    int argPos = 0;
    int removed = 0;
    for (int i = 0; argPos <= m_argPos; i++) {
        if (func->arguments().at(i).isModifiedRemoved())
            removed++;
        else
            argPos++;
    }

    return &func->arguments().at(m_argPos + removed);
}

static bool hasDefaultValue(const OverloadDataNodePtr &o)
{
    return o->getFunctionWithDefaultValue() != nullptr;
}

bool OverloadDataRootNode::nextArgumentHasDefaultValue() const
{
     return std::any_of(m_children.cbegin(), m_children.cend(), hasDefaultValue);
}

static const OverloadDataRootNode *_findNextArgWithDefault(const OverloadDataRootNode *overloadData)
{
    if (overloadData->getFunctionWithDefaultValue())
        return overloadData;

    const OverloadDataRootNode *result = nullptr;
    const OverloadDataList &data = overloadData->children();
    for (const auto &odata : data) {
        const auto *tmp = _findNextArgWithDefault(odata.get());
        if (!result || (tmp && result->argPos() > tmp->argPos()))
            result = tmp;
    }
    return result;
}

const OverloadDataRootNode *OverloadDataRootNode::findNextArgWithDefault() const
{
    return _findNextArgWithDefault(this);
}

bool OverloadDataRootNode::isFinalOccurrence(const AbstractMetaFunctionCPtr &func) const
{
    auto containsPredicate = [&func](const OverloadDataNodePtr &o) {
        return o->overloads().contains(func);
    };
    return std::none_of(m_children.cbegin(), m_children.cend(), containsPredicate);
}

AbstractMetaFunctionCPtr OverloadDataRootNode::getFunctionWithDefaultValue() const
{
    const qsizetype argpos = argPos();
    for (const auto &func : m_overloads) {
        qsizetype removedArgs = 0;
        for (qsizetype i = 0; i <= argpos + removedArgs; i++) {
            if (func->arguments().at(i).isModifiedRemoved())
                removedArgs++;
        }
        if (func->arguments().at(argpos + removedArgs).hasDefaultValueExpression())
            return func;
    }
    return {};
}

QList<int> OverloadData::invalidArgumentLengths() const
{
    QSet<int> validArgLengths;

    for (const auto &func : m_overloads) {
        const AbstractMetaArgumentList args = func->arguments();
        int offset = 0;
        for (qsizetype i = 0; i < args.size(); ++i) {
            if (func->arguments().at(i).isModifiedRemoved()) {
                offset++;
            } else {
                if (args.at(i).hasDefaultValueExpression())
                    validArgLengths << i-offset;
            }
        }
        validArgLengths << args.size() - offset;
    }

    QList<int> invalidArgLengths;
    for (int i = m_minArgs + 1; i < m_maxArgs; i++) {
        if (!validArgLengths.contains(i))
            invalidArgLengths.append(i);
    }

    return invalidArgLengths;
}

int OverloadData::numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func)
{
    return std::count_if(func->arguments().cbegin(), func->arguments().cend(),
                         [](const AbstractMetaArgument &a) { return a.isModifiedRemoved(); });
}

int OverloadData::numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func, int finalArgPos)
{
    Q_ASSERT(finalArgPos >= 0);
    int removed = 0;
    const auto size = func->arguments().size();
    for (qsizetype i = 0; i < qMin(size, qsizetype(finalArgPos + removed)); ++i) {
        if (func->arguments().at(i).isModifiedRemoved())
            ++removed;
    }
    return removed;
}

void OverloadData::dumpGraph(const QString &filename) const
{
    QFile file(filename);
    if (file.open(QFile::WriteOnly)) {
        QTextStream s(&file);
        dumpRootGraph(s, m_minArgs, m_maxArgs);
    }
}

QString OverloadData::dumpGraph() const
{
    QString result;
    QTextStream s(&result);
    dumpRootGraph(s, m_minArgs, m_maxArgs);
    return result;
}

bool OverloadData::showGraph() const
{
    return showDotGraph(referenceFunction()->name(), dumpGraph());
}

static inline QString toHtml(QString s)
{
    s.replace(u'<', u"&lt;"_s);
    s.replace(u'>', u"&gt;"_s);
    s.replace(u'&', u"&amp;"_s);
    return s;
}

void OverloadDataRootNode::dumpRootGraph(QTextStream &s, int minArgs, int maxArgs) const
{
    const auto rfunc = referenceFunction();
    s << "digraph OverloadedFunction {\n";
    s << "    graph [fontsize=12 fontname=freemono labelloc=t splines=true overlap=false rankdir=LR];\n";

    // Shows all function signatures
    s << "legend [fontsize=9 fontname=freemono shape=rect label=\"";
    for (const auto &func : m_overloads) {
        s << "f" << functionNumber(func) << " : "
            << toHtml(func->type().cppSignature())
            << ' ' << toHtml(func->minimalSignature()) << "\\l";
    }
    s << "\"];\n";

    // Function box title
    s << "    \"" << rfunc->name() << "\" [shape=plaintext style=\"filled,bold\" margin=0 fontname=freemono fillcolor=white penwidth=1 ";
    s << "label=<<table border=\"0\" cellborder=\"0\" cellpadding=\"3\" bgcolor=\"white\">";
    s << "<tr><td bgcolor=\"black\" align=\"center\" cellpadding=\"6\" colspan=\"2\"><font color=\"white\">";
    if (rfunc->ownerClass())
        s << rfunc->ownerClass()->name() << "::";
    s << toHtml(rfunc->name()) << "</font>";
    if (rfunc->isVirtual()) {
        s << "<br/><font color=\"white\" point-size=\"10\">&lt;&lt;";
        if (rfunc->isAbstract())
            s << "pure ";
        s << "virtual&gt;&gt;</font>";
    }
    s << "</td></tr>";

    // Function return type
    s << "<tr><td bgcolor=\"gray\" align=\"right\">original type</td><td bgcolor=\"gray\" align=\"left\">"
        << toHtml(rfunc->type().cppSignature())
        << "</td></tr>";

    // Shows type changes for all function signatures
    for (const auto &func : m_overloads) {
        if (!func->isTypeModified())
            continue;
        s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << functionNumber(func);
        s << "-type</td><td bgcolor=\"gray\" align=\"left\">";
        s << toHtml(func->modifiedTypeName()) << "</td></tr>";
    }

    // Minimum and maximum number of arguments
    s << "<tr><td bgcolor=\"gray\" align=\"right\">minArgs</td><td bgcolor=\"gray\" align=\"left\">";
    s << minArgs << "</td></tr>";
    s << "<tr><td bgcolor=\"gray\" align=\"right\">maxArgs</td><td bgcolor=\"gray\" align=\"left\">";
    s << maxArgs << "</td></tr>";

    if (rfunc->ownerClass()) {
        if (rfunc->implementingClass() != rfunc->ownerClass())
            s << "<tr><td align=\"right\">implementor</td><td align=\"left\">" << rfunc->implementingClass()->name() << "</td></tr>";
        if (rfunc->declaringClass() != rfunc->ownerClass() && rfunc->declaringClass() != rfunc->implementingClass())
            s << "<tr><td align=\"right\">declarator</td><td align=\"left\">" << rfunc->declaringClass()->name() << "</td></tr>";
    }

    // Overloads for the signature to present point
    s << "<tr><td bgcolor=\"gray\" align=\"right\">overloads</td><td bgcolor=\"gray\" align=\"left\">";
    for (const auto &func : m_overloads)
        s << 'f' << functionNumber(func) << ' ';
    s << "</td></tr>";

    s << "</table>> ];\n";

    for (const auto &pd : m_children) {
        s << "    \""  << rfunc->name() << "\" -> ";
        pd->dumpNodeGraph(s);
    }

    s << "}\n";
}

void OverloadDataNode::dumpNodeGraph(QTextStream &s) const
{
    QString argId = u"arg_"_s + QString::number(quintptr(this));
    s << argId << ";\n";

    s << "    \"" << argId << "\" [shape=\"plaintext\" style=\"filled,bold\" margin=\"0\" fontname=\"freemono\" fillcolor=\"white\" penwidth=1 ";
    s << "label=<<table border=\"0\" cellborder=\"0\" cellpadding=\"3\" bgcolor=\"white\">";

    // Argument box title
    s << "<tr><td bgcolor=\"black\" align=\"left\" cellpadding=\"2\" colspan=\"2\">";
    s << "<font color=\"white\" point-size=\"11\">arg #" << argPos() << "</font></td></tr>";

    // Argument type information
    const QString type = modifiedArgType().cppSignature();
    s << "<tr><td bgcolor=\"gray\" align=\"right\">type</td><td bgcolor=\"gray\" align=\"left\">";
    s << toHtml(type) << "</td></tr>";
    if (isTypeModified()) {
        s << "<tr><td bgcolor=\"gray\" align=\"right\">orig. type</td><td bgcolor=\"gray\" align=\"left\">";
        s << toHtml(argType().cppSignature()) << "</td></tr>";
    }

    const OverloadDataRootNode *root = this;
    while (!root->isRoot())
        root = root->parent();

    // Overloads for the signature to present point
    s << "<tr><td bgcolor=\"gray\" align=\"right\">overloads</td><td bgcolor=\"gray\" align=\"left\">";
    for (const auto &func : m_overloads)
        s << 'f' << root->functionNumber(func) << ' ';
    s << "</td></tr>";

    // Show default values (original and modified) for various functions
    for (const auto &func : m_overloads) {
        const AbstractMetaArgument *arg = overloadArgument(func);
        if (!arg)
            continue;
        const int n = root->functionNumber(func);
        QString argDefault = arg->defaultValueExpression();
        if (!argDefault.isEmpty() ||
            argDefault != arg->originalDefaultValueExpression()) {
            s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << n;
            s << "-default</td><td bgcolor=\"gray\" align=\"left\">";
            s << argDefault << "</td></tr>";
        }
        if (argDefault != arg->originalDefaultValueExpression()) {
            s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << n;
            s << "-orig-default</td><td bgcolor=\"gray\" align=\"left\">";
            s << arg->originalDefaultValueExpression() << "</td></tr>";
        }
    }

    s << "</table>>];\n";

    for (const auto &pd : m_children) {
        s << "    " << argId << " -> ";
        pd->dumpNodeGraph(s);
    }
}

int OverloadDataRootNode::functionNumber(const AbstractMetaFunctionCPtr &func) const
{
    return m_overloads.indexOf(func);
}

bool OverloadData::pythonFunctionWrapperUsesListOfArguments() const
{
    auto referenceFunction = m_overloads.constFirst();
    if (referenceFunction->isCallOperator())
        return true;
    if (referenceFunction->isOperatorOverload())
        return false;
    const int maxArgs = this->maxArgs();
    const int minArgs = this->minArgs();
    return (minArgs != maxArgs)
           || (maxArgs > 1)
           || referenceFunction->isConstructor()
           || hasArgumentWithDefaultValue();
}

bool OverloadData::hasArgumentWithDefaultValue() const
{
    return maxArgs() != 0
           && std::any_of(m_overloads.cbegin(), m_overloads.cend(),
                          qOverload<const AbstractMetaFunctionCPtr &>(hasArgumentWithDefaultValue));
}

static bool isArgumentWithDefaultValue(const AbstractMetaArgument &arg)
{
    return !arg.isModifiedRemoved() && arg.hasDefaultValueExpression();
}

bool OverloadData::hasArgumentWithDefaultValue(const AbstractMetaFunctionCPtr &func)
{
    const AbstractMetaArgumentList &arguments = func->arguments();
    return std::any_of(arguments.cbegin(), arguments.cend(), isArgumentWithDefaultValue);
}

AbstractMetaArgumentList OverloadData::getArgumentsWithDefaultValues(const AbstractMetaFunctionCPtr &func)
{
    AbstractMetaArgumentList args;
    const AbstractMetaArgumentList &arguments = func->arguments();
    for (const AbstractMetaArgument &arg : arguments) {
        if (!arg.hasDefaultValueExpression()
            || arg.isModifiedRemoved())
            continue;
        args << arg;
    }
    return args;
}

#ifndef QT_NO_DEBUG_STREAM

void OverloadDataRootNode::formatReferenceFunction(QDebug &d) const
{
    auto refFunc = referenceFunction();
    d << '"';
    if (auto owner = refFunc->ownerClass())
        d << owner->qualifiedCppName() << "::";
    d << refFunc->minimalSignature() << '"';
    if (m_overloads.constFirst()->isReverseOperator())
        d << " [reverseop]";
}

void OverloadDataRootNode::formatOverloads(QDebug &d) const
{
    const qsizetype count = m_overloads.size();
    d << ", overloads[" << count << ']';
    if (count < 2)
        return;
    d << "=(";
    for (qsizetype i = 0; i < count; ++i) {
        if (i)
            d << '\n';
        d << m_overloads.at(i)->signature();
    }
    d << ')';
}

void OverloadDataRootNode::formatNextOverloadData(QDebug &d) const
{
    const qsizetype count = m_children.size();
    d << ", next[" << count << ']';
    if (d.verbosity() >= 3) {
        d << "=(";
        for (qsizetype i = 0; i < count; ++i) {
            if (i)
                d << '\n';
            m_children.at(i)->formatDebug(d);
        }
        d << ')';
    }
}

void OverloadDataRootNode::formatDebug(QDebug &d) const
{
    formatReferenceFunction(d);
    formatOverloads(d);
    formatNextOverloadData(d);
}

void OverloadDataNode::formatDebug(QDebug &d) const
{
    d << "OverloadDataNode(";
    formatReferenceFunction(d);
    d << ", argPos=" << m_argPos;
    if (m_argument.argumentIndex() != m_argPos)
        d << ", argIndex=" << m_argument.argumentIndex();
    d << ", argType=\"" << m_argument.type().cppSignature() << '"';
    if (isTypeModified())
        d << ", modifiedArgType=\"" << modifiedArgType().cppSignature() << '"';
    formatOverloads(d);
    formatNextOverloadData(d);
    d << ')';
}

void OverloadData::formatDebug(QDebug &d) const
{
    d << "OverloadData(";
    formatReferenceFunction(d);
    d << ", minArgs=" << m_minArgs << ", maxArgs=" << m_maxArgs;
    formatOverloads(d);
    formatNextOverloadData(d);
    d << ')';
}

QDebug operator<<(QDebug d, const OverloadData &od)
{
    QDebugStateSaver saver(d);
    d.noquote();
    d.nospace();
    od.formatDebug(d);
    return d;
}
#endif // !QT_NO_DEBUG_STREAM
