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

#include "ctf.h"
#include "provider.h"
#include "helpers.h"
#include "panic.h"
#include "qtheaders.h"

#include <qfile.h>
#include <qfileinfo.h>
#include <qtextstream.h>
#include <qdebug.h>


static void writePrologue(QTextStream &stream, const QString &fileName, const Provider &provider)
{
    writeCommonPrologue(stream);
    const QString guard = includeGuard(fileName);

    // include prefix text or qt headers only once
    stream << "#if !defined(" << guard << ")\n";
    stream << qtHeaders();
    stream << "\n";
    if (!provider.prefixText.isEmpty())
        stream << provider.prefixText.join(u'\n') << "\n\n";
    stream << "#endif\n\n";

    /* the first guard is the usual one, the second is required
     * by LTTNG to force the re-evaluation of TRACEPOINT_* macros
     */
    stream << "#if !defined(" << guard << ") || defined(TRACEPOINT_HEADER_MULTI_READ)\n";

    stream << "#define " << guard << "\n\n"
           << "#undef TRACEPOINT_INCLUDE\n"
           << "#define TRACEPOINT_INCLUDE \"" << fileName << "\"\n\n";

    stream << "#include <private/qctf_p.h>\n\n";

    const QString namespaceGuard = guard + QStringLiteral("_USE_NAMESPACE");
    stream << "#if !defined(" << namespaceGuard << ")\n"
           << "#define " << namespaceGuard << "\n"
           << "QT_USE_NAMESPACE\n"
           << "#endif // " << namespaceGuard << "\n\n";

    stream << "TRACEPOINT_PROVIDER(" << provider.name << ");\n\n";
}

static void writeEpilogue(QTextStream &stream, const QString &fileName)
{
    stream << "\n";
    stream << "#endif // " << includeGuard(fileName) << "\n"
           << "#include <private/qtrace_p.h>\n";
}

static void writeWrapper(QTextStream &stream,
        const Tracepoint &tracepoint, const Provider &provider)
{
    const QString argList = formatFunctionSignature(tracepoint.args);
    const QString paramList = formatParameterList(provider, tracepoint.args, tracepoint.fields, CTF);
    const QString &name = tracepoint.name;
    const QString includeGuard = QStringLiteral("TP_%1_%2").arg(provider.name).arg(name).toUpper();

    /* prevents the redefinion of the inline wrapper functions
     */
    stream << "\n"
           << "#ifndef " << includeGuard << "\n"
           << "#define " << includeGuard << "\n"
           << "QT_BEGIN_NAMESPACE\n"
           << "namespace QtPrivate {\n";

    stream << "inline void trace_" << name << "(" << argList << ")\n"
           << "{\n"
           << "    tracepoint(" << provider.name << ", " << name << paramList << ");\n"
           << "}\n";

    stream << "inline void do_trace_" << name << "(" << argList << ")\n"
           << "{\n"
           << "    do_tracepoint(" << provider.name << ", " << name << paramList << ");\n"
           << "}\n";

    stream << "inline bool trace_" << name << "_enabled()\n"
           << "{\n"
           << "    return tracepoint_enabled(" << provider.name << ", " << name << ");\n"
           << "}\n";

    stream << "} // namespace QtPrivate\n"
           << "QT_END_NAMESPACE\n"
           << "#endif // " << includeGuard << "\n\n";
}


static void writeMetadataGenerators(QTextStream &stream)
{
    stream << R"CPP(
template <typename T>
inline QString integerToMetadata(const QString &name)
{
    QString ret;
    if (!std::is_signed<T>().value)
        ret += QLatin1Char('u');
    if (sizeof(T) == 8)
        ret += QStringLiteral("int64_t ");
    else if (sizeof(T) == 4)
        ret += QStringLiteral("int32_t ");
    else if (sizeof(T) == 2)
        ret += QStringLiteral("int16_t ");
    else if (sizeof(T) == 1)
        ret += QStringLiteral("int8_t ");
    ret += name + QLatin1Char(';');
    return ret;
}

template <typename T>
inline QString integerArrayToMetadata(const QString &size, const QString &name)
{
    QString ret;
    if (!std::is_signed<T>().value)
        ret += QLatin1Char('u');
    if (sizeof(T) == 8)
        ret += QStringLiteral("int64_t ");
    else if (sizeof(T) == 4)
        ret += QStringLiteral("int32_t ");
    else if (sizeof(T) == 2)
        ret += QStringLiteral("int16_t ");
    else if (sizeof(T) == 1)
        ret += QStringLiteral("int8_t ");
    ret += name + QLatin1Char('[') + size + QStringLiteral("];");
    return ret;
}

template <typename T>
inline QString floatToMetadata(const QString &name)
{
    QString ret;
    if (sizeof(T) == 8)
        ret += QStringLiteral("double ");
    else if (sizeof(T) == 4)
        ret += QStringLiteral("float ");
    ret += name + QLatin1Char(';');
    return ret;
}

template <typename T>
inline QString floatArrayToMetadata(const QString &size, const QString &name)
{
    QString ret;
    if (sizeof(T) == 8)
        ret += QStringLiteral("double ");
    else if (sizeof(T) == 4)
        ret += QStringLiteral("float ");
    ret += name + QLatin1Char('[') + size + QLatin1Char(']');
    return ret + QLatin1Char(';');
}

inline QString pointerToMetadata(const QString &name)
{
    QString ret;
    if (QT_POINTER_SIZE == 8)
        ret += QStringLiteral("intptr64_t ");
    else if (QT_POINTER_SIZE == 4)
        ret += QStringLiteral("intptr32_t ");
    ret += name + QLatin1Char(';');
    return ret;
}

)CPP";
}

static void writeTracepoint(QTextStream &stream,
                            const Tracepoint &tracepoint, const QString &providerName)
{
    stream  << "TRACEPOINT_EVENT(\n"
            << "    " << providerName << ",\n"
            << "    " << tracepoint.name << ",\n";

    const auto checkUnknownArgs = [](const Tracepoint &tracepoint) {
        for (auto &field : tracepoint.fields) {
            if (field.backendType == Tracepoint::Field::Unknown)
                return true;
        }
        return false;
    };

    const auto formatType = [](const QString &type) {
        QString ret = type;
        if (type.endsWith(QLatin1Char('*')) || type.endsWith(QLatin1Char('&')))
            ret = type.left(type.length() - 1).simplified();
        if (ret.startsWith(QStringLiteral("const")))
            ret = ret.right(ret.length() - 6).simplified();
        return typeToTypeName(ret);
    };
    QString eventSize;
    bool variableSize = false;
    const bool emptyMetadata = checkUnknownArgs(tracepoint) || tracepoint.args.size() == 0;
    if (!emptyMetadata) {
        for (int i = 0; i < tracepoint.args.size(); i++) {
            auto &arg = tracepoint.args[i];
            auto &field = tracepoint.fields[i];
            if (i > 0) {
                stream << " + QStringLiteral(\"\\n\\\n        \") + ";
                eventSize += QStringLiteral(" + ");
            }
            const bool array = field.arrayLen > 0;
            switch (field.backendType) {
            case Tracepoint::Field::Boolean: {
                stream << "QStringLiteral(\"Boolean " << arg.name << ";\")";
                eventSize += QStringLiteral("sizeof(bool)");
            } break;
            case Tracepoint::Field::Integer: {
                if (array) {
                    stream << "integerArrayToMetadata<" << formatType(arg.type)
                           << ">(QStringLiteral(\"" << field.arrayLen << "\"), QStringLiteral(\""
                           << arg.name << "\"))";
                } else {
                    stream << "integerToMetadata<" << formatType(arg.type) << ">(QStringLiteral(\""
                           << arg.name << "\"))";
                }
                eventSize += QStringLiteral("sizeof(") + formatType(arg.type) + QStringLiteral(")");
                if (array)
                    eventSize += QStringLiteral(" * ") + QString::number(field.arrayLen);
            } break;
            case Tracepoint::Field::Pointer:
            case Tracepoint::Field::IntegerHex: {
                stream << "pointerToMetadata(QStringLiteral(\"" << formatType(arg.type) << "_"
                       << arg.name << "\"))";
                eventSize += QStringLiteral("QT_POINTER_SIZE");
            } break;
            case Tracepoint::Field::Float: {
                if (array) {
                    stream << "floatArrayToMetadata<" << formatType(arg.type)
                           << ">(QStringLiteral(\"" << field.arrayLen << "\"), QStringLiteral(\""
                           << arg.name << "\"))";
                } else {
                    stream << "floatToMetadata<" << formatType(arg.type) << ">(QStringLiteral(\""
                           << arg.name << "\"))";
                }
                eventSize += QStringLiteral("sizeof(") + formatType(arg.type) + QStringLiteral(")");
                if (array)
                    eventSize += QStringLiteral(" * ") + QString::number(field.arrayLen);
            } break;
            case Tracepoint::Field::QtUrl:
            case Tracepoint::Field::QtString:
            case Tracepoint::Field::String: {
                stream << "QStringLiteral(\"string " << arg.name << ";\")";
                eventSize += QStringLiteral("1");
                variableSize = true;
            } break;
            case Tracepoint::Field::QtRect: {
                stream << "QStringLiteral(\"int32_t QRect_" << arg.name << "_x;\\n\\\n        \")";
                stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_y;\\n\\\n        \")";
                stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_width;\\n\\\n        \")";
                stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_height;\\n\\\n        \")";
                eventSize += QStringLiteral("16");
            } break;
            case Tracepoint::Field::QtSize: {
                stream << "QStringLiteral(\"int32_t QSize_" << arg.name << "_width;\\n\\\n        \")";
                stream << " + QStringLiteral(\"int32_t QSize_" << arg.name << "_height;\\n\\\n        \")";
                eventSize += QStringLiteral("8");
            } break;
            case Tracepoint::Field::QtRectF: {
                stream << "QStringLiteral(\"float QRectF_" << arg.name << "_x;\\n\\\n        \")";
                stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_y;\\n\\\n        \")";
                stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_width;\\n\\\n        \")";
                stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_height;\\n\\\n        \")";
                eventSize += QStringLiteral("16");
            } break;
            case Tracepoint::Field::QtSizeF: {
                stream << "QStringLiteral(\"float QSizeF_" << arg.name << "_width;\\n\\\n        \")";
                stream << " + QStringLiteral(\"float QSizeF_" << arg.name << "_height;\\n\\\n        \")";
                eventSize += QStringLiteral("8");
            } break;
            case Tracepoint::Field::Unknown:
                break;
            case Tracepoint::Field::EnumeratedType: {
                stream << "QStringLiteral(\"" << typeToTypeName(arg.type) << " " << arg.name << ";\")";
                eventSize += QString::number(field.enumValueSize / 8);
                variableSize = true;
            } break;
            case Tracepoint::Field::FlagType: {
                stream << "QStringLiteral(\"uint8_t " << arg.name << "_length;\\n\\\n        ";
                stream << typeToTypeName(arg.type) << " " << arg.name << "[" << arg.name << "_length];\")";
                eventSize += QStringLiteral("2");
                variableSize = true;
            } break;
            case Tracepoint::Field::QtByteArray:
            case Tracepoint::Field::Sequence:
                panic("Unhandled type '%s %s", qPrintable(arg.type), qPrintable(arg.name));
                break;
            }
        }
    }

    if (emptyMetadata)
        stream << "{},\n";
    else
        stream << ",\n";
    if (eventSize.length())
        stream << eventSize << ", \n";
    else
        stream << "0, \n";
    stream << (variableSize ? "true" : "false") << "\n";
    stream << ")\n\n";
}

static void writeTracepoints(QTextStream &stream, const Provider &provider)
{
    for (const Tracepoint &t : provider.tracepoints) {
        writeTracepoint(stream, t, provider.name);
        writeWrapper(stream, t, provider);
    }
}

static void writeEnums(QTextStream &stream, const Provider &provider)
{
    for (const auto &e : provider.enumerations) {
        QString name = e.name;
        name.replace(QStringLiteral("::"), QStringLiteral("_"));
        stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n";
        stream << "QStringLiteral(\"typealias enum : integer { size = " << e.valueSize << "; } {\\n\\\n";

        const auto values = e.values;
        QList<int> handledValues;

        for (const auto &v : values) {
            if (handledValues.contains(v.value))
                continue;
            if (v.range) {
                stream << v.name << " = " << v.value << " ... "  << v.range << ", \\n\\\n";
            } else {
                const QString names = aggregateListValues(v.value, values);
                stream << names << " = " << v.value << ", \\n\\\n";
                handledValues.append(v.value);
            }
        }
        stream << "} := " << name << ";\\n\\n\"));\n\n";
    }
    stream << "\n";
}

static void writeFlags(QTextStream &stream, const Provider &provider)
{
    for (const auto &e : provider.flags) {
        QString name = e.name;
        name.replace(QStringLiteral("::"), QStringLiteral("_"));
        stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n";
        stream << "QStringLiteral(\"typealias enum : integer { size = 8; } {\\n\\\n";

        const auto values = e.values;
        QList<int> handledValues;

        for (const auto &v : values) {
            if (handledValues.contains(v.value))
                continue;
            const QString names = aggregateListValues(v.value, values);
            stream << names << " = " << v.value << ", \\n\\\n";
            handledValues.append(v.value);
        }
        stream << "} := " << name << ";\\n\\n\"));\n\n";
    }
    stream << "\n";
}

void writeCtf(QFile &file, const Provider &provider)
{
    QTextStream stream(&file);

    const QString fileName = QFileInfo(file.fileName()).fileName();

    writePrologue(stream, fileName, provider);
    writeMetadataGenerators(stream);
    writeEnums(stream, provider);
    writeFlags(stream, provider);
    writeTracepoints(stream, provider);
    writeEpilogue(stream, fileName);
}
