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

#include "clangtoolsplugin.h"

#include "clangtool.h"
#include "clangtoolsconstants.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolsprojectsettingswidget.h"
#include "documentclangtoolrunner.h"
#include "documentquickfixfactory.h"
#include "settingswidget.h"

#ifdef WITH_TESTS
#include "readexporteddiagnosticstest.h"
#include "clangtoolspreconfiguredsessiontests.h"
#include "clangtoolsunittests.h"
#endif

#include <utils/mimeutils.h>
#include <utils/qtcassert.h>

#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>

#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppmodelmanager.h>

#include <texteditor/texteditor.h>

#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>

#include <QAction>
#include <QDebug>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
#include <QToolBar>

using namespace Core;
using namespace ProjectExplorer;

namespace ClangTools {
namespace Internal {

static ProjectPanelFactory *m_projectPanelFactoryInstance = nullptr;

ProjectPanelFactory *projectPanelFactory()
{
    return m_projectPanelFactoryInstance;
}

class ClangToolsPluginPrivate
{
public:
    ClangToolsPluginPrivate()
        : quickFixFactory(
            [this](const Utils::FilePath &filePath) { return runnerForFilePath(filePath); })
    {}

    DocumentClangToolRunner *runnerForFilePath(const Utils::FilePath &filePath)
    {
        for (DocumentClangToolRunner *runner : std::as_const(documentRunners)) {
            if (runner->filePath() == filePath)
                return runner;
        }
        return nullptr;
    }

    ClangTool clangTool;
    ClangToolsOptionsPage optionsPage;
    QMap<Core::IDocument *, DocumentClangToolRunner *> documentRunners;
    DocumentQuickFixFactory quickFixFactory;
};

ClangToolsPlugin::~ClangToolsPlugin()
{
    delete d;
}

bool ClangToolsPlugin::initialize(const QStringList &arguments, QString *errorString)
{
    Q_UNUSED(arguments)
    Q_UNUSED(errorString)

    TaskHub::addCategory(taskCategory(), tr("Clang Tools"));

    // Import tidy/clazy diagnostic configs from CppEditor now
    // instead of at opening time of the settings page
    ClangToolsSettings::instance();

    d = new ClangToolsPluginPrivate;

    registerAnalyzeActions();

    auto panelFactory = m_projectPanelFactoryInstance = new ProjectPanelFactory;
    panelFactory->setPriority(100);
    panelFactory->setId(Constants::PROJECT_PANEL_ID);
    panelFactory->setDisplayName(tr("Clang Tools"));
    panelFactory->setCreateWidgetFunction(
        [](Project *project) { return new ClangToolsProjectSettingsWidget(project); });
    ProjectPanelFactory::registerFactory(panelFactory);

    connect(Core::EditorManager::instance(),
            &Core::EditorManager::currentEditorChanged,
            this,
            &ClangToolsPlugin::onCurrentEditorChanged);

    return true;
}

void ClangToolsPlugin::onCurrentEditorChanged()
{
    for (Core::IEditor *editor : Core::EditorManager::visibleEditors()) {
        IDocument *document = editor->document();
        if (d->documentRunners.contains(document))
            continue;
        auto runner = new DocumentClangToolRunner(document);
        connect(runner, &DocumentClangToolRunner::destroyed, this, [this, document]() {
            d->documentRunners.remove(document);
        });
        d->documentRunners[document] = runner;
    }
}

void ClangToolsPlugin::registerAnalyzeActions()
{
    ActionManager::registerAction(d->clangTool.startAction(), Constants::RUN_ON_PROJECT);
    Command *cmd = ActionManager::registerAction(d->clangTool.startOnCurrentFileAction(),
                                                 Constants::RUN_ON_CURRENT_FILE);
    ActionContainer *mtoolscpp = ActionManager::actionContainer(CppEditor::Constants::M_TOOLS_CPP);
    if (mtoolscpp)
        mtoolscpp->addAction(cmd);

    Core::ActionContainer *mcontext = Core::ActionManager::actionContainer(
        CppEditor::Constants::M_CONTEXT);
    if (mcontext)
        mcontext->addAction(cmd, CppEditor::Constants::G_CONTEXT_FIRST);

    // add button to tool bar of C++ source files
    connect(EditorManager::instance(), &EditorManager::editorOpened, this, [this, cmd](IEditor *editor) {
        if (editor->document()->filePath().isEmpty()
            || !Utils::mimeTypeForName(editor->document()->mimeType()).inherits("text/x-c++src"))
            return;
        auto *textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor);
        if (!textEditor)
            return;
        TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
        if (!widget)
            return;
        const QIcon icon = Utils::Icon({{":/debugger/images/debugger_singleinstructionmode.png",
                                         Utils::Theme::IconsBaseColor}})
                               .icon();
        QAction *action = widget->toolBar()->addAction(icon, tr("Analyze File"), [this, editor]() {
            d->clangTool.startTool(editor->document()->filePath());
        });
        cmd->augmentActionWithShortcutToolTip(action);
    });
}

QVector<QObject *> ClangToolsPlugin::createTestObjects() const
{
    QVector<QObject *> tests;
#ifdef WITH_TESTS
    tests << new PreconfiguredSessionTests;
    tests << new ClangToolsUnitTests;
    tests << new ReadExportedDiagnosticsTest;
#endif
    return tests;
}

} // namespace Internal
} // namespace ClangTools
