/*
 * Copyright (c) 2020-2026 Valve Corporation
 * Copyright (c) 2020-2026 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authors:
 * - Christophe Riccio <christophe@lunarg.com>
 */

#include "tab_diagnostics.h"
#include "mainwindow.h"

#include "../vkconfig_core/configurator.h"

#include <QFileDialog>
#include <QMessageBox>
#include <QMenu>
#include <QDesktopServices>
#include <QShortcut>
#include <QScrollBar>

TabDiagnostics::TabDiagnostics(MainWindow &window, std::shared_ptr<Ui::MainWindow> ui)
    : Tab(TAB_DIAGNOSTIC, window, ui), timer_search(new QTimer(this)) {
    this->connect(this->ui->diagnostic_mode, SIGNAL(currentIndexChanged(int)), this, SLOT(on_mode_changed(int)));
    this->connect(this->ui->diagnostic_mode_options, SIGNAL(currentIndexChanged(int)), this, SLOT(on_mode_options_changed(int)));
    this->connect(this->ui->diagnostic_export_folder, SIGNAL(clicked()), this, SLOT(on_export_folder()));
    this->connect(this->ui->diagnostic_export_file, SIGNAL(clicked()), this, SLOT(on_export_file()));
    this->connect(this->ui->diagnostic_refresh, SIGNAL(clicked()), this, SLOT(on_refresh_log()));
    this->connect(this->ui->diagnostic_search_edit, SIGNAL(textEdited(QString)), this, SLOT(on_search_textEdited(QString)));
    this->connect(this->ui->diagnostic_search_edit, SIGNAL(returnPressed()), this, SLOT(on_search_next_pressed()));
    this->connect(this->ui->diagnostic_search_clear, SIGNAL(clicked()), this, SLOT(on_search_clear_pressed()));
    this->connect(this->ui->diagnostic_search_next, SIGNAL(clicked()), this, SLOT(on_search_next_pressed()));
    this->connect(this->ui->diagnostic_search_prev, SIGNAL(clicked()), this, SLOT(on_search_prev_pressed()));
    this->connect(this->ui->diagnostic_status_text, SIGNAL(customContextMenuRequested(QPoint)), this,
                  SLOT(on_context_menu(const QPoint &)));
    this->connect(this->ui->diagnostic_search_case, SIGNAL(toggled(bool)), this, SLOT(on_search_case_toggled(bool)));
    this->connect(this->ui->diagnostic_search_whole, SIGNAL(toggled(bool)), this, SLOT(on_search_whole_toggled(bool)));
    this->connect(this->ui->diagnostic_search_regex, SIGNAL(toggled(bool)), this, SLOT(on_search_regex_toggled(bool)));

    this->connect(this->ui->diagnostic_group_box_loader_log, SIGNAL(toggled(bool)), this,
                  SLOT(on_diagnostic_loader_messages_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_errors, SIGNAL(toggled(bool)), this, SLOT(on_diagnostic_loader_errors_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_warns, SIGNAL(toggled(bool)), this, SLOT(on_diagnostic_loader_warns_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_infos, SIGNAL(toggled(bool)), this, SLOT(on_diagnostic_loader_infos_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_debug, SIGNAL(toggled(bool)), this, SLOT(on_diagnostic_loader_debug_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_layers, SIGNAL(toggled(bool)), this, SLOT(on_diagnostic_loader_layers_toggled(bool)));
    this->connect(this->ui->diagnostic_loader_drivers, SIGNAL(toggled(bool)), this,
                  SLOT(on_diagnostic_loader_drivers_toggled(bool)));
    this->connect(this->ui->diagnostic_dir_sdk, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_sdk_pressed()));
    this->connect(this->ui->diagnostic_dir_home, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_home_pressed()));
    this->connect(this->ui->diagnostic_dir_system, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_system_pressed()));
    this->connect(this->ui->diagnostic_dir_info, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_info_pressed()));

    this->connect(this->timer_search, &QTimer::timeout, this, &TabDiagnostics::on_timer_search);

    QShortcut *shortcut_override = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->diagnostic_group_box_loader_log);
    this->connect(shortcut_override, SIGNAL(activated()), this, SLOT(on_diagnostic_loader_messages_toggled()));

    QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->diagnostic_status_text);
    this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search()));
    QShortcut *shortcut_next = new QShortcut(QKeySequence(Qt::Key_F3), this->ui->diagnostic_status_text);
    this->connect(shortcut_next, SIGNAL(activated()), this, SLOT(on_search_next_pressed()));
    QShortcut *shortcut_prev = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F3), this->ui->diagnostic_status_text);
    this->connect(shortcut_prev, SIGNAL(activated()), this, SLOT(on_search_prev_pressed()));
    /*
        QShortcut *shortcut_case = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_C), this->ui->diagnostic_search_case);
        this->connect(shortcut_case, SIGNAL(activated()), this, SLOT(on_search_case_activated()));
        QShortcut *shortcut_whole = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_W), this->ui->diagnostic_search_whole);
        this->connect(shortcut_whole, SIGNAL(activated()), this, SLOT(on_search_whole_activated()));
        QShortcut *shortcut_regex = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_R), this->ui->diagnostic_search_regex);
        this->connect(shortcut_regex, SIGNAL(activated()), this, SLOT(on_search_regex_activated()));
    */
    this->highlighter = new Highlighter(this->ui->diagnostic_status_text->document());

    this->on_search_clear_pressed();

    this->ui->diagnostic_search_case->setVisible(false);
    this->ui->diagnostic_search_regex->setVisible(false);
    this->ui->diagnostic_search_whole->setVisible(false);

    this->ui->diagnostic_search_next->setEnabled(false);
    this->ui->diagnostic_search_prev->setEnabled(false);

    // this->ui->diagnostic_status_text->installEventFilter(&window);
    this->ui->diagnostic_status_text->document()->setMaximumBlockCount(8192);
    this->ui->diagnostic_status_text->setContextMenuPolicy(Qt::CustomContextMenu);

    this->UpdateStatus();
}

TabDiagnostics::~TabDiagnostics() {}

std::string TabDiagnostics::BuildStatus(DiagnosticMode selected_mode, std::size_t mode_index) {
    this->log_path.Clear();

    std::string log_status;

    Path working_directory = AbsolutePath(Path::APPDATA) + "/vkconfig/diagnostic";
    if (!working_directory.Exists()) {
        working_directory.Create();
    }

    Configurator &configurator = Configurator::Get();

    switch (selected_mode) {
        default: {
            assert(0);
        } break;
        case DIAGNOSTIC_VULKAN_STATUS: {
            log_status = configurator.GenerateVulkanStatus();
        } break;
        case DIAGNOSTIC_VULKAN_INFO_SUMMARY:
        case DIAGNOSTIC_VULKAN_INFO_TEXT:
        case DIAGNOSTIC_VULKAN_PROFILE:
        case DIAGNOSTIC_VULKAN_LOADER_LOG: {
            const DefaultExecutable defaults_executable{::GetExecutable(EXECUTABLE_VKINFO), "vulkaninfo"};
            const Executable executable(defaults_executable);

            if (this->process == nullptr) {
                this->process.reset(new QProcess(this));
                this->connect(this->process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(standardOutputAvailable()));
                this->connect(this->process.get(), SIGNAL(readyReadStandardError()), this, SLOT(errorOutputAvailable()));
                this->connect(this->process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this,
                              SLOT(processClosed(int, QProcess::ExitStatus)));
            }

            this->process->setProgram(executable.path.AbsolutePath().c_str());
            this->process->setWorkingDirectory(working_directory.AbsolutePath().c_str());
            this->process->setEnvironment(QProcess::systemEnvironment());

            ExecutableScope saved_scope = configurator.GetExecutableScope();

            QStringList args;

            std::string filename;

            if (selected_mode == DIAGNOSTIC_VULKAN_LOADER_LOG) {
                this->status.clear();

                args += "--summary";
                args += "-o";
                args += "vulkaninfo_tmp.txt";
            }
            if (selected_mode == DIAGNOSTIC_VULKAN_INFO_SUMMARY) {
                this->status.clear();

                filename = "vulkaninfo_summary.txt";

                args += "--summary";
                args += "-o";
                args += filename.c_str();
            }

            if (selected_mode == DIAGNOSTIC_VULKAN_INFO_TEXT) {
                filename = "vulkaninfo.txt";

                args += "--text";
                args += "-o";
                args += filename.c_str();
            }
            if (selected_mode == DIAGNOSTIC_VULKAN_PROFILE) {
                filename = format("%s.json", configurator.vulkan_system_info.physicalDevices[mode_index].deviceName.c_str());

                args += format("--json=%d", mode_index).c_str();
                args += "-o";
                args += filename.c_str();
            }

            if (!filename.empty()) {
                this->log_path = working_directory.AbsolutePath() + "/" + filename;
            }

            // configurator.SetExecutableScope(selected_mode == DIAGNOSTIC_VULKAN_PROFILE ? EXECUTABLE_NONE : EXECUTABLE_ANY);
            configurator.layers_override_enabled = selected_mode != DIAGNOSTIC_VULKAN_PROFILE;
            configurator.Override(OVERRIDE_AREA_ALL);

            this->process->setArguments(args);
            this->process->setProcessChannelMode(QProcess::MergedChannels);
            this->process->start(QIODevice::ReadOnly | QIODevice::Unbuffered);
            this->process->closeWriteChannel();

            while (this->process->waitForFinished(1000)) {
            }

            configurator.SetExecutableScope(saved_scope);
            configurator.Override(OVERRIDE_AREA_ALL);

            log_status = this->status;
        } break;
        case DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION: {
            const Configuration *configuration = nullptr;

            Configurator &configurator = Configurator::Get();
            if (configurator.GetExecutableScope() == EXECUTABLE_PER) {
                const std::vector<Executable> &executables = configurator.executables.GetExecutables();
                if (!executables.empty()) {
                    configuration = configurator.configurations.FindConfiguration(executables[mode_index].configuration);
                }
            } else {
                configuration = configurator.GetActiveConfiguration();
            }

            if (configuration != nullptr) {
                const Path &path = working_directory.AbsolutePath() + "/loader_configuration.json";
                configuration->Save(path.AbsolutePath(), true);

                QFile file(path.AbsolutePath().c_str());
                const bool result = file.open(QIODevice::ReadOnly | QIODevice::Text);
                assert(result);
                log_status = file.readAll().toStdString();
                file.close();
            } else {
                log_status = "No active layers configuration selected.";
            }
        } break;
        case DIAGNOSTIC_VULKAN_LAYERS_SETTINGS: {
            Path path = AbsolutePath(Path::LAYERS_SETTINGS);
            if (configurator.GetExecutableScope() == EXECUTABLE_PER) {
                const std::vector<Executable> &executables = configurator.executables.GetExecutables();
                path = executables[mode_index].GetLocalLayersSettingsPath();
            }

            QFile file(path.AbsolutePath().c_str());
            const bool result = file.open(QIODevice::ReadOnly | QIODevice::Text);
            if (result) {
                log_status = file.readAll().toStdString();
            } else {
                log_status = "No active layers configuration applied.";
            }
            file.close();
        } break;
        case DIAGNOSTIC_VULKAN_LOADER_SETTINGS: {
            const Path &path = AbsolutePath(Path::LOADER_SETTINGS);

            QFile file(path.AbsolutePath().c_str());
            const bool result = file.open(QIODevice::ReadOnly | QIODevice::Text);
            if (result) {
                log_status = file.readAll().toStdString();
            } else {
                log_status = "No active layers configuration applied.";
            }
            file.close();
        } break;
    }

    return log_status;
}

void TabDiagnostics::UpdateStatus() {
    this->status = this->BuildStatus(this->mode, this->ui->diagnostic_mode_options->currentIndex());

    this->ui->diagnostic_status_text->setPlainText(this->status.c_str());

    this->ui->diagnostic_search_clear->setEnabled(!this->ui->diagnostic_search_edit->text().isEmpty());
    this->ui->diagnostic_search_edit->setFocus();
    this->ui->diagnostic_export_file->setEnabled(true);

    this->ResetTextCursor();
}

void TabDiagnostics::UpdateUI(UpdateUIMode mode) {
    (void)mode;

    const Configurator &configurator = Configurator::Get();

    this->on_mode_changed(this->mode);

    this->ui->diagnostic_loader_errors->blockSignals(true);
    this->ui->diagnostic_loader_errors->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_ERROR));
    this->ui->diagnostic_loader_errors->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_errors->blockSignals(false);
    this->ui->diagnostic_loader_warns->blockSignals(true);
    this->ui->diagnostic_loader_warns->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_WARN));
    this->ui->diagnostic_loader_warns->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_warns->blockSignals(false);
    this->ui->diagnostic_loader_infos->blockSignals(true);
    this->ui->diagnostic_loader_infos->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_INFO));
    this->ui->diagnostic_loader_infos->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_infos->blockSignals(false);
    this->ui->diagnostic_loader_debug->blockSignals(true);
    this->ui->diagnostic_loader_debug->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_DEBUG));
    this->ui->diagnostic_loader_debug->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_debug->blockSignals(false);
    this->ui->diagnostic_loader_layers->blockSignals(true);
    this->ui->diagnostic_loader_layers->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_LAYER));
    this->ui->diagnostic_loader_layers->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_layers->blockSignals(false);
    this->ui->diagnostic_loader_drivers->blockSignals(true);
    this->ui->diagnostic_loader_drivers->setChecked(configurator.loader_log_messages_flags & GetBit(LOG_DRIVER));
    this->ui->diagnostic_loader_drivers->setEnabled(configurator.loader_log_enabled);
    this->ui->diagnostic_loader_drivers->blockSignals(false);

    this->ui->diagnostic_group_box_loader_log->blockSignals(true);
    this->ui->diagnostic_group_box_loader_log->setChecked(configurator.loader_log_enabled);
    this->ui->diagnostic_group_box_loader_log->blockSignals(false);

    this->window.UpdateUI_Status();

    this->ResetTextCursor();

    // QColor background_color = palette.color(::GetActualThemeMode(configurator.current_theme_mode) == THEME_MODE_FORCE_LIGHT ?
    // QPalette::Light : QPalette::Dark);
    QPalette palette = this->ui->configurations_list->palette();
    QColor highlight = palette.color(QPalette::AlternateBase);
    this->highlighter->setColor(highlight);
    // this->highlighter->setColor(
    //     ::GetActualThemeMode(configurator.current_theme_mode) == THEME_MODE_FORCE_LIGHT ? highlight.lighter() :
    //     highlight.darker());
}

void TabDiagnostics::CleanUI() {}

bool TabDiagnostics::EventFilter(QObject *target, QEvent *event) {
    (void)target;
    /*
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        switch (keyEvent->key()) {
            case Qt::Key_Enter:
            case Qt::Key_Return:
                if (this->ui->diagnostic_status_text->hasFocus()) {
                    this->SearchFind(false);
                }
                return false;
        }
        return false;
    }
    */
    return false;
}

void TabDiagnostics::on_diagnostic_loader_messages_toggled() {
    this->ui->diagnostic_group_box_loader_log->setChecked(!this->ui->diagnostic_group_box_loader_log->isChecked());
}

void TabDiagnostics::on_diagnostic_loader_messages_toggled(bool checked) {
    Configurator &configurator = Configurator::Get();
    configurator.loader_log_enabled = checked;
    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->UpdateUI(UPDATE_REFRESH_UI);
}

void TabDiagnostics::OnCheckedLoaderMessageTypes(bool checked) {
    (void)checked;

    Configurator &configurator = Configurator::Get();
    configurator.loader_log_messages_flags = 0;

    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_errors->isChecked() ? GetBit(LOG_ERROR) : 0;
    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_warns->isChecked() ? GetBit(LOG_WARN) : 0;
    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_infos->isChecked() ? GetBit(LOG_INFO) : 0;
    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_debug->isChecked() ? GetBit(LOG_DEBUG) : 0;
    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_layers->isChecked() ? GetBit(LOG_LAYER) : 0;
    configurator.loader_log_messages_flags |= this->ui->diagnostic_loader_drivers->isChecked() ? GetBit(LOG_DRIVER) : 0;

    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);
}

void TabDiagnostics::on_diagnostic_loader_errors_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_loader_warns_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_loader_infos_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_loader_debug_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_loader_layers_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_loader_drivers_toggled(bool checked) { this->OnCheckedLoaderMessageTypes(checked); }

void TabDiagnostics::on_diagnostic_dir_sdk_pressed() {
    QDesktopServices::openUrl(QUrl::fromLocalFile(AbsolutePath(Path::SDK).c_str()));
}

void TabDiagnostics::on_diagnostic_dir_home_pressed() {
    QDesktopServices::openUrl(QUrl::fromLocalFile(AbsolutePath(Path::HOME).c_str()));
}

void TabDiagnostics::on_diagnostic_dir_system_pressed() {
    QDesktopServices::openUrl(QUrl::fromLocalFile(AbsolutePath(Path::APPDATA).c_str()));
}

void TabDiagnostics::on_diagnostic_dir_info_pressed() {
    DefaultExecutable default_executable = {
        GetExecutable(EXECUTABLE_VKCAPSVIEWER), "vulkanCapsViewer", true, {{"Open GUI", "${VULKAN_HOME}", ""}}};
    const Executable executable(default_executable);
    const ExecutableOptions *options = executable.GetActiveOptions();

    std::unique_ptr<QProcess> caps_viewer_process;
    caps_viewer_process.reset(new QProcess(this));
    // this->connect(caps_viewer_process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(standardOutputAvailable()));
    // this->connect(caps_viewer_process.get(), SIGNAL(readyReadStandardError()), this, SLOT(errorOutputAvailable()));
    // this->connect(caps_viewer_process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processClosed(int,
    // QProcess::ExitStatus)));

    caps_viewer_process->setProgram(executable.path.AbsolutePath().c_str());
    caps_viewer_process->setWorkingDirectory(options->working_folder.AbsolutePath().c_str());
    caps_viewer_process->setEnvironment(QProcess::systemEnvironment());
    caps_viewer_process->startDetached();
}

void TabDiagnostics::on_refresh_log() {
    static bool only_once = true;

    Configurator &configurator = Configurator::Get();

    if (only_once && !(configurator.Get(HIDE_MESSAGE_WARN_NO_LOADER_LOG_ENABLED))) {
        if (this->mode == DIAGNOSTIC_VULKAN_LOADER_LOG &&
            (!configurator.loader_log_enabled || configurator.loader_log_messages_flags == 0)) {
            only_once = false;  // don't show again during this run

            QMessageBox alert;
            alert.setWindowTitle("Vulkan Loader Log is disabled");
            alert.setText("Do you want enable Vulkan Loader log?");
            alert.setInformativeText("If you are not, the Vulkan Loader log will be essentially empty...");
            alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            alert.setDefaultButton(QMessageBox::Yes);
            alert.setCheckBox(new QCheckBox("Do not show again."));
            alert.setIcon(QMessageBox::Warning);
            int result = alert.exec();
            if (alert.checkBox()->isChecked()) {
                configurator.Set(HIDE_MESSAGE_WARN_NO_LOADER_LOG_ENABLED);
            }

            if (result == QMessageBox::Yes) {
                if (configurator.loader_log_messages_flags == 0) {
                    configurator.loader_log_messages_flags =
                        GetBit(LOG_ERROR) | GetBit(LOG_WARN) | GetBit(LOG_INFO) | GetBit(LOG_LAYER) | GetBit(LOG_DRIVER);
                }

                this->on_diagnostic_loader_messages_toggled(true);
            }
        }
    }

    const bool per_executable =
        (this->mode == DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION || this->mode == DIAGNOSTIC_VULKAN_LAYERS_SETTINGS) &&
        configurator.GetExecutableScope() == EXECUTABLE_PER;
    const bool profile_mode = this->mode == DIAGNOSTIC_VULKAN_PROFILE;

    this->ui->diagnostic_mode_options->setEnabled((profile_mode && configurator.vulkan_system_info.physicalDevices.size() > 1) ||
                                                  (per_executable && configurator.executables.GetExecutables().size() > 1));
    this->ui->diagnostic_mode_options->setVisible(profile_mode || per_executable);

    if (this->ui->diagnostic_mode_options->isVisible()) {
        this->ui->diagnostic_mode_options->blockSignals(true);
        this->ui->diagnostic_mode_options->clear();
        if (profile_mode) {
            for (std::size_t i = 0, n = configurator.vulkan_system_info.physicalDevices.size(); i < n; ++i) {
                this->ui->diagnostic_mode_options->addItem(configurator.vulkan_system_info.physicalDevices[i].deviceName.c_str());
            }
        } else {
            const std::vector<Executable> &executables = configurator.executables.GetExecutables();
            for (std::size_t i = 0, n = executables.size(); i < n; ++i) {
                if (i == 0) {
                    this->ui->diagnostic_mode_options->setToolTip(executables[i].path.AbsolutePath().c_str());
                }
                this->ui->diagnostic_mode_options->addItem(executables[i].path.Basename().c_str());
                this->ui->diagnostic_mode_options->setItemData(i, executables[i].path.AbsolutePath().c_str(), Qt::ToolTipRole);
            }
        }
        this->ui->diagnostic_mode_options->blockSignals(false);
    }

    this->UpdateStatus();
}

void TabDiagnostics::on_mode_changed(int index) {
    this->mode = static_cast<DiagnosticMode>(index);

    this->on_refresh_log();
}

void TabDiagnostics::on_mode_options_changed(int index) { this->status = this->BuildStatus(this->mode, index); }

void TabDiagnostics::on_export_folder() {
    Configurator &configurator = Configurator::Get();
    const Configuration *configuration = nullptr;

    std::string saved_status = this->status;

    Path export_dir = configurator.last_path_status;
    if (!export_dir.Exists()) {
        export_dir.Create(false);
    }

    const QString selected_dir = QFileDialog::getExistingDirectory(
        this->ui->diagnostic_export_folder, "Select Diagnostic Logs Directory...", export_dir.AbsolutePath().c_str());
    if (selected_dir.isEmpty()) {
        return;
    }

    configurator.last_path_status = export_dir = selected_dir.toStdString();
    QDesktopServices::openUrl(QUrl::fromLocalFile(export_dir.AbsolutePath().c_str()));

    for (int diag_index = 0, diag_count = DIAGNOSTIC_MODE_COUNT; diag_index < diag_count; ++diag_index) {
        DiagnosticMode current_mode = static_cast<DiagnosticMode>(diag_index);
        std::size_t options_count = 1;

        switch (diag_index) {
            case DIAGNOSTIC_VULKAN_PROFILE:
                options_count = configurator.vulkan_system_info.physicalDevices.size();
                break;
            case DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION:
            case DIAGNOSTIC_VULKAN_LAYERS_SETTINGS:
                if (configurator.GetExecutableScope() == EXECUTABLE_PER) {
                    options_count = configurator.executables.GetExecutables().size();
                }
                break;
            default:
                break;
        }

        for (std::size_t options_index = 0; options_index < options_count; ++options_index) {
            std::string log_status = this->BuildStatus(current_mode, options_index);

            Path export_path = export_dir;

            switch (current_mode) {
                default:
                case DIAGNOSTIC_VULKAN_STATUS:
                case DIAGNOSTIC_VULKAN_INFO_SUMMARY:
                case DIAGNOSTIC_VULKAN_INFO_TEXT:
                case DIAGNOSTIC_VULKAN_LOADER_LOG:
                case DIAGNOSTIC_VULKAN_LOADER_SETTINGS:
                    export_path = export_dir.RelativePath() + ::GetFilename(current_mode);
                    break;
                case DIAGNOSTIC_VULKAN_PROFILE: {
                    export_path =
                        export_dir.RelativePath() +
                        format("/%s.json", configurator.vulkan_system_info.physicalDevices[options_index].deviceName.c_str());
                } break;
                case DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION:
                case DIAGNOSTIC_VULKAN_LAYERS_SETTINGS: {
                    if (configurator.GetExecutableScope() == EXECUTABLE_PER) {
                        const std::vector<Executable> &executables = configurator.executables.GetExecutables();
                        configuration = configurator.configurations.FindConfiguration(executables[options_index].configuration);
                    } else {
                        configuration = configurator.GetActiveConfiguration();
                    }

                    if (current_mode == DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION) {
                        export_path = export_dir.RelativePath() + format("/%s.json", configuration->key.c_str());
                    } else {
                        export_path = export_dir.RelativePath() + format("/%s.txt", configuration->key.c_str());
                    }
                } break;
            }

            QFile json_file(export_path.AbsolutePath().c_str());
            const bool result = json_file.open(QIODevice::WriteOnly | QIODevice::Text);
            if (result) {
                json_file.write(log_status.c_str());
                json_file.close();
            }
        }
    }

    this->status = saved_status;
}

void TabDiagnostics::on_export_file() {
    Configurator &configurator = Configurator::Get();

    const Configuration *configuration = nullptr;

    Path export_path = configurator.last_path_status;

    switch (this->mode) {
        default:
        case DIAGNOSTIC_VULKAN_STATUS:
        case DIAGNOSTIC_VULKAN_INFO_SUMMARY:
        case DIAGNOSTIC_VULKAN_INFO_TEXT:
        case DIAGNOSTIC_VULKAN_LOADER_LOG:
        case DIAGNOSTIC_VULKAN_LOADER_SETTINGS:
            export_path = export_path.RelativePath() + ::GetFilename(this->mode);
            break;
        case DIAGNOSTIC_VULKAN_PROFILE: {
            std::size_t index = this->ui->diagnostic_mode_options->currentIndex();

            export_path = export_path.RelativePath() +
                          format("/%s.json", configurator.vulkan_system_info.physicalDevices[index].deviceName.c_str());
        } break;
        case DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION:
        case DIAGNOSTIC_VULKAN_LAYERS_SETTINGS: {
            if (configurator.GetExecutableScope() == EXECUTABLE_PER) {
                int index = this->ui->diagnostic_mode_options->currentIndex();
                const std::vector<Executable> &executables = configurator.executables.GetExecutables();
                configuration = configurator.configurations.FindConfiguration(executables[index].configuration);
            } else {
                configuration = configurator.GetActiveConfiguration();
            }

            if (this->mode == DIAGNOSTIC_VULKAN_LAYERS_CONFIGURATION) {
                export_path = export_path.RelativePath() + format("/%s.json", configuration->key.c_str());
            } else {
                export_path = export_path.RelativePath() + format("/%s.txt", configuration->key.c_str());
            }
        } break;
    }

    export_path = QFileDialog::getSaveFileName(this->ui->diagnostic_export_file, "Save file...", export_path.AbsolutePath().c_str(),
                                               "Log (*.*)")
                      .toStdString();

    if (!export_path.Empty()) {
        configurator.last_path_status = export_path.AbsoluteDir();

        QFile json_file(export_path.AbsolutePath().c_str());
        const bool result = json_file.open(QIODevice::WriteOnly | QIODevice::Text);
        if (result) {
            json_file.write(this->status.c_str());
            json_file.close();

            QDesktopServices::openUrl(QUrl::fromLocalFile(export_path.AbsolutePath().c_str()));
        } else {
            QMessageBox message;
            message.setIcon(QMessageBox::Critical);
            message.setWindowTitle("Failed to save the diagnostic log!");
            message.setText(format("Couldn't write to '%s'.", export_path.AbsolutePath().c_str()).c_str());
            message.setInformativeText("Select a file path with 'write' rights.");
        }
    }
}

void TabDiagnostics::on_focus_search() {
    this->ui->diagnostic_search_edit->setFocus();
    // this->ui->diagnostic_status_text->moveCursor(QTextCursor::Start);
}

void TabDiagnostics::on_timer_search() {
    this->timer_search->stop();

    this->ResetTextCursor();

    if (!this->diagnostic_search_text.empty()) {
        this->highlighter->setSearch(this->diagnostic_search_text.c_str());

        this->ui->diagnostic_status_text->moveCursor(QTextCursor::StartOfWord);
        this->SearchFind(false);
        this->text_is_reset = false;
    }
}

void TabDiagnostics::on_search_textEdited(const QString &text) {
    this->timer_search->start(200);

    this->diagnostic_search_text = text.toStdString();

    this->ui->diagnostic_search_next->setEnabled(!this->ui->diagnostic_search_edit->text().isEmpty());
    this->ui->diagnostic_search_prev->setEnabled(!this->ui->diagnostic_search_edit->text().isEmpty());
    this->ui->diagnostic_search_clear->setEnabled(!text.isEmpty());
}

void TabDiagnostics::ResetTextCursor() {
    if (this->ui->diagnostic_status_text->document()->isEmpty()) {
        return;
    }

    if (this->text_is_reset) {
        return;
    }

    QTextCursor cursor = this->ui->diagnostic_status_text->textCursor();
    int saved_anchor = cursor.anchor();
    int saved_Position = cursor.position();

    cursor.setPosition(0, QTextCursor::MoveAnchor);
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);

    QTextCharFormat format;
    cursor.setCharFormat(format);

    cursor.setPosition(saved_Position, QTextCursor::MoveAnchor);
    cursor.setPosition(saved_anchor, QTextCursor::KeepAnchor);

    this->text_is_reset = true;
}

void TabDiagnostics::on_search_clear_pressed() {
    this->highlighter->setSearch("");

    this->diagnostic_search_text.clear();
    this->ui->diagnostic_search_edit->clear();
    this->ui->diagnostic_search_next->setEnabled(false);
    this->ui->diagnostic_search_prev->setEnabled(false);
    this->ui->diagnostic_search_clear->setEnabled(false);

    this->ResetTextCursor();
}

void TabDiagnostics::on_search_next_pressed() {
    QTextCursor cursor = this->ui->diagnostic_status_text->textCursor();
    this->ui->diagnostic_status_text->moveCursor(QTextCursor::EndOfWord);
    this->ui->diagnostic_status_text->setTextCursor(cursor);
    this->SearchFind(false);
}

void TabDiagnostics::on_search_prev_pressed() {
    QTextCursor cursor = this->ui->diagnostic_status_text->textCursor();
    this->ui->diagnostic_status_text->moveCursor(QTextCursor::StartOfWord);
    this->ui->diagnostic_status_text->setTextCursor(cursor);
    this->SearchFind(true);
}

void TabDiagnostics::SearchFind(bool prev) {
    this->ResetTextCursor();

    QTextDocument::FindFlags flags = prev ? QTextDocument::FindBackward : QTextDocument::FindFlags(0);

    if (this->search_case) {
        flags |= QTextDocument::FindCaseSensitively;
    }
    if (this->search_whole) {
        flags |= QTextDocument::FindWholeWords;
    }

    bool found = false;
    if (this->search_regex) {
        found = this->ui->diagnostic_status_text->find(QRegularExpression(this->ui->diagnostic_search_edit->text()), flags);
    } else {
        found = this->ui->diagnostic_status_text->find(this->ui->diagnostic_search_edit->text(), flags);
    }

    QTextCursor cursor = this->ui->diagnostic_status_text->textCursor();

    if (found) {
        QTextCharFormat format;
        format.setFontWeight(QFont::Bold);
        cursor.mergeCharFormat(format);

        this->ui->diagnostic_status_text->ensureCursorVisible();
    } else {
        if (!prev && !cursor.atStart()) {
            this->ui->diagnostic_status_text->moveCursor(QTextCursor::Start);
            this->SearchFind(prev);
            return;
        }

        if (prev && !cursor.atEnd()) {
            this->ui->diagnostic_status_text->moveCursor(QTextCursor::End);
            this->SearchFind(prev);
            return;
        }
    }
}

void TabDiagnostics::on_search_case_activated() { this->ui->diagnostic_search_case->setChecked(!this->search_case); }

void TabDiagnostics::on_search_case_toggled(bool checked) {
    this->search_case = checked;
    this->highlighter->setCase(checked);
}

void TabDiagnostics::on_search_whole_activated() { this->ui->diagnostic_search_whole->setChecked(!this->search_whole); }

void TabDiagnostics::on_search_whole_toggled(bool checked) {
    this->search_whole = checked;
    this->highlighter->setWhole(checked);
}

void TabDiagnostics::on_search_regex_activated() { this->ui->diagnostic_search_regex->setChecked(!this->search_regex); }

void TabDiagnostics::on_search_regex_toggled(bool checked) {
    this->search_regex = checked;
    this->highlighter->setRegex(checked);
}

void TabDiagnostics::on_context_menu(const QPoint &pos) {
    QMenu *menu = this->ui->diagnostic_status_text->createStandardContextMenu();
    menu->addSeparator();

    QAction *action_refresh = new QAction(this->status.empty() ? "Refresh" : "Clear", nullptr);
    action_refresh->setEnabled(true);
    menu->addAction(action_refresh);

    menu->addSeparator();

    QAction *action_search = new QAction("Search...", nullptr);
    // action_search->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_F));
    action_search->setEnabled(true);
    menu->addAction(action_search);

    QAction *action = menu->exec(this->ui->diagnostic_status_text->mapToGlobal(pos));

    if (action == action_refresh) {
        if (this->status.empty()) {
            this->status = this->BuildStatus(static_cast<DiagnosticMode>(this->ui->diagnostic_mode->currentIndex()),
                                             this->ui->diagnostic_mode_options->currentIndex());
        } else {
            this->status.clear();
            this->ui->diagnostic_status_text->clear();
        }
    } else if (action == action_search) {
        QTextCursor cursor = this->ui->diagnostic_status_text->textCursor();
        if (cursor.hasSelection()) {
            QString text = cursor.selectedText();
            this->ui->diagnostic_search_edit->setText(text);
            this->on_search_textEdited(text);
        }
        this->on_focus_search();
    }

    menu->deleteLater();
}

void TabDiagnostics::processClosed(int exit_code, QProcess::ExitStatus status) {
    (void)exit_code;
    (void)status;

    assert(this->process);

    if (!this->log_path.Empty()) {
        QFile file(this->log_path.AbsolutePath().c_str());
        const bool result = file.open(QIODevice::ReadOnly | QIODevice::Text);
        if (result) {
            this->status = file.readAll().toStdString();
            file.close();
        }
    }

    this->ui->diagnostic_status_text->setPlainText(this->status.c_str());

    if (this->process->processId() > 0) {
        this->process->kill();
        this->process->waitForFinished();
    }
}

void TabDiagnostics::standardOutputAvailable() {
    if (this->process != nullptr && this->log_path.Empty()) {
        this->status += this->process->readAllStandardOutput().toStdString();
    }
}

void TabDiagnostics::errorOutputAvailable() {
    if (this->process != nullptr && this->log_path.Empty()) {
        this->status += this->process->readAllStandardError().toStdString();
    }
}
