// Copyright (C) 2020 The Qt Company Ltd.
// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0


#include "typeinfo.h"
#include "codemodel.h"

#include <clangparser/clangutils.h>
#include <debughelpers_p.h>

#include "qtcompat.h"

#include <QtCore/qdebug.h>
#include <QtCore/qstack.h>
#include <QtCore/qtextstream.h>

#include <iostream>

using namespace Qt::StringLiterals;

class TypeInfoData : public QSharedData
{

public:
    TypeInfoData();

    bool equals(const TypeInfoData &other) const;
    bool isStdType() const;
    void simplifyStdType();

    QStringList m_qualifiedName;
    QStringList m_arrayElements;
    TypeInfo::TypeInfoList m_arguments;
    TypeInfo::TypeInfoList m_instantiations;
    TypeInfo::Indirections m_indirections;

    union {
        uint flags;

        struct {
            uint m_constant: 1;
            uint m_volatile: 1;
            uint m_functionPointer: 1;
            uint m_padding: 29;
        };
    };

    ReferenceType m_referenceType = NoReference;
    TypeCategory m_category = TypeCategory::Other;
};

TypeInfoData::TypeInfoData() : flags(0)
{
}

TypeInfo::TypeInfo() : d(new TypeInfoData)
{
}

TypeInfo::~TypeInfo() = default;
TypeInfo::TypeInfo(const TypeInfo &) = default;
TypeInfo& TypeInfo::operator=(const TypeInfo &) = default;
TypeInfo::TypeInfo(TypeInfo &&) noexcept = default;
TypeInfo &TypeInfo::operator=(TypeInfo &&) noexcept = default;

static inline TypeInfo createType(const QString &name)
{
    TypeInfo result;
    result.addName(name);
    return result;
}

TypeInfo TypeInfo::voidType()
{
    static const TypeInfo result = createType(u"void"_s);
    return result;
}

TypeInfo TypeInfo::varArgsType()
{
    static const TypeInfo result = createType(u"..."_s);
    return result;
}

TypeInfo TypeInfo::combine(const TypeInfo &__lhs, const TypeInfo &__rhs)
{
    TypeInfo __result = __lhs;

    __result.setConstant(__result.isConstant() || __rhs.isConstant());
    __result.setVolatile(__result.isVolatile() || __rhs.isVolatile());
    if (__rhs.referenceType() > __result.referenceType())
        __result.setReferenceType(__rhs.referenceType());

    const auto &indirections = __rhs.indirectionsV();
    for (auto i : indirections)
        __result.addIndirection(i);

    __result.setArrayElements(__result.arrayElements() + __rhs.arrayElements());

    const auto &instantiations = __rhs.instantiations();
    for (const auto &i : instantiations)
        __result.addInstantiation(i);

    return __result;
}

QStringList TypeInfo::qualifiedName() const
{
    return d->m_qualifiedName;
}

QString TypeInfo::qualifiedNameString() const
{
    return d->m_qualifiedName.join("::"_L1);
}

QString TypeInfo::qualifiedInstantationName() const
{
    QString result = qualifiedNameString();
    if (const auto instantiationCount = d->m_instantiations.size()) {
        result += u'<';
        for (qsizetype i = 0; i < instantiationCount; ++i) {
            if (i)
                result += ", "_L1;
            result += d->m_instantiations.at(i).toString();
        }
        if (result.endsWith(u'>'))
            result += u' ';
        result += u'>';
    }
    return result;
}

void TypeInfo::setQualifiedName(const QStringList &qualified_name)
{
    if (d->m_qualifiedName != qualified_name)
        d->m_qualifiedName = qualified_name;
}

void  TypeInfo::addName(const QString &n)
{
    d->m_qualifiedName.append(n);
}

bool TypeInfo::isVoid() const
{
    return d->m_category == TypeCategory::Void;
}

TypeCategory TypeInfo::typeCategory() const
{
    return d->m_category;

}

void TypeInfo::setTypeCategory(TypeCategory c)
{
    if (d->m_category != c)
        d->m_category = c;
}

bool TypeInfo::isConstant() const
{
    return d->m_constant;
}

void TypeInfo::setConstant(bool is)
{
    if (d->m_constant != is)
        d->m_constant = is;
}

bool TypeInfo::isVolatile() const
{
    return d->m_volatile;
}

void TypeInfo::setVolatile(bool is)
{
    if (d->m_volatile != is)
        d->m_volatile = is;
}

ReferenceType TypeInfo::referenceType() const
{
    return d->m_referenceType;
}

void TypeInfo::setReferenceType(ReferenceType r)
{
    if (d->m_referenceType != r)
        d->m_referenceType = r;
}

const TypeInfo::Indirections &TypeInfo::indirectionsV() const
{
    return d->m_indirections;
}

void TypeInfo::setIndirectionsV(const TypeInfo::Indirections &i)
{
    if (d->m_indirections != i)
        d->m_indirections = i;
}

int TypeInfo::indirections() const
{
    return d->m_indirections.size();
}

void TypeInfo::setIndirections(int indirections)
{
    const Indirections newValue(indirections, Indirection::Pointer);
    if (d->m_indirections != newValue)
        d->m_indirections = newValue;
}

void TypeInfo::addIndirection(Indirection i)
{
    d->m_indirections.append(i);
}

bool TypeInfo::isFunctionPointer() const
{
    return d->m_functionPointer;
}

void TypeInfo::setFunctionPointer(bool is)
{
    if (d->m_functionPointer != is)
        d->m_functionPointer = is;
}

const QStringList &TypeInfo::arrayElements() const
{
    return d->m_arrayElements;
}

void TypeInfo::setArrayElements(const QStringList &arrayElements)
{
    if (d->m_arrayElements != arrayElements)
        d->m_arrayElements = arrayElements;
}

void TypeInfo::addArrayElement(const QString &a)
{
    d->m_arrayElements.append(a);
}

const QList<TypeInfo> &TypeInfo::arguments() const
{
    return d->m_arguments;
}

void TypeInfo::setArguments(const QList<TypeInfo> &arguments)
{
    if (d->m_arguments != arguments)
        d->m_arguments = arguments;
}

void TypeInfo::addArgument(const TypeInfo &arg)
{
    d->m_arguments.append(arg);
}

const TypeInfo::TypeInfoList &TypeInfo::instantiations() const
{
    return d->m_instantiations;
}

TypeInfo::TypeInfoList &TypeInfo::instantiations()
{
    return d->m_instantiations;
}

void TypeInfo::setInstantiations(const TypeInfoList &i)
{
    if (d->m_instantiations != i)
        d->m_instantiations = i;
}

void TypeInfo::addInstantiation(const TypeInfo &i)
{
    d->m_instantiations.append(i);
}

void TypeInfo::clearInstantiations()
{
    if (!d->m_instantiations.isEmpty())
        d->m_instantiations.clear();
}

bool TypeInfo::isPlain() const
{
    return d->m_constant == 0 && d->m_volatile == 0 && d->m_referenceType == NoReference
        &&  d->m_indirections.isEmpty() && d->m_arrayElements.isEmpty();
}

TypeInfo TypeInfo::resolveType(TypeInfo const &__type, const ScopeModelItem &__scope)
{
    return TypeInfo::resolveType(CodeModel::findItem(__type.qualifiedName(), __scope),
                                 __type, __scope);
}

TypeInfo TypeInfo::resolveType(const CodeModelItem &__item, TypeInfo const &__type,
                               const ScopeModelItem &__scope)
{
    // Copy the type and replace with the proper qualified name. This
    // only makes sence to do if we're actually getting a resolved
    // type with a namespace. We only get this if the returned type
    // has more than 2 entries in the qualified name... This test
    // could be improved by returning if the type was found or not.
    TypeInfo otherType(__type);
    if (__item && __item->qualifiedName().size() > 1) {
        otherType.setQualifiedName(__item->qualifiedName());
    }

    if (TypeDefModelItem __typedef = std::dynamic_pointer_cast<_TypeDefModelItem>(__item)) {
        TypeInfo combined = TypeInfo::combine(__typedef->type(), otherType);
        const CodeModelItem nextItem = CodeModel::findItem(combined.qualifiedName(), __scope);
        if (!nextItem)
            return combined;
        // PYSIDE-362, prevent recursion on opaque structs like
        // typedef struct xcb_connection_t xcb_connection_t;
        if (nextItem.get() ==__item.get()) {
            std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__
                << "() on " << qPrintable(__type.qualifiedNameString())
                << '\n';
            return otherType;
        }
        return resolveType(nextItem, combined, __scope);
    }

    if (TemplateTypeAliasModelItem templateTypeAlias = std::dynamic_pointer_cast<_TemplateTypeAliasModelItem>(__item)) {

        TypeInfo combined = TypeInfo::combine(templateTypeAlias->type(), otherType);
        // For the alias "template<typename T> using QList = QVector<T>" with
        // other="QList<int>", replace the instantiations to obtain "QVector<int>".
        auto aliasInstantiations = templateTypeAlias->type().instantiations();
        const auto &concreteInstantiations = otherType.instantiations();
        const auto count = qMin(aliasInstantiations.size(), concreteInstantiations.size());
        for (qsizetype i = 0; i < count; ++i)
            aliasInstantiations[i] = concreteInstantiations.at(i);
        combined.setInstantiations(aliasInstantiations);
        const CodeModelItem nextItem = CodeModel::findItem(combined.qualifiedName(), __scope);
        if (!nextItem)
            return combined;
        return resolveType(nextItem, combined, __scope);
    }

    return otherType;
}

// Handler for clang::parseTemplateArgumentList() that populates
// TypeInfo::m_instantiations
class TypeInfoTemplateArgumentHandler
{
public:
    explicit TypeInfoTemplateArgumentHandler(TypeInfo *t)
    {
        m_parseStack.append(t);
    }

    void operator()(int level, QStringView name)
    {
        if (level > m_parseStack.size()) {
            Q_ASSERT(!top()->instantiations().isEmpty());
            m_parseStack.push(&top()->instantiations().back());
        }
        while (level < m_parseStack.size())
            m_parseStack.pop();
        TypeInfo instantiation;
        if (name.startsWith(u"const ")) {
            instantiation.setConstant(true);
            name = name.mid(6);
        }
        instantiation.setQualifiedName(qualifiedName(name));
        top()->addInstantiation(instantiation);
   }

private:
    TypeInfo *top() const { return m_parseStack.back(); }

    static QStringList qualifiedName(QStringView name)
    {
        QStringList result;
        const auto nameParts = name.split(u"::");
        result.reserve(nameParts.size());
        for (const auto &p : nameParts)
            result.append(p.toString());
        return result;
    }

    QStack<TypeInfo *> m_parseStack;
};

std::pair<qsizetype, qsizetype>
    TypeInfo::parseTemplateArgumentList(const QString &l, qsizetype from)
{
    return clang::parseTemplateArgumentList(l, clang::TemplateArgumentHandler(TypeInfoTemplateArgumentHandler(this)), from);
}

QString TypeInfo::toString() const
{
    QString tmp;
    if (isConstant())
        tmp += u"const "_s;

    if (isVolatile())
        tmp += u"volatile "_s;

    tmp += qualifiedInstantationName();

    for (Indirection i : d->m_indirections)
        tmp.append(indirectionKeyword(i));

    switch (referenceType()) {
    case NoReference:
        break;
    case LValueReference:
        tmp += u'&';
        break;
    case RValueReference:
        tmp += u"&&"_s;
        break;
    }

    if (isFunctionPointer()) {
        tmp += u" (*)("_s;
        for (qsizetype i = 0; i < d->m_arguments.size(); ++i) {
            if (i != 0)
                tmp += u", "_s;

            tmp += d->m_arguments.at(i).toString();
        }
        tmp += u')';
    }

    for (const QString &elt : d->m_arrayElements)
        tmp += u'[' + elt + u']';

    return tmp;
}

bool TypeInfoData::equals(const TypeInfoData &other) const
{
    if (m_arrayElements.size() != other.m_arrayElements.size())
        return false;

#if defined (RXX_CHECK_ARRAY_ELEMENTS) // ### it'll break
    for (qsizetype i = 0; i < arrayElements().size(); ++i) {
        QString elt1 = arrayElements().at(i).trimmed();
        QString elt2 = other.arrayElements().at(i).trimmed();

        if (elt1 != elt2)
            return false;
    }
#endif

    return flags == other.flags
           && m_qualifiedName == other.m_qualifiedName
           && m_category == other.m_category
           && (!m_functionPointer || m_arguments == other.m_arguments)
           && m_instantiations == other.m_instantiations;
}


bool comparesEqual(const TypeInfo &lhs, const TypeInfo &rhs) noexcept
{
   return lhs.d.data() == rhs.d.data() || lhs.d->equals(*rhs.d);
}

QString TypeInfo::indirectionKeyword(Indirection i)
{
    return i == Indirection::Pointer ? "*"_L1 : "*const"_L1;
}

bool TypeInfo::stripLeadingConst(QString *s)
{
    return stripLeadingQualifier("const"_L1, s);
}

bool TypeInfo::stripLeadingVolatile(QString *s)
{
    return stripLeadingQualifier("volatile"_L1, s);
}

bool TypeInfo::stripLeadingQualifier(QLatin1StringView qualifier, QString *s)
{
    // "const int x"
    const auto qualifierSize = qualifier.size();
    if (s->size() < qualifierSize + 1 || !s->startsWith(qualifier)
        || !s->at(qualifierSize).isSpace()) {
        return false;
    }
    s->remove(0, qualifierSize + 1);
    while (!s->isEmpty() && s->at(0).isSpace())
        s->remove(0, 1);
    return true;
}

// Strip all const/volatile/*/&
void TypeInfo::stripQualifiers(QString *s)
{
    stripLeadingConst(s);
    stripLeadingVolatile(s);
    while (s->endsWith(u'&') || s->endsWith(u'*') || s->endsWith(u' '))
        s->chop(1);
}

// Helper functionality to simplify a raw standard type as returned by
// clang_getCanonicalType() for g++ standard containers from
// "std::__cxx11::list<int, std::allocator<int> >" or
// "std::__1::list<int, std::allocator<int> >" -> "std::list<int>".

bool TypeInfoData::isStdType() const
{
    return m_qualifiedName.size() > 1
        && m_qualifiedName.constFirst() == u"std";
}

bool TypeInfo::isStdType() const
{
    return d->isStdType();
}

static inline bool discardStdType(const QString &name)
{
    return name == u"allocator" || name == u"less";
}

void TypeInfoData::simplifyStdType()
{
    Q_ASSERT(isStdType());
    if (m_qualifiedName.at(1).startsWith(u"__"))
        m_qualifiedName.removeAt(1);
    for (auto t = m_instantiations.size() - 1; t >= 0; --t) {
        if (m_instantiations.at(t).isStdType()) {
            if (discardStdType(m_instantiations.at(t).qualifiedName().constLast()))
                m_instantiations.removeAt(t);
            else
                m_instantiations[t].simplifyStdType();
        }
    }
}

void TypeInfo::simplifyStdType()
{
    if (isStdType())
        d->simplifyStdType();
}

void TypeInfo::formatTypeSystemSignature(QTextStream &str) const
{
    if (d->m_constant)
        str << "const ";
    str << d->m_qualifiedName.join(u"::"_s);
    switch (d->m_referenceType) {
    case NoReference:
        break;
    case LValueReference:
        str << '&';
        break;
    case RValueReference:
        str << "&&";
        break;
    }
    for (auto i : d->m_indirections) {
        switch (i) {
        case Indirection::Pointer:
            str << '*';
            break;
        case Indirection::ConstPointer:
            str << "* const";
            break;
        }
    }
}

#ifndef QT_NO_DEBUG_STREAM
void TypeInfo::formatDebug(QDebug &debug) const
{
    debug << '"';
    formatSequence(debug, d->m_qualifiedName.begin(), d->m_qualifiedName.end(), "\", \"");
    debug << '"';
    if (d->m_constant)
        debug << ", [const]";
    if (d->m_volatile)
        debug << ", [volatile]";
    switch (d->m_category) {
    case TypeCategory::Other:
    case TypeCategory::Void:
        break;
    case TypeCategory::Builtin:
        debug << ", [builtin]";
        break;
    case TypeCategory::Enum:
        debug << ", [enum]";
        break;
    case TypeCategory::Pointer:
        debug << ", [pointer]";
        break;
    case TypeCategory::Function:
        debug << ", [function";
        break;
    }
    if (!d->m_indirections.isEmpty()) {
        debug << ", indirections=";
        for (auto i : d->m_indirections)
            debug << ' ' << TypeInfo::indirectionKeyword(i);
    }
    switch (d->m_referenceType) {
    case NoReference:
        break;
    case LValueReference:
        debug << ", [ref]";
        break;
    case RValueReference:
        debug << ", [rvalref]";
        break;
    }
    if (!d->m_instantiations.isEmpty()) {
        debug << ", template<";
        formatSequence(debug, d->m_instantiations.begin(), d->m_instantiations.end());
        debug << '>';
    }
    if (d->m_functionPointer) {
        debug << ", function ptr(";
        formatSequence(debug, d->m_arguments.begin(), d->m_arguments.end());
        debug << ')';
    }
    if (!d->m_arrayElements.isEmpty()) {
        debug << ", array[" << d->m_arrayElements.size() << "][";
        formatSequence(debug, d->m_arrayElements.begin(), d->m_arrayElements.end());
        debug << ']';
    }
}

QDebug operator<<(QDebug d, const TypeInfo &t)
{
    QDebugStateSaver s(d);
    const int verbosity = d.verbosity();
    d.noquote();
    d.nospace();
    d << "TypeInfo(";
    if (verbosity > 2)
        t.formatDebug(d);
    else
        d << t.toString();
    d << ')';
    return d;
}
#endif // !QT_NO_DEBUG_STREAM
