#include "compiledbreader.h"

#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QProcess>

#include <KTextEditor/MainWindow>

#include "gitprocess.h"

#include <optional>

QString CompileDBReader::locateCompileCommands(KTextEditor::MainWindow *mw, const QString &openedFile)
{
    Q_ASSERT(mw);

    // Check using project
    QObject *project = mw->pluginView(QStringLiteral("kateprojectplugin"));
    if (project) {
        QString baseDir = project->property("projectBaseDir").toString();
        if (baseDir.endsWith(u'/')) {
            baseDir.chop(1);
        }

        if (QFile::exists(baseDir + QStringLiteral("/compile_commands.json"))) {
            return baseDir + QStringLiteral("/compile_commands.json");
        }
    }

    // Check using git
    // For now it only checks for compile_commands in the .git/../ directory
    QFileInfo fi(openedFile);
    if (fi.exists()) {
        auto basePathOptional = getRepoBasePath(fi.absolutePath());
        if (basePathOptional.has_value()) {
            auto basePath = basePathOptional.value();
            if (basePath.endsWith(u'/')) {
                basePath.chop(1);
            }
            if (QFile::exists(basePath + QStringLiteral("/compile_commands.json"))) {
                return basePath + QStringLiteral("/compile_commands.json");
            }
        }
    }

    qWarning("Compile DB not found for file: %ls", qUtf16Printable(openedFile));

    return {};
}

QString CompileDBReader::argsForFile(const QString &compile_commandsPath, const QString &file)
{
    QFile f(compile_commandsPath);
    if (!f.open(QFile::ReadOnly)) {
        // TODO: Use Output view to report error
        qWarning("Failed to load compile_commands: %ls", qUtf16Printable(f.errorString()));
        return {};
    }

    QJsonParseError error;
    QJsonDocument cmdCmds = QJsonDocument::fromJson(f.readAll(), &error);
    if (error.error != QJsonParseError::NoError) {
        qWarning("Failed to read compile_commands: %ls", qUtf16Printable(error.errorString()));
        return {};
    }

    if (!cmdCmds.isArray()) {
        qWarning("Invalid compile_commands, root element is not an array");
        return {};
    }

    const QJsonArray commandsArray = cmdCmds.array();

    for (const auto &cmdJV : commandsArray) {
        auto compileCommand = cmdJV.toObject();
        auto cmpCmdFile = compileCommand.value(QStringLiteral("file")).toString();

        QFileInfo fi(cmpCmdFile);
        if (fi.isRelative()) {
            // QString dir = QDir::cleanPath(compileCommand.value(QStringLiteral("directory")).toString());
            //             QString file = QDir::cleanPath(dir + QStringLiteral("/") + cmpCmdFile);
        } else {
            if (fi.canonicalFilePath() == file) {
                return compileCommand.value(QStringLiteral("command")).toString();
            }
        }
    }

    qWarning("compile_command for %ls not found", qUtf16Printable(file));
    return {};
}

static void addCurrentFilePathToCompileCommands(const QString &currentCompiler, QStringList &commands, const QString &fileBasePath)
{
    // For these compilers we include the current file path to
    // the compiler commands
    QStringList compilers = {
        QStringLiteral("c++"),
        QStringLiteral("g++"),
        QStringLiteral("gcc"),
        QStringLiteral("clang"),
        QStringLiteral("clang++"),
    };

    for (const auto &c : compilers) {
        if (currentCompiler.contains(c)) {
            commands << QStringLiteral("-I") + fileBasePath;
        }
    }
}

/*
 * Remove args like "-include xyz.h"
 * CompilerExplorer doesn't like them
 */
static void removeIncludeArgument(QStringList &commands)
{
    QStringList toRemove;
    for (int i = 0; i < commands.size(); ++i) {
        if (commands.at(i) == QLatin1String("-include")) {
            if (i + 1 < commands.size()) {
                toRemove << commands.at(i);
                toRemove << commands.at(i + 1);
                ++i;
            }
        }
    }

    for (const auto &rem : std::as_const(toRemove)) {
        commands.removeAll(rem);
    }
}

QString CompileDBReader::filteredArgsForFile(const QString &compile_commandsPath, const QString &file)
{
    QString args = argsForFile(compile_commandsPath, file);

    QFileInfo fi(file);
    QString fileBasePath = fi.canonicalPath();

    QStringList argsList = args.split(u' ');
    QString currentCompiler = argsList.takeFirst(); // First is the compiler, drop it
    QStringList finalArgs;
    finalArgs.reserve(argsList.size() - 2);

    for (auto &&arg : argsList) {
        if (arg == QLatin1String("-o")) {
            continue;
        }
        if (arg.endsWith(QLatin1String(".o"))) {
            continue;
        }
        if (arg == QLatin1String("-c")) {
            continue;
        }
        if (file == arg || file.contains(arg)) {
            continue;
        }

        finalArgs << arg;
    }

    removeIncludeArgument(finalArgs);

    addCurrentFilePathToCompileCommands(currentCompiler, finalArgs, fileBasePath);

    return finalArgs.join(u' ');
}
