// 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 "moc.h"

#include <QAxObject>
#include <QAxBaseWidget>
#include <QFile>
#include <QMetaObject>
#include <QMetaEnum>
#include <QDebug>
#include <QTextStream>
#include <QSettings>
#include <QStringList>
#include <QTemporaryFile>
#include <QUuid>
#include <QCoreApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QFileInfo>
#include <qt_windows.h>
#include <ocidl.h>
#include <private/qmetaobject_p.h>
QT_BEGIN_NAMESPACE

static ITypeInfo *currentTypeInfo = nullptr;

enum ProgramMode {
    GenerateMode,
    TypeLibID
};

enum ObjectCategory
{
    DefaultObject    = 0x00,
    SubObject        = 0x001,
    ActiveX          = 0x002,
    NoMetaObject     = 0x004,
    NoImplementation = 0x008,
    NoDeclaration    = 0x010,
    NoInlines        = 0x020,
    OnlyInlines      = 0x040,
    Licensed         = 0x100,
};

Q_DECLARE_FLAGS(ObjectCategories, ObjectCategory)
Q_DECLARE_OPERATORS_FOR_FLAGS(ObjectCategories)

extern QMetaObject *qax_readEnumInfo(ITypeLib *typeLib, const QMetaObject *parentObject);
extern QMetaObject *qax_readClassInfo(ITypeLib *typeLib, ITypeInfo *typeInfo, const QMetaObject *parentObject);
extern QMetaObject *qax_readInterfaceInfo(ITypeLib *typeLib, ITypeInfo *typeInfo, const QMetaObject *parentObject);
extern QByteArrayList qax_qualified_usertypes;
extern QHash<QByteArray, QByteArray> qax_enum_values;
extern QString qax_docuFromName(ITypeInfo *typeInfo, const QString &name);
extern bool qax_dispatchEqualsIDispatch;
extern void qax_deleteMetaObject(QMetaObject *mo);

static QMap<QByteArray, QByteArray> namespaceForType;
static QList<QByteArray> strings;
static QHash<QByteArray, int> stringIndex; // Optimization, speeds up generation
static QByteArrayList vTableOnlyStubs;

void writeEnums(QTextStream &out, const QMetaObject *mo)
{
    // enums
    for (int ienum = mo->enumeratorOffset(); ienum < mo->enumeratorCount(); ++ienum) {
        formatCppEnum(out, mo->enumerator(ienum));
        out << '\n';
    }
}

void writeHeader(QTextStream &out, const QString &nameSpace, const QString &outFileName)
{
    out << "#ifndef QAX_DUMPCPP_" << outFileName.toUpper() << "_H" << Qt::endl;
    out << "#define QAX_DUMPCPP_" << outFileName.toUpper() << "_H" << Qt::endl;
    out << Qt::endl;
    out << "// Define this symbol to __declspec(dllexport) or __declspec(dllimport)" << Qt::endl;
    out << "#ifndef " << nameSpace.toUpper() << "_EXPORT" << Qt::endl;
    out << "#define " << nameSpace.toUpper() << "_EXPORT" << Qt::endl;
    out << "#endif" << Qt::endl;
    out << Qt::endl;
    out << "#include <qaxobject.h>" << Qt::endl;
    out << "#include <qaxwidget.h>" << Qt::endl;
    out << "#include <qdatetime.h>" << Qt::endl;
    out << "#include <qpixmap.h>" << Qt::endl;
    out << Qt::endl;
    out << "struct IDispatch;" << Qt::endl;
    out << Qt::endl;
}

void generateNameSpace(QTextStream &out, const QMetaObject *mo, const QByteArray &nameSpace)
{
    out << "namespace " << nameSpace << " {" << Qt::endl;
    out << Qt::endl;
    writeEnums(out, mo);

    // don't close on purpose
}

static QByteArray joinParameterNames(const QByteArrayList &parameterNames)
{
    QByteArray slotParameters;
    for (qsizetype p = 0; p < parameterNames.size(); ++p) {
        slotParameters += parameterNames.at(p);
        if (p < parameterNames.size() - 1)
            slotParameters += ',';
    }

    return slotParameters;
}

QByteArray constRefify(const QByteArray &type)
{
    QByteArray ctype(type);
    if (type == "QString" || type == "QPixmap"
        || type == "QVariant" || type == "QDateTime"
        || type == "QColor" || type == "QFont"
        || type == "QByteArray" || type == "QValueList<QVariant>"
        || type == "QStringList")
        ctype = "const " + ctype + '&';

    return ctype;
}

static void formatConstructorSignature(QTextStream &out, ObjectCategories category,
                                       bool declaration)
{
    out << '(';
    if (category & Licensed) {
        out << "const QString &licenseKey, ";
        if (declaration)
            out << " = QString()";
        out << ", ";
    }
    if (category & ActiveX) {
        out << "QWidget *parent";
        if (declaration)
            out << " = nullptr";
        out << ", Qt::WindowFlags f";
        if (declaration)
            out << " = {}";
    } else if (category & SubObject) {
        out << "IDispatch *subobject";
        if (declaration)
            out << " = nullptr";
        out << ", QAxObject *parent";
        if (declaration)
            out << " = nullptr";
    } else {
        out << "QObject *parent";
        if (declaration)
            out << " = nullptr";
    }
    out << ')';
}

static void formatMetaFunctions(QTextStream &out, const QByteArray &nameSpace,
                             const QByteArray &className, ObjectCategories category)
{
    // add a replacement qt_metacall()
    QStringView parentClass = (category & ActiveX) ? u"QAxWidget" : u"QAxObject";
    out << "int ";
    if (!nameSpace.isEmpty())
        out << nameSpace << "::";
    out << className << "::qt_metacall(QMetaObject::Call _c, int _id, void **_a)\n"
           "{\n"
           "    return " << parentClass << "::qt_metacall(_c, _id, _a);\n"
           "}\n\n";
}

static void formatConstructorBody(QTextStream &out, const QByteArray &nameSpace,
                                  const QByteArray &className,
                                  const QString &controlID, ObjectCategories category, bool useControlName)
{
    QString controlName;
    if (useControlName) {
        if (!nameSpace.isEmpty())
            controlName = QString::fromUtf8(nameSpace) + QStringLiteral(".");
        controlName += QString::fromUtf8(className);
    } else {
        controlName = controlID;
    }
    if (!nameSpace.isEmpty())
        out << nameSpace << "::";
    out << className << "::" << className;
    formatConstructorSignature(out, category, false);
    out << " :" << Qt::endl << "    ";
    if (category & ActiveX)
        out << "QAxWidget(parent, f";
    else if (category & SubObject)
        out << "QAxObject(subobject, parent";
    else
        out << "QAxObject(parent";
    out << ')' << Qt::endl << '{' << Qt::endl;
    if (category & SubObject) {
        out << "    internalRelease();" << Qt::endl;
    } else if (category & Licensed) {
        out << "    if (licenseKey.isEmpty())" << Qt::endl;
        out << "        setControl(QStringLiteral(\"" << controlName << "\"));" << Qt::endl;
        out << "    else" << Qt::endl;
        out << "        setControl(QStringLiteral(\"" << controlName << ":\") + licenseKey);" << Qt::endl;
    } else {
        out << "    setControl(QStringLiteral(\"" << controlName << "\"));" << Qt::endl;
    }
    out << '}' << Qt::endl << Qt::endl;
}

// Hash of C# only types.
static const QSet<QByteArray> cSharpTypes = {
    "ICloneable", "ICollection", "IDisposable", "IEnumerable",
    "IList", "ISerializable", "_Attribute"
};

void generateClassDecl(QTextStream &out, const QMetaObject *mo,
                       const QByteArray &className, const QByteArray &nameSpace,
                       ObjectCategories category)
{
    QByteArrayList functions;

    QByteArray indent;
    if (!(category & OnlyInlines))
        indent = "    ";

    if (!(category & OnlyInlines)) {
        // constructors
        out << "class " << nameSpace.toUpper() << "_EXPORT " << className << " : public ";
        if (category & ActiveX)
            out << "QAxWidget";
        else
            out << "QAxObject";
        out << Qt::endl;

        out << '{' << Qt::endl;
        out << "public:" << Qt::endl << "    explicit " << className;
        formatConstructorSignature(out, category, true);
        out << ';' << Qt::endl;
    }

    functions << className;

    // enums
    if (nameSpace.isEmpty() && !(category & OnlyInlines))
        writeEnums(out, mo);
    // QAxBase public virtual functions.
    QByteArrayList axBase_vfuncs;
    axBase_vfuncs.append("metaObject");
    axBase_vfuncs.append("qObject");
    axBase_vfuncs.append("className");
    axBase_vfuncs.append("propertyWritable");
    axBase_vfuncs.append("setPropertyWritable");

    // properties
    for (int iprop = mo->propertyOffset(); iprop < mo->propertyCount(); ++iprop) {
        QMetaProperty property = mo->property(iprop);
        if (!property.isReadable())
            continue;

        QByteArray propertyName(property.name());
        if (propertyName == className)
            continue;

        if (!(category & OnlyInlines)) {
            out << indent << "/*" << Qt::endl << indent << "Property " << propertyName << Qt::endl;
            QString documentation = qax_docuFromName(currentTypeInfo, QString::fromLatin1(propertyName.constData()));
            if (!documentation.isEmpty()) {
                out << Qt::endl;
                out << indent << documentation << Qt::endl;
            }
            out << indent << "*/" << Qt::endl;
        }

        // Check whether the new function conflicts with any of QAxBase public virtual functions.
        // If so, prepend the function name with '<classname>_'. Since all internal metaobject magic
        // remains the same, we have to use the original name when used with QObject::connect or QMetaObject
        QByteArray propertyFunctionName(propertyName);
        if (axBase_vfuncs.contains(propertyFunctionName)) {
            propertyFunctionName = className + '_' + propertyName;
            qWarning("property conflits with QAXBase: %s changed to %s", propertyName.constData(), propertyFunctionName.constData());
        }

        QByteArray propertyType(property.typeName());

        QByteArray simplePropType = propertyType;
        simplePropType.replace('*', "");

        out << indent << "inline ";
        bool foreignNamespace = true;
        if (!propertyType.contains("::") &&
            (qax_qualified_usertypes.contains(simplePropType) || qax_qualified_usertypes.contains("enum "+ simplePropType))
           ) {
            propertyType.prepend(nameSpace + "::");
            foreignNamespace = false;
        }

        out << propertyType << ' ';

        if (category & OnlyInlines)
            out << className << "::";
        out << propertyFunctionName << "() const";

        if (!(category & NoInlines)) {
            out << Qt::endl << indent << '{' << Qt::endl;
            if (qax_qualified_usertypes.contains(simplePropType)) {
                if (foreignNamespace)
                    out << "#ifdef QAX_DUMPCPP_" << propertyType.left(propertyType.indexOf("::")).toUpper() << "_H" << Qt::endl;
                QByteArray simplePropTypeWithNamespace = propertyType;
                simplePropTypeWithNamespace.replace('*', "");
                out << indent << "    qRegisterMetaType<" << propertyType << ">(\"" << property.typeName() << "\");" << Qt::endl;
                out << indent << "    qRegisterMetaType<" << simplePropTypeWithNamespace << ">(\"" << simplePropType << "\");" << Qt::endl;
            }
            out << indent << "    QVariant qax_result = property(\"" << propertyName << "\");" << Qt::endl;
            if (propertyType.length() && propertyType.at(propertyType.length()-1) == '*')
                out << indent << "    if (qax_result.constData() == nullptr)\n"
                    << indent << "        return nullptr;\n"
                    << indent << "    Q_ASSERT(qax_result.isValid());" << Qt::endl;
            if (qax_qualified_usertypes.contains(simplePropType)) {
                simplePropType = propertyType;
                simplePropType.replace('*', "");
                out << indent << "    return *reinterpret_cast<" << propertyType << "*>(qax_result.data());\n";
                if (foreignNamespace) {
                    out << "#else" << Qt::endl;
                    out << indent << "    return nullptr; // foreign namespace not included" << Qt::endl;
                    out << "#endif" << Qt::endl;
                }

            } else {
                out << indent << "    return *reinterpret_cast<" << propertyType << "*>(qax_result.data());\n";
            }
            out << indent << '}' << Qt::endl;
        } else {
            out << "; //Returns the value of " << propertyName << Qt::endl;
        }

        functions << propertyName;

        if (property.isWritable()) {
            const QByteArray setter = setterName(propertyName);

            out << indent << "inline " << "void ";
            if (category & OnlyInlines)
                out << className << "::";
            out << setter << '(' << constRefify(propertyType) << " value)";

            if (!(category & NoInlines)) {
                if (propertyType.endsWith('*')) {
                    out << '{' << Qt::endl;
                    out << "    int typeId = qRegisterMetaType<" << propertyType << ">(\"" << propertyType << "\");" << Qt::endl;
                    out << "    setProperty(\"" << propertyName << "\", QVariant(QMetaType(typeId), &value));" << Qt::endl;
                    out << '}' << Qt::endl;
                } else {
                    out << "{ setProperty(\"" << propertyName << "\", QVariant(value)); }" << Qt::endl;
                }
            } else {
                out << "; //Sets the value of the " << propertyName << " property" << Qt::endl;
            }

            functions << setter;
        }

        out << Qt::endl;
    }

    // slots - but not property setters
    int defaultArguments = 0;
    for (int islot = mo->methodOffset(); islot < mo->methodCount(); ++islot) {
        const QMetaMethod slot(mo->method(islot));
        if (slot.methodType() != QMetaMethod::Slot)
            continue;

#if 0
        // makes not sense really to respect default arguments...
        if (slot.attributes() & Cloned) {
            ++defaultArguments;
            continue;
        }
#endif

        QByteArray slotSignature(slot.methodSignature());
        QByteArray slotName = slotSignature.left(slotSignature.indexOf('('));
        if (functions.contains(slotName))
            continue;

        if (!(category & OnlyInlines)) {
            out << indent << "/*" << Qt::endl << indent << "Method " << slotName << Qt::endl;
            QString documentation = qax_docuFromName(currentTypeInfo, QString::fromLatin1(slotName.constData()));
            if (!documentation.isEmpty()) {
                out << Qt::endl;
                out << indent << documentation << Qt::endl;
            }
            out << indent << "*/" << Qt::endl;
        }

        QByteArray slotParameters(joinParameterNames(slot.parameterNames()));
        QByteArray slotTag(slot.tag());
        QByteArray slotType(slot.typeName());

        QByteArray simpleSlotType = slotType;
        simpleSlotType.replace('*', "");
        if (!slotType.contains("::") && qax_qualified_usertypes.contains(simpleSlotType))
            slotType.prepend(nameSpace + "::");


        QByteArray slotNamedSignature;
        if (slotSignature.endsWith("()")) { // no parameters - no names
            slotNamedSignature = slotSignature;
        } else {
            slotNamedSignature = slotSignature.left(slotSignature.indexOf('(') + 1);
            QByteArray slotSignatureTruncated(slotSignature.mid(slotNamedSignature.length()));
            slotSignatureTruncated.truncate(slotSignatureTruncated.length() - 1);

            const auto signatureSplit = slotSignatureTruncated.split(',');
            QByteArrayList parameterSplit;
            if (slotParameters.isEmpty()) { // generate parameter names
                for (qsizetype i = 0; i < signatureSplit.size(); ++i)
                    parameterSplit << QByteArray("p") + QByteArray::number(i);
            } else {
                parameterSplit = slotParameters.split(',');
            }

            for (qsizetype i = 0; i < signatureSplit.count(); ++i) {
                QByteArray parameterType = signatureSplit.at(i);
                if (!parameterType.contains("::") && namespaceForType.contains(parameterType))
                    parameterType.prepend(namespaceForType.value(parameterType) + "::");

                QByteArray arraySpec; // transform array method signature "foo(int[4])" ->"foo(int p[4])"
                const qsizetype arrayPos = parameterType.lastIndexOf('[');
                if (arrayPos != -1) {
                    arraySpec = parameterType.right(parameterType.size() - arrayPos);
                    parameterType.truncate(arrayPos);
                }
                slotNamedSignature += constRefify(parameterType);
                slotNamedSignature += ' ';
                slotNamedSignature += parameterSplit.at(i);
                slotNamedSignature += arraySpec;
                if (defaultArguments >= signatureSplit.size() - i) {
                    slotNamedSignature += " = ";
                    slotNamedSignature += parameterType + "()";
                }
                if (i + 1 < signatureSplit.size())
                    slotNamedSignature += ", ";
            }
            slotNamedSignature += ')';
        }

        out << indent << "inline ";

        if (!slotTag.isEmpty())
            out << slotTag << ' ';
        else
            out << slotType << ' ';
        if (category & OnlyInlines)
            out << className << "::";

        // Update function name in case of conflicts with QAxBase public virtual functions.
        int parnIdx = slotNamedSignature.indexOf('(');
        QByteArray slotOriginalName =  slotNamedSignature.left(parnIdx);
        if (axBase_vfuncs.contains(slotOriginalName)) {
            QByteArray newSignature = className + '_' + slotOriginalName;
            newSignature += slotNamedSignature.mid(parnIdx);
            qWarning("function name conflits with QAXBase %s changed to %s", slotNamedSignature.constData(), newSignature.constData());
            slotNamedSignature = newSignature;
        }

        out << slotNamedSignature;

        if (category & NoInlines) {
            out << ';' << Qt::endl;
        } else {
            out << Qt::endl;
            out << indent << '{' << Qt::endl;

            if (slotType != QByteArrayLiteral("void")) {
                out << indent << "    " << slotType << " qax_result";
                if (slotType.endsWith('*'))
                    out << " = 0";
                out << ';' << Qt::endl;
                if (qax_qualified_usertypes.contains(simpleSlotType)) {
                    bool foreignNamespace = simpleSlotType.contains("::");
                    if (foreignNamespace)
                        out << "#ifdef QAX_DUMPCPP_" << simpleSlotType.left(simpleSlotType.indexOf(':')).toUpper() << "_H" << Qt::endl;
                    QByteArray simpleSlotTypeWithNamespace = slotType;
                    simpleSlotTypeWithNamespace.replace('*', "");
                    out << indent << "    qRegisterMetaType<" << simpleSlotTypeWithNamespace << "*>(\"" << simpleSlotType << "*\");" << Qt::endl;
                    if (!vTableOnlyStubs.contains(simpleSlotTypeWithNamespace))
                        out << indent << "    qRegisterMetaType<" << simpleSlotTypeWithNamespace << ">(\"" << simpleSlotType << "\");" << Qt::endl;
                    if (foreignNamespace)
                        out << "#endif" << Qt::endl;
                }
            }
            out << indent << "    void *_a[] = {";
            if (slotType != QByteArrayLiteral("void"))
                out << "(void*)&qax_result";
            else
                out << '0';
            if (!slotParameters.isEmpty()) {
                out << ", (void*)&";
                out << slotParameters.replace(",", ", (void*)&");
            }
            out << "};" << Qt::endl;

            out << indent << "    qt_metacall(QMetaObject::InvokeMetaMethod, " << islot << ", _a);" << Qt::endl;
            if (slotType != QByteArrayLiteral("void"))
                out << indent << "    return qax_result;" << Qt::endl;
            out << indent << '}' << Qt::endl;
        }

        out << Qt::endl;
        defaultArguments = 0;
    }

    if (!(category & OnlyInlines)) {
        if (!(category & NoMetaObject)) {
            out << "// meta object functions" << Qt::endl;
            out << "    Q_OBJECT_FAKE" << Qt::endl;
        }

        out << "};" << Qt::endl;
    }
}

bool generateClassImpl(QTextStream &out, const QMetaObject *mo, const QByteArray &className,
                       const QString &controlID,
                       const QByteArray &nameSpace, ObjectCategories category,
                       bool useControlName,
                       QString *errorString)
{
    Q_STATIC_ASSERT_X(QMetaObjectPrivate::OutputRevision == 13, "dumpcpp should generate the same version as moc");

    QByteArray qualifiedClassName;
    if (!nameSpace.isEmpty())
        qualifiedClassName = nameSpace + "::";
    qualifiedClassName += className;
    const QByteArray nestedQualifier = className + "::";

    QString moCode = mocCode(mo, QLatin1String(qualifiedClassName), errorString);
    if (moCode.isEmpty()) {
        out << "#error moc error\n";
        return false;
    }
    out << moCode << "\n\n";

    formatConstructorBody(out, nameSpace, className, controlID, category, useControlName);
    formatMetaFunctions(out, nameSpace, className, category);

    return true;
}

static void formatCommentBlockFooter(const QString &typeLibFile, QTextStream &str)
{
    str << " generated by dumpcpp v" << QT_VERSION_STR << " using\n**";
    const QStringList arguments = QCoreApplication::arguments();
    for (const QString &arg : arguments)
        str << ' ' << arg;
    str << "\n** from the type library " << typeLibFile << "\n**\n"
        << "****************************************************************************/\n\n";
}

static QByteArray classNameFromTypeInfo(ITypeInfo *typeinfo)
{
    BSTR bstr;
    QByteArray result;
    if (SUCCEEDED(typeinfo->GetDocumentation(-1, &bstr, nullptr, nullptr, nullptr))) {
        result = QString::fromWCharArray(bstr).toLatin1();
        SysFreeString(bstr);
    }
    return result;
}

// Extract a list of VTable only stubs from a ITypeLib.
static QByteArrayList vTableOnlyStubsFromTypeLib(ITypeLib *typelib, const QString &nameSpace)
{
    QByteArrayList result;
    const QByteArray nameSpacePrefix = nameSpace.toLatin1() + "::";
    for (UINT i = 0, typeCount = typelib->GetTypeInfoCount(); i < typeCount; ++i) {
        TYPEKIND typekind;
        if (SUCCEEDED(typelib->GetTypeInfoType(i, &typekind)) && typekind == TKIND_INTERFACE) {
            ITypeInfo *typeinfo = nullptr;
            if (SUCCEEDED(typelib->GetTypeInfo(i, &typeinfo) && typeinfo)) {
                result.append(nameSpacePrefix + classNameFromTypeInfo(typeinfo));
                typeinfo->Release();
            }
        }
    }
    return result;
}

static const QMetaObject *baseMetaObject(ObjectCategories c)
{
    return c.testFlag(ActiveX)
        ? &QAxBaseWidget::staticMetaObject
        : &QAxBaseObject::staticMetaObject;
}

bool generateTypeLibrary(QString typeLibFile, QString outname,
                         const QString &nameSpace, ObjectCategories category, bool useControlName)
{
    typeLibFile.replace(QLatin1Char('/'), QLatin1Char('\\'));

    ITypeLib *typelib;
    LoadTypeLibEx(reinterpret_cast<const wchar_t *>(typeLibFile.utf16()), REGKIND_NONE, &typelib);
    if (!typelib) {
        qWarning("dumpcpp: loading '%s' as a type library failed", qPrintable(typeLibFile));
        return false;
    }

    QString libName = nameSpace;
    if (libName.isEmpty()) {
        BSTR nameString = nullptr;
        if (SUCCEEDED(typelib->GetDocumentation(-1, &nameString, nullptr, nullptr, nullptr))) {
            libName = QString::fromWCharArray(nameString);
            SysFreeString(nameString);
        }
    }
    const QByteArray libNameBa = libName.toLatin1();
    vTableOnlyStubs = vTableOnlyStubsFromTypeLib(typelib, libName);

    QString libVersion(QLatin1String("1.0"));

    TLIBATTR *tlibattr = nullptr;
    typelib->GetLibAttr(&tlibattr);
    if (tlibattr) {
        libVersion = QString::fromLatin1("%1.%2").arg(tlibattr->wMajorVerNum).arg(tlibattr->wMinorVerNum);
        typelib->ReleaseTLibAttr(tlibattr);
    }

    if (outname.isEmpty())
        outname = libName.toLower();

    if (outname.isEmpty()) {
        qWarning("dumpcpp: no output filename provided, and cannot deduce output filename");
        return false;
    }

    QMetaObject *namespaceObject = qax_readEnumInfo(typelib, nullptr);

    QString classImpl;
    QTextStream classImplOut(&classImpl);
    QFile implFile(outname + QLatin1String(".cpp"));
    QTextStream implOut(&implFile);
    if (!(category & (NoMetaObject|NoImplementation))) {
        if (!implFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            qWarning("dumpcpp: Could not open output file '%s'", qPrintable(implFile.fileName()));
            return false;
        }

        implOut << "/****************************************************************************\n"
                   "**\n** Metadata for " << libName;
        formatCommentBlockFooter(typeLibFile, implOut);

        implOut << "#define QAX_DUMPCPP_" << libName.toUpper() << "_NOINLINES" << Qt::endl;

        implOut << "#include \"" << outname << ".h\"" << Qt::endl;
        implOut << "#include <OAIdl.h>" << Qt::endl; // For IDispatch
        implOut << Qt::endl;
    }

    QFile declFile(outname + QLatin1String(".h"));
    QTextStream declOut(&declFile);
    QByteArray classes;
    QTextStream classesOut(&classes, QIODevice::WriteOnly);
    QByteArray inlines;
    QTextStream inlinesOut(&inlines, QIODevice::WriteOnly);

    QMap<QByteArray, QByteArrayList> namespaces;

    if(!(category & NoDeclaration)) {
        if (!declFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            qWarning("dumpcpp: Could not open output file '%s'", qPrintable(declFile.fileName()));
            return false;
        }

        declOut << "/****************************************************************************\n"
                   "**\n** Namespace " << libName;
        formatCommentBlockFooter(typeLibFile, declOut);

        QFileInfo cppFileInfo(outname);
        writeHeader(declOut, libName, cppFileInfo.fileName());

        UINT typeCount = typelib->GetTypeInfoCount();
        if (declFile.isOpen()) {
            QByteArrayList opaquePointerTypes;
            declOut << Qt::endl;
            declOut << "// Referenced namespace" << Qt::endl;
            for (UINT index = 0; index < typeCount; ++index) {
                ITypeInfo *typeinfo = nullptr;
                typelib->GetTypeInfo(index, &typeinfo);
                if (!typeinfo)
                    continue;

                TYPEATTR *typeattr;
                typeinfo->GetTypeAttr(&typeattr);
                if (!typeattr) {
                    typeinfo->Release();
                    continue;
                }

                TYPEKIND typekind;
                typelib->GetTypeInfoType(index, &typekind);

                QMetaObject *metaObject = nullptr;

                // trigger meta object to collect references to other type libraries
                switch (typekind) {
                case TKIND_COCLASS:
                    metaObject = qax_readClassInfo(typelib, typeinfo, baseMetaObject(category));
                    break;
                case TKIND_DISPATCH:
                    metaObject = qax_readInterfaceInfo(typelib, typeinfo, baseMetaObject(category));
                    break;
                case TKIND_RECORD:
                case TKIND_ENUM:
                case TKIND_INTERFACE: // only for forward declarations
                    {
                        QByteArray className = classNameFromTypeInfo(typeinfo);
                        switch (typekind) {
                        case TKIND_RECORD:
                            className.prepend("struct ");
                            break;
                        case TKIND_ENUM:
                            className.prepend("enum ");
                            break;
                        default:
                            break;
                        }
                        namespaces[libNameBa].append(className);
                        if (!qax_qualified_usertypes.contains(className))
                            qax_qualified_usertypes << className;
                    }
                    break;
                default:
                    break;
                }
                qax_deleteMetaObject(metaObject);
                typeinfo->ReleaseTypeAttr(typeattr);
                typeinfo->Release();
            }

            for (qsizetype i = 0; i < qax_qualified_usertypes.size(); ++i) {
                QByteArray refType = qax_qualified_usertypes.at(i);
                QByteArray refTypeLib;
                if (refType.contains("::")) {
                    refTypeLib = refType;
                    refType.remove(0, refType.lastIndexOf("::") + 2);
                    if (refTypeLib.contains(' ')) {
                        refType = refTypeLib.left(refTypeLib.indexOf(' ')) + ' ' + refType;
                    }
                    refTypeLib.truncate(refTypeLib.indexOf("::"));
                    refTypeLib.remove(0, refTypeLib.lastIndexOf(' ') + 1);
                    namespaces[refTypeLib].append(refType);
                } else {
                    namespaces[libNameBa].append(refType);
                }
            }

            for (auto it = namespaces.cbegin(), end  = namespaces.cend(); it != end; ++it) {
                const QByteArray &nspace = it.key();
                if (libName != QLatin1String(nspace)) {
                    declOut << "namespace " << nspace << " {" << Qt::endl;
                    for (const auto &className : it.value()) {
                        const auto spacePos = className.indexOf(' ');
                        if (spacePos != -1) {
                            const QByteArray name = className.mid(spacePos + 1);
                            if (className.startsWith("enum ")) {
                                declOut << "    " << className << " {\n"
                                    << qax_enum_values.value(nspace + "::" + name) << "    };\n";
                            } else {
                                declOut << "    " << className << ";\n";
                            }
                            namespaceForType.insert(name, nspace);
                        } else {
                            declOut << "    class " << className << ';' << Qt::endl;
                            opaquePointerTypes.append(nspace + "::" + className);
                            namespaceForType.insert(className, nspace);
                            namespaceForType.insert(className + '*', nspace);
                            namespaceForType.insert(className + "**", nspace);
                        }
                    }
                    declOut << '}' << Qt::endl << Qt::endl;
                }
            }
            for (const QByteArray &opaquePointerType : std::as_const(opaquePointerTypes))
                declOut << "Q_DECLARE_OPAQUE_POINTER(" << opaquePointerType << "*)" << Qt::endl;
            declOut << Qt::endl;
        }
        generateNameSpace(declOut, namespaceObject, libNameBa);

        auto nspIt = namespaces.constFind(libNameBa);
        if (nspIt != namespaces.constEnd() && !nspIt.value().isEmpty()) {
            declOut << "// forward declarations" << Qt::endl;
            for (const auto &className : nspIt.value()) {
                if (className.contains(' ')) {
                    declOut << "    " << className << ';' << Qt::endl;
                    namespaceForType.insert(className.mid(className.indexOf(' ') + 1), libNameBa);
                } else {
                    declOut << "    class " << className << ';' << Qt::endl;
                    namespaceForType.insert(className, libNameBa);
                    namespaceForType.insert(className + '*', libNameBa);
                    namespaceForType.insert(className + "**", libNameBa);
                }
            }
        }

        declOut << Qt::endl;
    }

    QByteArrayList subtypes;

    UINT typeCount = typelib->GetTypeInfoCount();
    for (UINT index = 0; index < typeCount; ++index) {
        ITypeInfo *typeinfo = nullptr;
        typelib->GetTypeInfo(index, &typeinfo);
        if (!typeinfo)
            continue;

        TYPEATTR *typeattr;
        typeinfo->GetTypeAttr(&typeattr);
        if (!typeattr) {
            typeinfo->Release();
            continue;
        }

        TYPEKIND typekind;
        typelib->GetTypeInfoType(index, &typekind);

        ObjectCategories object_category = category;
        if (!(typeattr->wTypeFlags & TYPEFLAG_FCANCREATE))
            object_category |= SubObject;
        else if (typeattr->wTypeFlags & TYPEFLAG_FCONTROL)
            object_category |= ActiveX;

        QMetaObject *metaObject = nullptr;
        QUuid guid(typeattr->guid);

        if (!(object_category & ActiveX)) {
            QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID\\") + guid.toString(), QSettings::NativeFormat);
            if (settings.childGroups().contains(QLatin1String("Control"))) {
                object_category |= ActiveX;
                object_category &= ~SubObject;
            }
        }

        switch (typekind) {
        case TKIND_COCLASS:
            metaObject = qax_readClassInfo(typelib, typeinfo, baseMetaObject(object_category));
            break;
        case TKIND_DISPATCH:
            metaObject = qax_readInterfaceInfo(typelib, typeinfo, baseMetaObject(object_category));
            break;
        case TKIND_INTERFACE: { // only stub: QTBUG-27792, explicitly disable copy in inherited
                                // class to make related error messages clearer
                const QByteArray className = classNameFromTypeInfo(typeinfo);
                declOut << "// stub for vtable-only interface\n"
                    << "class " << className << " : public QAxObject { Q_DISABLE_COPY_MOVE("
                    << className << ") };\n\n";
            }
            break;
        default:
            break;
        }

        if (metaObject) {
            currentTypeInfo = typeinfo;
            QByteArray className(metaObject->className());
            if (!(typeattr->wTypeFlags & TYPEFLAG_FDUAL)
                && (metaObject->propertyCount() - metaObject->propertyOffset()) == 1
                && className.contains("Events")) {
                declOut << "// skipping event interface " << className << Qt::endl << Qt::endl;
            } else {
                if (declFile.isOpen()) {
                    if (typeattr->wTypeFlags & TYPEFLAG_FLICENSED)
                        object_category |= Licensed;
                    if (typekind == TKIND_COCLASS) { // write those later...
                        generateClassDecl(classesOut, metaObject, className, libNameBa,
                                          object_category | NoInlines);
                        classesOut << Qt::endl;
                    } else {
                        generateClassDecl(declOut, metaObject, className, libNameBa,
                                          object_category | NoInlines);
                        declOut << Qt::endl;
                    }
                    subtypes << className;
                    generateClassDecl(inlinesOut, metaObject, className, libNameBa,
                                      object_category | OnlyInlines);
                    inlinesOut << Qt::endl;
                }
                if (implFile.isOpen()) {
                    QString errorString;
                    if (!generateClassImpl(classImplOut, metaObject, className, guid.toString(), libNameBa,
                                           object_category, useControlName, &errorString)) {
                        qWarning("%s", qPrintable(errorString));
                        return false;
                    }
                }
            }
            currentTypeInfo = nullptr;
        }

        qax_deleteMetaObject(metaObject);

        typeinfo->ReleaseTypeAttr(typeattr);
        typeinfo->Release();
    }

    // String table generation logic was ported from moc generator, with some modifications
    // required to split large stringdata arrays.
    if (implFile.isOpen()) {
        classImplOut.flush();
        implOut << classImpl <<  Qt::endl;
    }

    qax_deleteMetaObject(namespaceObject);

    classesOut.flush();
    inlinesOut.flush();

    if (declFile.isOpen()) {
        if (classes.size()) {
            declOut << "// Actual coclasses" << Qt::endl;
            declOut << classes;
        }
        if (inlines.size()) {
            declOut << "// member function implementation" << Qt::endl;
            declOut << "#ifndef QAX_DUMPCPP_" << libName.toUpper() << "_NOINLINES" << Qt::endl;
            declOut << inlines << Qt::endl;
            declOut << "#endif" << Qt::endl << Qt::endl;
        }
        // close namespace
        declOut << '}' << Qt::endl;
        declOut << Qt::endl;

        declOut << "#endif" << Qt::endl;
        declOut << Qt::endl;
    }

    typelib->Release();
    return true;
}

QT_END_NAMESPACE

QT_USE_NAMESPACE

struct Options
{
    Options() = default;

    ProgramMode mode = GenerateMode;
    ObjectCategories category = DefaultObject;
    bool dispatchEqualsIDispatch = false;
    bool useControlName = false;

    QString outname;
    QString typeLib;
    QString nameSpace;
};

static void parseOptions(Options *options)
{
    const char helpText[] =
        "\nGenerate a C++ namespace from a type library.\n\n"
        "Examples:\n"
        "   dumpcpp -o ieframe %WINDIR%\\system32\\ieframe.dll\n"
        "   dumpcpp -o outlook Outlook.Application\n"
        "   dumpcpp {3B756301-0075-4E40-8BE8-5A81DE2426B7}\n"
        "   dumpcpp -getfile {21D6D480-A88B-11D0-83DD-00AA003CCABD}\n";

    const char outputOptionC[] = "-o";
    const char nameSpaceOptionC[] = "-n";
    const char getfileOptionC[] = "-getfile";
    const char useControlNameOptionC[] = "-controlname";

    QStringList args = QCoreApplication::arguments();
    // Convert Windows-style '/option' into '-option'.
    for (int i = 1; i < args.size(); ) {
        QString &arg = args[i];
        if (arg.startsWith(QLatin1Char('/')))
            arg[0] = QLatin1Char('-');
        const bool takesOptionValue = arg == QLatin1String(outputOptionC)
            || arg == QLatin1String(nameSpaceOptionC)
            || arg == QLatin1String(getfileOptionC);
        i += takesOptionValue ? 2 : 1;
    }

    QCommandLineParser parser;
    QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
    parser.setApplicationDescription(QLatin1String(helpText));


    parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
    parser.addHelpOption();
    parser.addVersionOption();
    QCommandLineOption outputOption(QLatin1String(outputOptionC + 1),
                                    QStringLiteral("Write output to file."),
                                    QStringLiteral("file"));
    parser.addOption(outputOption);
    QCommandLineOption nameSpaceOption(QLatin1String(nameSpaceOptionC + 1),
                                       QStringLiteral("The name of the generated C++ namespace."),
                                       QStringLiteral("namespace"));
    parser.addOption(nameSpaceOption);
    QCommandLineOption noMetaObjectOption(QStringLiteral("nometaobject"),
                                          QStringLiteral("Don't generate meta object information (no .cpp file). The meta object is then generated in runtime."));
    parser.addOption(noMetaObjectOption);
    QCommandLineOption noDeclarationOption(QStringLiteral("impl"),
                                           QStringLiteral("Only generate the .cpp file."));
    parser.addOption(noDeclarationOption);
    QCommandLineOption noImplementationOption(QStringLiteral("decl"),
                                              QStringLiteral("Only generate the .h file."));
    parser.addOption(noImplementationOption);
    QCommandLineOption compatOption(QStringLiteral("compat"),
                                    QStringLiteral("Treat all coclass parameters as IDispatch."));
    parser.addOption(compatOption);
    QCommandLineOption getFileOption(QLatin1String(getfileOptionC + 1),
                                     QStringLiteral("Print the filename for the type library it to standard output."),
                                     QStringLiteral("id"));
    parser.addOption(getFileOption);
    parser.addPositionalArgument(QStringLiteral("input"),
                                 QStringLiteral("A type library file, type library ID, ProgID or CLSID."));
    QCommandLineOption useControlNameOption(QLatin1String(useControlNameOptionC + 1),
                                            QStringLiteral("Use the control class name instead of the UUID for setControl()."));
    parser.addOption(useControlNameOption);
    parser.process(args);

    if (parser.isSet(outputOption))
        options->outname = parser.value(outputOption);
    if (parser.isSet(nameSpaceOption))
        options->nameSpace = parser.value(nameSpaceOption);
    if (parser.isSet(noMetaObjectOption))
         options->category |= NoMetaObject;
    if (parser.isSet(noDeclarationOption))
        options->category |= NoDeclaration;
    if (parser.isSet(noImplementationOption))
        options->category |= NoImplementation;
    options->dispatchEqualsIDispatch = parser.isSet(compatOption);
    if (parser.isSet(getFileOption)) {
        options->typeLib = parser.value(getFileOption);
        options->mode = TypeLibID;
    }
    if (parser.isSet(useControlNameOption))
        options->useControlName = true;
    if (!parser.positionalArguments().isEmpty())
        options->typeLib = parser.positionalArguments().first();

    if (options->mode == GenerateMode && options->typeLib.isEmpty()) {
        qWarning("dumpcpp: No object class or type library name provided.\n");
        parser.showHelp(1);
    }
}

int main(int argc, char **argv)
{
    if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
        qErrnoWarning("CoInitializeEx() failed.");
        return -1;
    }
    QCoreApplication app(argc, argv);

    Options options;
    parseOptions(&options);
    qax_dispatchEqualsIDispatch = options.dispatchEqualsIDispatch;
    QString typeLib = options.typeLib;

    if (options.mode == TypeLibID) {
        QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\") +
                           typeLib, QSettings::NativeFormat);
        typeLib.clear();
        QStringList codes = settings.childGroups();
        for (qsizetype c = 0; c < codes.size(); ++c) {
            const QString keyPrefix = QLatin1Char('/') + codes.at(c) + QLatin1String("/0/");
            if (QT_POINTER_SIZE == 8) {
                typeLib = settings.value(keyPrefix + QLatin1String("win64/.")).toString();
                if (QFile::exists(typeLib))
                    break;
            }
            typeLib = settings.value(keyPrefix + QLatin1String("win32/.")).toString();
            if (QFile::exists(typeLib))
                break;
        }

        if (!typeLib.isEmpty())
            fprintf(stdout, "\"%s\"\n", qPrintable(typeLib));
        return 0;
    }

    if (typeLib.isEmpty()) {
        qWarning("dumpcpp: No object class or type library name provided.\n"
            "         Use -h for help.");
        return -1;
    }

    // not a file - search registry
    if (!QFile::exists(typeLib)) {
        bool isObject = false;
        QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\Software\\Classes"), QSettings::NativeFormat);

        // regular string and not a file - must be ProgID
        if (typeLib.at(0) != QLatin1Char('{')) {
            CLSID clsid;
            if (CLSIDFromProgID(reinterpret_cast<const wchar_t *>(typeLib.utf16()), &clsid) != S_OK) {
                qWarning("dumpcpp: '%s' is not a type library and not a registered ProgID",
                         qPrintable(typeLib));
                return -2;
            }
            typeLib = QUuid(clsid).toString();
            isObject = true;
        }

        // check if CLSID
        if (!isObject) {
            QVariant test = settings.value(QLatin1String("/CLSID/") + typeLib + QLatin1String("/."));
            isObject = test.isValid();
        }

        // search typelib ID for CLSID
        if (isObject)
            typeLib = settings.value(QLatin1String("/CLSID/") + typeLib
                                     + QLatin1String("/Typelib/.")).toString();

        // interpret input as type library ID
        QString key = QLatin1String("/TypeLib/") + typeLib;
        settings.beginGroup(key);
        QStringList versions = settings.childGroups();
        QStringList codes;
        if (!versions.isEmpty()) {
            settings.beginGroup(QLatin1Char('/') + versions.last());
            codes = settings.childGroups();
            key += QLatin1Char('/') + versions.last();
            settings.endGroup();
        }
        settings.endGroup();

        for (qsizetype c = 0; c < codes.size(); ++c) {
            const QString keyPrefix = key + QLatin1Char('/') + codes.at(c) + QLatin1Char('/');
            if (QT_POINTER_SIZE == 8) {
                typeLib = settings.value(keyPrefix + QLatin1String("win64/.")).toString();
                if (QFile::exists(typeLib))
                    break;
            }
            typeLib = settings.value(keyPrefix + QLatin1String("win32/.")).toString();
            if (QFile::exists(typeLib))
                break;
        }
    }

    if (!QFile::exists(typeLib)) {
        qWarning("dumpcpp: type library '%s' not found", qPrintable(typeLib));
        return -2;
    }

    if (!generateTypeLibrary(typeLib, options.outname, options.nameSpace, options.category, options.useControlName)) {
        qWarning("dumpcpp: error processing type library '%s'", qPrintable(typeLib));
        return -1;
    }

    return 0;
}
