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

#include "abstractmetatype.h"
#include "abstractmetabuilder.h"
#include "abstractmetalang.h"
#include "messages.h"
#include "typedatabase.h"
#include "containertypeentry.h"
#include "enumtypeentry.h"
#include "flagstypeentry.h"

#include "qtcompat.h"
#include "typeinfo.h"

#ifndef QT_NO_DEBUG_STREAM
#  include <QtCore/QDebug>
#endif

#include <QtCore/QHash>
#include <QtCore/QSharedData>
#include <QtCore/QStack>

#include <memory>

using namespace Qt::StringLiterals;

using AbstractMetaTypeCPtr = std::shared_ptr<const AbstractMetaType>;

const QSet<QString> &AbstractMetaType::cppFloatTypes()
{
    static const QSet<QString> result{u"double"_s, u"float"_s};
    return result;
}

const QSet<QString> &AbstractMetaType::cppSignedCharTypes()
{
    static const QSet<QString> result{u"char"_s, u"signed char"_s};
    return result;
}

const QSet<QString> &AbstractMetaType::cppUnsignedCharTypes()
{
    static const QSet<QString> result{u"unsigned char"_s};
    return result;
}

const QSet<QString> &AbstractMetaType::cppCharTypes()
{
    static const QSet<QString> result = cppSignedCharTypes() | cppUnsignedCharTypes();
    return result;
}

const QSet<QString> &AbstractMetaType::cppSignedIntTypes()
{
    static QSet<QString> result;
    if (result.isEmpty()) {
        result = {u"char"_s, u"signed char"_s, u"short"_s, u"short int"_s,
                  u"signed short"_s, u"signed short int"_s,
                  u"int"_s, u"signed int"_s,
                  u"long"_s, u"long int"_s,
                  u"signed long"_s, u"signed long int"_s,
                  u"long long"_s, u"long long int"_s,
                  u"signed long long int"_s,
                  u"ptrdiff_t"_s};
        result |= cppSignedCharTypes();
    }
    return result;
}

const QSet<QString> &AbstractMetaType::cppUnsignedIntTypes()
{
    static QSet<QString> result;
    if (result.isEmpty()) {
        result = {u"unsigned short"_s, u"unsigned short int"_s,
                  u"unsigned"_s, u"unsigned int"_s,
                  u"unsigned long"_s, u"unsigned long int"_s,
                  u"unsigned long long"_s,
                  u"unsigned long long int"_s,
                  u"size_t"_s};
        result |= cppUnsignedCharTypes();
    }
    return result;
}

const QSet<QString> &AbstractMetaType::cppIntegralTypes()
{
    static QSet<QString> result;
    if (result.isEmpty()) {
        result |= cppSignedIntTypes();
        result |= cppUnsignedIntTypes();
        result.insert(u"bool"_s);
    }
    return result;
}

const QSet<QString> &AbstractMetaType::cppPrimitiveTypes()
{
    static QSet<QString> result;
    if (result.isEmpty()) {
        result |= cppIntegralTypes();
        result |= cppFloatTypes();
        result.insert(u"wchar_t"_s);
    }
    return result;
}

class AbstractMetaTypeData : public QSharedData
{
public:
    AbstractMetaTypeData(const TypeEntryCPtr &t);

    int actualIndirections() const;
    bool passByConstRef() const;
    bool passByValue() const;
    AbstractMetaType::TypeUsagePattern determineUsagePattern() const;
    bool hasTemplateChildren() const;
    QString formatSignature(bool minimal) const;
    QString formatPythonSignature() const;
    bool isEquivalent(const AbstractMetaTypeData &rhs) const;
    bool equals(const AbstractMetaTypeData &rhs) const;
    QStringList instantiationCppSignatures() const;

    template <class Predicate>
    bool generateOpaqueContainer(Predicate p) const;

    TypeEntryCPtr m_typeEntry;
    AbstractMetaTypeList m_instantiations;
    mutable QString m_cachedCppSignature;
    mutable QString m_cachedPythonSignature;
    QString m_originalTypeDescription;

    int m_arrayElementCount = -1;
    AbstractMetaTypeCPtr m_arrayElementType;
    AbstractMetaTypeCPtr m_originalTemplateType;
    AbstractMetaTypeCPtr m_viewOn;
    AbstractMetaType::Indirections m_indirections;

    AbstractMetaType::TypeUsagePattern m_pattern = AbstractMetaType::VoidPattern;
    uint m_constant : 1;
    uint m_volatile : 1;
    uint m_signaturesDirty : 1;
    uint m_reserved : 29; // unused

    ReferenceType m_referenceType = NoReference;
    AbstractMetaTypeList m_children;
};

AbstractMetaTypeData::AbstractMetaTypeData(const TypeEntryCPtr &t) :
    m_typeEntry(t),
    m_constant(false),
    m_volatile(false),
    m_signaturesDirty(true),
    m_reserved(0)
{
}

QStringList AbstractMetaTypeData::instantiationCppSignatures() const
{
    QStringList result;
    for (const auto &i : m_instantiations)
        result.append(i.cppSignature());
    return result;
}

AbstractMetaType::AbstractMetaType(const TypeEntryCPtr &t) :
    d(new AbstractMetaTypeData(t))
{
    Q_ASSERT(t);
}

AbstractMetaType::AbstractMetaType()
{
    *this = AbstractMetaType::createVoid();
}

AbstractMetaType &AbstractMetaType::operator=(const AbstractMetaType &) = default;

AbstractMetaType::AbstractMetaType(const AbstractMetaType &rhs) = default;

AbstractMetaType::AbstractMetaType(AbstractMetaType &&) noexcept = default;

AbstractMetaType &AbstractMetaType::operator=(AbstractMetaType &&) noexcept = default;

AbstractMetaType::~AbstractMetaType() = default;

QString AbstractMetaType::package() const
{
    return d->m_typeEntry->targetLangPackage();
}

QString AbstractMetaType::name() const
{
    return d->m_typeEntry->targetLangEntryName();
}

QString AbstractMetaType::fullName() const
{
    return d->m_typeEntry->qualifiedTargetLangName();
}

void AbstractMetaType::setTypeUsagePattern(AbstractMetaType::TypeUsagePattern pattern)
{
    if (d->m_pattern != pattern)
        d->m_pattern = pattern;
}

AbstractMetaType::TypeUsagePattern AbstractMetaType::typeUsagePattern() const
{
    return d->m_pattern;
}

bool AbstractMetaType::hasInstantiations() const
{
    return !d->m_instantiations.isEmpty();
}

void AbstractMetaType::addInstantiation(const AbstractMetaType &inst)
{
    d->m_instantiations << inst;
}

void AbstractMetaType::setInstantiations(const AbstractMetaTypeList &insts)
{
    if (insts != d->m_instantiations)
        d->m_instantiations = insts;
}

const AbstractMetaTypeList &AbstractMetaType::instantiations() const
{
    return d->m_instantiations;
}

QStringList AbstractMetaType::instantiationCppSignatures() const
{
    return d->instantiationCppSignatures();
}

// For applying the <array> function argument modification: change into a type
// where "int *" becomes "int[]".
bool AbstractMetaType::applyArrayModification(QString *errorMessage)
{

    if (d->m_pattern == AbstractMetaType::NativePointerAsArrayPattern) {
        *errorMessage = u"<array> modification already applied."_s;
        return false;
    }
    if (d->m_arrayElementType)  {
        QTextStream(errorMessage) << "The type \"" << cppSignature()
            << "\" is an array of " << d->m_arrayElementType->name() << '.';
        return false;
    }
    if (d->m_indirections.isEmpty()) {
        QTextStream(errorMessage) << "The type \"" << cppSignature()
            << "\" does not have indirections.";
        return false;
    }
    // Element type to be used for ArrayHandle<>, strip constness.

    auto *elementType = new AbstractMetaType(*this);
    auto indir = indirectionsV();
    indir.pop_front();
    elementType->setIndirectionsV(indir);
    elementType->setConstant(false);
    elementType->setVolatile(false);
    elementType->decideUsagePattern();
    d->m_arrayElementType.reset(elementType);
    d->m_pattern = AbstractMetaType::NativePointerAsArrayPattern;
    return true;
}

TypeEntryCPtr AbstractMetaType::typeEntry() const
{
    return d->m_typeEntry;
}

void AbstractMetaType::setTypeEntry(const TypeEntryCPtr &type)
{
    if (d->m_typeEntry != type)
        d->m_typeEntry = type;
}

void AbstractMetaType::setOriginalTypeDescription(const QString &otd)
{
    if (d->m_originalTypeDescription != otd)
        d->m_originalTypeDescription = otd;
}

QString AbstractMetaType::originalTypeDescription() const
{
    return d->m_originalTypeDescription;
}

void AbstractMetaType::setOriginalTemplateType(const AbstractMetaType &type)
{
    if (!d->m_originalTemplateType || *d->m_originalTemplateType != type)
        d->m_originalTemplateType = std::make_shared<AbstractMetaType>(type);
}

const AbstractMetaType *AbstractMetaType::originalTemplateType() const
{
    return d->m_originalTemplateType.get();
}

AbstractMetaType AbstractMetaType::getSmartPointerInnerType() const
{
    Q_ASSERT(isSmartPointer());
    Q_ASSERT(!d->m_instantiations.isEmpty());
    AbstractMetaType innerType = d->m_instantiations.at(0);
    return innerType;
}

QString AbstractMetaType::getSmartPointerInnerTypeName() const
{
    Q_ASSERT(isSmartPointer());
    return getSmartPointerInnerType().name();
}

AbstractMetaTypeList AbstractMetaType::nestedArrayTypes() const
{
    AbstractMetaTypeList result;
    switch (d->m_pattern) {
    case ArrayPattern:
        for (AbstractMetaType t = *this; t.typeUsagePattern() == ArrayPattern; ) {
            const AbstractMetaType *elt = t.arrayElementType();
            result.append(*elt);
            t = *elt;
        }
        break;
    case NativePointerAsArrayPattern:
        result.append(*d->m_arrayElementType.get());
        break;
    default:
        break;
    }
    return result;
}

bool AbstractMetaTypeData::passByConstRef() const
{
    return m_constant && m_referenceType == LValueReference && m_indirections.isEmpty();
}

bool AbstractMetaType::passByConstRef() const
{
    return d->passByConstRef();
}

bool AbstractMetaTypeData::passByValue() const
{
    return m_referenceType == NoReference && m_indirections.isEmpty();
}

bool AbstractMetaType::passByValue() const
{
    return d->passByValue();
}

bool AbstractMetaType::useStdMove() const
{
    return (isUniquePointer() && d->passByValue())
        || d->m_referenceType == RValueReference;
}

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

void AbstractMetaType::setReferenceType(ReferenceType ref)
{
    if (d->m_referenceType != ref) {
        d->m_referenceType = ref;
        d->m_signaturesDirty = true;
    }
}

int AbstractMetaTypeData::actualIndirections() const
{
    return m_indirections.size() + (m_referenceType == LValueReference ? 1 : 0);
}

int AbstractMetaType::actualIndirections() const
{
    return d->actualIndirections();
}

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

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

void AbstractMetaType::clearIndirections()
{
    if (!d->m_indirections.isEmpty()) {
        d->m_indirections.clear();
        d->m_signaturesDirty = true;
    }
}

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

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

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

void AbstractMetaType::setArrayElementCount(int n)
{
    if (d->m_arrayElementCount != n) {
        d->m_arrayElementCount = n;
        d->m_signaturesDirty = true;
    }
}

int AbstractMetaType::arrayElementCount() const
{
    return d->m_arrayElementCount;
}

const AbstractMetaType *AbstractMetaType::arrayElementType() const
{
    return d->m_arrayElementType.get();
}

void AbstractMetaType::setArrayElementType(const AbstractMetaType &t)
{
    if (!d->m_arrayElementType || *d->m_arrayElementType != t) {
        d->m_arrayElementType = std::make_shared<AbstractMetaType>(t);
        d->m_signaturesDirty = true;
    }
}

AbstractMetaType AbstractMetaType::plainType() const
{
    AbstractMetaType result = *this;
    result.clearIndirections();
    result.setReferenceType(NoReference);
    result.setConstant(false);
    result.setVolatile(false);
    result.decideUsagePattern();
    return result;
}

QString AbstractMetaType::cppSignature() const
{
    const AbstractMetaTypeData *cd = d.constData();
    if (cd->m_cachedCppSignature.isEmpty() || cd->m_signaturesDirty)
        cd->m_cachedCppSignature = formatSignature(false);
    return cd->m_cachedCppSignature;
}

QString AbstractMetaType::pythonSignature() const
{
    // PYSIDE-921: Handle container returntypes correctly.
    // This is now a clean reimplementation.
    const AbstractMetaTypeData *cd = d.constData();
    if (cd->m_cachedPythonSignature.isEmpty() || cd->m_signaturesDirty)
        cd->m_cachedPythonSignature = formatPythonSignature();
    return cd->m_cachedPythonSignature;
}

AbstractMetaType::TypeUsagePattern AbstractMetaTypeData::determineUsagePattern() const
{
    if (m_typeEntry->isTemplateArgument())
        return AbstractMetaType::TemplateArgument;

    if (m_typeEntry->type() == TypeEntry::ConstantValueType)
        return AbstractMetaType::NonTypeTemplateArgument;

    if (m_typeEntry->isPrimitive() && (actualIndirections() == 0 || passByConstRef()))
        return AbstractMetaType::PrimitivePattern;

    if (m_typeEntry->isVoid()) {
        return m_arrayElementCount < 0 && m_referenceType == NoReference
            && m_indirections.isEmpty() && m_constant == 0 && m_volatile == 0
            ? AbstractMetaType::VoidPattern : AbstractMetaType::NativePointerPattern;
    }

    if (m_typeEntry->isVarargs())
        return AbstractMetaType::VarargsPattern;

    if (m_typeEntry->isEnum() && (actualIndirections() == 0 || passByConstRef()))
        return AbstractMetaType::EnumPattern;

    if (m_typeEntry->isObject()) {
        if (m_indirections.isEmpty() && m_referenceType == NoReference)
            return AbstractMetaType::ValuePattern;
        return AbstractMetaType::ObjectPattern;
    }

    if (m_typeEntry->isContainer() && m_indirections.isEmpty())
        return AbstractMetaType::ContainerPattern;

    if (m_typeEntry->isSmartPointer() && m_indirections.isEmpty())
        return AbstractMetaType::SmartPointerPattern;

    if (m_typeEntry->isFlags() && (actualIndirections() == 0 || passByConstRef()))
        return AbstractMetaType::FlagsPattern;

    if (m_typeEntry->isArray())
        return AbstractMetaType::ArrayPattern;

    if (m_typeEntry->isValue())
        return m_indirections.size() == 1 ? AbstractMetaType::ValuePointerPattern : AbstractMetaType::ValuePattern;

    return AbstractMetaType::NativePointerPattern;
}

AbstractMetaType::TypeUsagePattern AbstractMetaType::determineUsagePattern() const
{
    return d->determineUsagePattern();
}

void AbstractMetaType::decideUsagePattern()
{
    TypeUsagePattern pattern = determineUsagePattern();
    if (d->m_typeEntry->isObject() && indirections() == 1
        && d->m_referenceType == LValueReference && isConstant()) {
        // const-references to pointers can be passed as pointers
        setReferenceType(NoReference);
        setConstant(false);
        pattern = ObjectPattern;
    }
    setTypeUsagePattern(pattern);
    Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty());
}

bool AbstractMetaTypeData::hasTemplateChildren() const
{
    QStack<AbstractMetaType> children;
    children << m_instantiations;

    // Recursively iterate over the children / descendants of the type, to check if any of them
    // corresponds to a template argument type.
    while (!children.isEmpty()) {
        AbstractMetaType child = children.pop();
        if (child.typeEntry()->isTemplateArgument())
            return true;
        children << child.instantiations();
    }
    return false;
}

bool AbstractMetaType::hasTemplateChildren() const
{
    return d->hasTemplateChildren();
}

static inline QString formatArraySize(int e)
{
    QString result;
    result += u'[';
    if (e >= 0)
        result += QString::number(e);
    result += u']';
    return result;
}

// Return the number of template parameters; remove the default
// non template type parameter of std::span from the signature.
static qsizetype stripDefaultTemplateArgs(const TypeEntryCPtr &te,
                                          const AbstractMetaTypeList &instantiations)
{
    static const char16_t dynamicExtent64[] = u"18446744073709551615"; // size_t(-1)
    static const char16_t dynamicExtent32[] = u"4294967295";

    qsizetype result = instantiations.size();
    if (result == 0 || !te->isContainer())
        return result;
    auto cte = std::static_pointer_cast<const ContainerTypeEntry>(te);
    if (cte->containerKind() != ContainerTypeEntry::SpanContainer)
        return result;
    const auto lastTe = instantiations.constLast().typeEntry();
    if (lastTe->type() == TypeEntry::ConstantValueType) {
        const QString &name = lastTe->name();
        if (name == dynamicExtent64 || name == dynamicExtent32)
           --result;
    }
    return result;
}

QString AbstractMetaTypeData::formatSignature(bool minimal) const
{
    QString result;
    if (m_constant)
        result += u"const "_s;
    if (m_volatile)
        result += u"volatile "_s;
    if (m_pattern == AbstractMetaType::ArrayPattern) {
        // Build nested array dimensions a[2][3] in correct order
        result += m_arrayElementType->minimalSignature();
        const auto arrayPos = result.indexOf(u'[');
        if (arrayPos != -1)
            result.insert(arrayPos, formatArraySize(m_arrayElementCount));
        else
            result.append(formatArraySize(m_arrayElementCount));
    } else {
        result += m_typeEntry->qualifiedCppName();
    }
    if (!m_instantiations.isEmpty()) {
        result += u'<';
        const auto size = stripDefaultTemplateArgs(m_typeEntry, m_instantiations);
        for (qsizetype i = 0; i < size; ++i) {
            if (i > 0)
                result += u',';
            result += m_instantiations.at(i).minimalSignature();
        }
        result += u'>';
    }

    if (!minimal && (!m_indirections.isEmpty() || m_referenceType != NoReference))
        result += u' ';
    for (Indirection i : m_indirections)
        result += TypeInfo::indirectionKeyword(i);
    switch (m_referenceType) {
    case NoReference:
        break;
    case LValueReference:
        result += u'&';
        break;
    case RValueReference:
        result += u"&&"_s;
        break;
    }
    return result;
}

QString AbstractMetaType::formatSignature(bool minimal) const
{
    return d->formatSignature(minimal);
}

QString AbstractMetaTypeData::formatPythonSignature() const
{
    /*
     * This is a version of the above, more suitable for Python.
     * We avoid extra keywords that are not needed in Python.
     * We prepend the package name, unless it is a primitive type.
     *
     * Primitive types like 'int', 'char' etc.:
     * When we have a primitive with an indirection, we use that '*'
     * character for later postprocessing, since those indirections
     * need to be modified into a result tuple.
     * Smart pointer instantiations: Drop the package
     */
    QString result;
    if (m_pattern == AbstractMetaType::NativePointerAsArrayPattern)
        result += u"array "_s;
    // We no longer use the "const" qualifier for heuristics. Instead,
    // NativePointerAsArrayPattern indicates when we have <array> in XML.
    // if (m_typeEntry->isPrimitive() && isConstant())
    //     result += QLatin1String("const ");
    if (!m_typeEntry->isPrimitive() && !m_typeEntry->isSmartPointer()) {
        const QString package = m_typeEntry->targetLangPackage();
        if (!package.isEmpty())
            result += package + u'.';
    }
    if (m_pattern == AbstractMetaType::ArrayPattern) {
        // Build nested array dimensions a[2][3] in correct order
        result += m_arrayElementType->formatPythonSignature();
        const auto arrayPos = result.indexOf(u'[');
        if (arrayPos != -1)
            result.insert(arrayPos, formatArraySize(m_arrayElementCount));
        else
            result.append(formatArraySize(m_arrayElementCount));
    } else {
        result += m_typeEntry->targetLangName();
    }
    if (!m_instantiations.isEmpty()) {
        result += u'[';
        for (qsizetype i = 0, size = m_instantiations.size(); i < size; ++i) {
            if (i > 0)
                result += u", "_s;
            result += m_instantiations.at(i).formatPythonSignature();
        }
        result += u']';
    }
    if (m_typeEntry->isPrimitive())
        for (Indirection i : m_indirections)
            result += TypeInfo::indirectionKeyword(i);
    // If it is a flags type, we replace it with the full name of the enum:
    // "PySide6.QtCore.Qt.ItemFlag" instead of "PySide6.QtCore.QFlags<Qt.ItemFlag>"
    if (m_typeEntry->isFlags()) {
        const auto fte = std::static_pointer_cast<const FlagsTypeEntry>(m_typeEntry);
        result = fte->originator()->qualifiedTargetLangName();
    }
    result.replace(u"::"_s, u"."_s);
    return result;
}

QString AbstractMetaType::formatPythonSignature() const
{
    return d->formatPythonSignature();
}

bool AbstractMetaType::isCppPrimitive() const
{
    return d->m_pattern == PrimitivePattern && ::isCppPrimitive(d->m_typeEntry);
}

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

void AbstractMetaType::setConstant(bool constant)
{
    if (d->m_constant != constant) {
        d->m_constant = constant;
        d->m_signaturesDirty = true;
    }
}

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

void AbstractMetaType::setVolatile(bool v)
{
    if (d->m_volatile != v) {
        d->m_volatile = v;
        d->m_signaturesDirty = true;\
    }
}

static bool equalsCPtr(const AbstractMetaTypeCPtr &t1, const AbstractMetaTypeCPtr &t2)
{
    if (bool(t1) != bool(t2))
        return false;
    return !t1 || *t1 == *t2;
}

bool AbstractMetaTypeData::isEquivalent(const AbstractMetaTypeData &rhs) const
{
    if (m_typeEntry != rhs.m_typeEntry
        || m_indirections != rhs.m_indirections
        || m_arrayElementCount != rhs.m_arrayElementCount) {
        return false;
    }

    if (!equalsCPtr(m_arrayElementType, rhs.m_arrayElementType))
        return false;

    if (!equalsCPtr(m_viewOn, rhs.m_viewOn))
        return false;

    if (m_instantiations != rhs.m_instantiations)
        return false;
    return true;
}

bool AbstractMetaTypeData::equals(const AbstractMetaTypeData &rhs) const
{
    return m_constant == rhs.m_constant && m_volatile == rhs.m_volatile
        && m_referenceType == rhs.m_referenceType && isEquivalent(rhs);
}

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

bool AbstractMetaType::isEquivalent(const AbstractMetaType &rhs) const
{
    return d.constData() == rhs.d.constData() || d->isEquivalent(*rhs.d);
}

const AbstractMetaType *AbstractMetaType::viewOn() const
{
    return d->m_viewOn.get();
}

void AbstractMetaType::setViewOn(const AbstractMetaType &v)
{
    if (!d->m_viewOn || *d->m_viewOn != v)
        d->m_viewOn = std::make_shared<AbstractMetaType>(v);
}

AbstractMetaType AbstractMetaType::createVoid()
{
    static QScopedPointer<AbstractMetaType> metaType;
    if (metaType.isNull()) {
        static TypeEntryCPtr voidTypeEntry = TypeDatabase::instance()->findType(u"void"_s);
        Q_ASSERT(voidTypeEntry);
        metaType.reset(new AbstractMetaType(voidTypeEntry));
        metaType->decideUsagePattern();
    }
    return *metaType.data();
}

void AbstractMetaType::dereference(QString *type)
{
    type->prepend(u"(*"_s);
    type->append(u')');
}

QString AbstractMetaType::dereferencePrefix(qsizetype n)
{
    const QChar c = n > 0 ? u'*' : u'&';
    return QString(qAbs(n), c);
}

void AbstractMetaType::applyDereference(QString *type, qsizetype n)
{
    if (n == 0)
        return;

    type->prepend(dereferencePrefix(n));
    type->prepend(u'(');
    type->append(u')');
}

bool AbstractMetaType::stripDereference(QString *type)
{
    if (type->startsWith(u"(*") && type->endsWith(u')')) {
        type->chop(1);
        type->remove(0, 2);
        *type = type->trimmed();
        return true;
    }
    if (type->startsWith(u'*')) {
        type->remove(0, 1);
        *type = type->trimmed();
        return true;
    }
    return false;
}

// Query functions for generators
bool AbstractMetaType::isObjectType() const
{
    return d->m_typeEntry->isObject();
}

bool AbstractMetaType::isUniquePointer() const
{
    return isSmartPointer() && d->m_typeEntry->isUniquePointer();
}

bool AbstractMetaType::isPointer() const
{
    return !d->m_indirections.isEmpty()
        || isNativePointer() || isValuePointer();
}

bool AbstractMetaType::isPointerToConst() const
{
    return d->m_constant && !d->m_indirections.isEmpty()
        && d->m_indirections.constLast() != Indirection::ConstPointer;
}

bool AbstractMetaType::isCString() const
{
    return isNativePointer()
        && d->m_indirections.size() == 1
        && name() == u"char";
}

bool AbstractMetaType::isVoidPointer() const
{
    return isNativePointer()
        && d->m_indirections.size() == 1
        && name() == u"void";
}

bool AbstractMetaType::isUserPrimitive() const
{
    return d->m_indirections.isEmpty() && ::isUserPrimitive(d->m_typeEntry);
}

bool AbstractMetaType::isObjectTypeUsedAsValueType() const
{
    return d->m_typeEntry->isObject() && d->m_referenceType == NoReference
        && d->m_indirections.isEmpty();
}

bool AbstractMetaType::isWrapperType() const
{
    return d->m_typeEntry->isWrapperType();
}

bool AbstractMetaType::isPointerToWrapperType() const
{
    return (isObjectType() && d->m_indirections.size() == 1) || isValuePointer();
}

bool AbstractMetaType::isWrapperPassedByReference() const
{
    return d->m_referenceType == LValueReference && isWrapperType()
           && !isPointer();
}

bool AbstractMetaType::isCppIntegralPrimitive() const
{
    return ::isCppIntegralPrimitive(d->m_typeEntry);
}

bool AbstractMetaType::isExtendedCppPrimitive() const
{
    if (isCString() || isVoidPointer())
        return true;
    if (!d->m_indirections.isEmpty())
        return false;
    return ::isExtendedCppPrimitive(d->m_typeEntry);
}

bool AbstractMetaType::isValueTypeWithCopyConstructorOnly() const
{
    bool result = false;
    if (d->m_typeEntry->isComplex()) {
        const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(d->m_typeEntry);
        result = cte->isValueTypeWithCopyConstructorOnly();
    }
    return result;
}

bool AbstractMetaType::valueTypeWithCopyConstructorOnlyPassed() const
{
    return (passByValue() || passByConstRef())
           && isValueTypeWithCopyConstructorOnly();
}

using AbstractMetaTypeCache = QHash<QString, AbstractMetaType>;

Q_GLOBAL_STATIC(AbstractMetaTypeCache, metaTypeFromStringCache)

std::optional<AbstractMetaType>
AbstractMetaType::fromString(const QString &typeSignatureIn, QString *errorMessage)
{
    auto &cache = *metaTypeFromStringCache();
    auto it = cache.find(typeSignatureIn);
    if (it != cache.end())
        return it.value();

    QString typeSignature = typeSignatureIn.trimmed();
    if (typeSignature.startsWith(u"::"))
        typeSignature.remove(0, 2);

    it = cache.find(typeSignature);
    if (it == cache.end()) {
        auto metaType =
            AbstractMetaBuilder::translateType(typeSignature, nullptr, {}, errorMessage);
        if (Q_UNLIKELY(!metaType.has_value())) {
            if (errorMessage)
                errorMessage->prepend(msgCannotBuildMetaType(typeSignature));
            return {};
        }
        if (typeSignature != typeSignatureIn)
            cache.insert(typeSignatureIn, metaType.value());
        it = cache.insert(typeSignature, metaType.value());
    }
    return it.value();
}

AbstractMetaType AbstractMetaType::fromTypeEntry(const TypeEntryCPtr &typeEntry)
{
    QString typeName = typeEntry->qualifiedCppName();
    if (typeName.startsWith(u"::"))
        typeName.remove(0, 2);
    auto &cache  = *metaTypeFromStringCache();
    auto it = cache.find(typeName);
    if (it != cache.end())
        return it.value();
    AbstractMetaType metaType = AbstractMetaType(typeEntry).plainType();
    cache.insert(typeName, metaType);
    return metaType;
}

AbstractMetaType AbstractMetaType::fromAbstractMetaClass(const AbstractMetaClassCPtr &metaClass)
{
    return fromTypeEntry(metaClass->typeEntry());
}

template <class Predicate> // Predicate(containerTypeEntry, signature)
bool AbstractMetaTypeData::generateOpaqueContainer(Predicate pred) const
{
    // Allow for passing containers by pointer as well.
    if (!m_typeEntry->isContainer())
        return false;
    if (m_indirections.size() > 1)
        return false;
    auto containerTypeEntry = std::static_pointer_cast<const ContainerTypeEntry>(m_typeEntry);
    auto kind = containerTypeEntry->containerKind();
    if (kind != ContainerTypeEntry::ListContainer && kind != ContainerTypeEntry::SpanContainer)
        return false;

    const auto &firstInstantiation = m_instantiations.constFirst();
    if (firstInstantiation.referenceType() != NoReference)
        return false;
    switch (firstInstantiation.typeEntry()->type()) {
    case TypeEntry::PrimitiveType:
    case TypeEntry::FlagsType:
    case TypeEntry::EnumType:
    case TypeEntry::BasicValueType:
    case TypeEntry::ObjectType:
    case TypeEntry::CustomType:
        break;
    default:
        return false;
    }

    return pred(containerTypeEntry, instantiationCppSignatures());
}

// Simple predicate for checking whether an opaque container should be generated
static bool opaqueContainerPredicate(const ContainerTypeEntryCPtr &t,
                                     const QStringList &instantiations)
{
    return t->generateOpaqueContainer(instantiations);
}

bool AbstractMetaType::generateOpaqueContainer() const
{
    return d->generateOpaqueContainer(opaqueContainerPredicate);
}

// Helper for determining whether a function should return an opaque container,
// that is, the function return type is modified accordingly
// (cf AbstractMetaFunction::generateOpaqueContainerReturn())
bool AbstractMetaType::generateOpaqueContainerForGetter(const QString &modifiedType) const
{
    auto predicate = [&modifiedType](const ContainerTypeEntryCPtr &t,
                                     const QStringList &instantiations) {
        return t->opaqueContainerName(instantiations) == modifiedType;
    };
    return d->generateOpaqueContainer(predicate);
}

#ifndef QT_NO_DEBUG_STREAM
void AbstractMetaType::formatDebug(QDebug &debug) const
{
    debug << '"' << name() << '"';
    if (debug.verbosity() > 2 && !isVoid()) {
        auto te = typeEntry();
        debug << ", typeEntry=";
        if (debug.verbosity() > 3)
            debug << te;
        else
            debug << "(\"" << te->qualifiedCppName() << "\", " << te->type() << ')';
        debug << ", signature=\"" << cppSignature() << "\", pattern="
            << typeUsagePattern();
        const auto indirections = indirectionsV();
        if (!indirections.isEmpty()) {
            debug << ", indirections=";
            for (auto i : indirections)
                debug << ' ' << TypeInfo::indirectionKeyword(i);
        }
        if (referenceType() != NoReference)
            debug << ", reftype=" << referenceType();
        if (isConstant())
            debug << ", [const]";
        if (isVolatile())
            debug << ", [volatile]";
        if (isArray()) {
            debug << ", array of \"" << arrayElementType()->cppSignature()
                << "\", arrayElementCount="  << arrayElementCount();
        }
        const auto &instantiations = this->instantiations();
        if (const auto instantiationsSize = instantiations.size()) {
            debug << ", instantiations[" << instantiationsSize << "]=<";
            for (qsizetype i = 0; i < instantiationsSize; ++i) {
                if (i)
                    debug << ", ";
                instantiations.at(i).formatDebug(debug);
            }
        }
        debug << '>';
        if (viewOn())
            debug << ", views " << viewOn()->name();
    }
}

QDebug operator<<(QDebug d, const AbstractMetaType &at)
{
    QDebugStateSaver saver(d);
    d.noquote();
    d.nospace();
    d << "AbstractMetaType(";
    at.formatDebug(d);
    d << ')';
    return d;
}

QDebug operator<<(QDebug d, const AbstractMetaType *at)
{
    QDebugStateSaver saver(d);
    d.noquote();
    d.nospace();
    if (at)
        d << *at;
    else
        d << "AbstractMetaType(0)";
    return d;
}

#endif // !QT_NO_DEBUG_STREAM
