/*
 * 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:
 * - Richard S. Wright Jr. <richard@lunarg.com>
 * - Christophe Riccio <christophe@lunarg.com>
 */

#include "mainwindow.h"
#include "style.h"

#include "../vkconfig_core/configurator.h"
#include "../vkconfig_core/util.h"
#include "../vkconfig_core/ui.h"
#include "../vkconfig_core/version.h"
#include "../vkconfig_core/date.h"
#include "../vkconfig_core/type_platform.h"
#include "../vkconfig_core/type_log.h"
#include "../vkconfig_core/type_hide_message.h"

#include <QMenu>
#include <QMessageBox>
#include <QFrame>
#include <QComboBox>
#include <QVariant>
#include <QContextMenuEvent>
#include <QLineEdit>
#include <QFileDialog>
#include <QDesktopServices>
#include <QSettings>
#include <QWidgetAction>
#include <QShortcut>
#include <QSignalMapper>

#include <cassert>

MainWindow::MainWindow(QApplication& app, QWidget* parent)
    : QMainWindow(parent), _tray_icon(new QSystemTrayIcon(this)), app(app), ui(new Ui::MainWindow) {
    ui->setupUi(this);

    this->tabs[TAB_DIAGNOSTIC].reset(new TabDiagnostics(*this, ui));
    this->tabs[TAB_APPLICATIONS].reset(new TabApplications(*this, ui));
    this->tabs[TAB_LAYERS_PATHS].reset(new TabLayers(*this, ui));
    this->tabs[TAB_CONFIGURATIONS].reset(new TabConfigurations(*this, ui));
    this->tabs[TAB_DOCUMENTATION].reset(new TabDocumentation(*this, ui));
    this->tabs[TAB_DRIVERS].reset(new TabDrivers(*this, ui));
    this->tabs[TAB_PREFERENCES].reset(new TabPreferences(*this, ui));
    this->tabs[TAB_ABOUT].reset(new TabAbout(*this, ui));

    this->connect(qApp, &QGuiApplication::commitDataRequest, this, &MainWindow::commitDataRequest);

    for (int i = TAB_FIRST, l = TAB_LAST; i <= l; ++i) {
        this->ui->tab_widget->setTabText(i, GetLabel(static_cast<TabType>(i)));
    }

    Configurator& configurator = Configurator::Get();

    if (!configurator.window_geometry.isEmpty()) {
        this->restoreGeometry(configurator.window_geometry);
        this->restoreState(configurator.window_state);
    }

    this->ui->tab_widget->setCurrentIndex(configurator.active_tab);
    this->ui->tab_widget->installEventFilter(this);

    this->connect(this->ui->tab_widget, SIGNAL(currentChanged(int)), this, SLOT(on_tab_widget_currentChanged(int)));
    this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::OnIconActivated);

    this->UpdateUI(UPDATE_REBUILD_UI);

    configurator.Override(OVERRIDE_AREA_ALL);
}

MainWindow::~MainWindow() {}

void MainWindow::commitDataRequest(QSessionManager& manager) {
    (void)manager;

    Configurator& configurator = Configurator::Get();
    configurator.Surrender(OVERRIDE_AREA_ALL);

    Configurator::Release();
}

void MainWindow::UpdateUI(UpdateUIMode mode) {
    if (this->tabs[this->ui->tab_widget->currentIndex()] != nullptr) {
        this->tabs[this->ui->tab_widget->currentIndex()]->UpdateUI(mode);
    }

    // this->UpdateUI_Status();
}

void MainWindow::UpdateUI_Status() {
    const Configurator& configurator = Configurator::Get();

    this->setWindowTitle(GetMainWindowTitle().c_str());

    if (QSystemTrayIcon::isSystemTrayAvailable()) {
        // Device
        {
            QMenu* menu = new QMenu(this);

            QSignalMapper* mapper_device = new QSignalMapper(menu);
            QSignalMapper* mapper_layers = new QSignalMapper(menu);

            QAction* tray_restore_action = new QAction("&Show Vulkan Configurator UI", this);
            tray_restore_action->setIcon(QIcon(":/resourcefiles/vkconfig-on.png"));
            QFont font = tray_restore_action->font();
            font.setBold(true);
            tray_restore_action->setFont(font);
            this->connect(tray_restore_action, &QAction::triggered, this, &MainWindow::OnTrayActionShow);
            menu->addAction(tray_restore_action);

            menu->addSeparator();
            const bool enabled_device = configurator.driver_override_enabled;

            QAction* tray_override = new QAction("Override System Vulkan &Device with:", this);
            tray_override->setFont(font);
            tray_override->setCheckable(true);
            tray_override->setChecked(enabled_device);

            this->connect(tray_override, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideDevice);
            menu->addAction(tray_override);

            for (std::size_t i = 0, n = configurator.vulkan_system_info.physicalDevices.size(); i < n; ++i) {
                const VulkanPhysicalDeviceInfo& info = configurator.vulkan_system_info.physicalDevices[i];
                const DeviceInfo& device_info = ::GetDeviceInfo(info);

                QAction* action = new QAction(format("%s (%s)", info.GetLabel().c_str(), info.GetVersion().c_str()).c_str());
                action->setCheckable(true);
                action->setChecked(device_info == configurator.driver_override_info);
                action->setEnabled(enabled_device);

                menu->addAction(action);

                this->connect(action, SIGNAL(triggered()), mapper_device, SLOT(map()));
                mapper_device->setMapping(action, i);
            }

            this->connect(mapper_device, &QSignalMapper::mappedInt, this, &MainWindow::OnDeviceChanged);

            menu->addSeparator();
            const bool enabled_layers = configurator.layers_override_enabled && configurator.GetExecutableScope() != EXECUTABLE_PER;

            QAction* tray_override_layers = new QAction("Override System Vulkan &Layers Configuration with:", this);
            tray_override_layers->setFont(font);
            tray_override_layers->setCheckable(true);
            tray_override_layers->setChecked(enabled_layers);

            this->connect(tray_override_layers, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLayers);
            menu->addAction(tray_override_layers);

            for (std::size_t i = 0, n = configurator.configurations.available_configurations.size(); i < n; ++i) {
                const Configuration& configuration = configurator.configurations.available_configurations[i];

                QAction* action = new QAction(configuration.key.c_str(), this);
                action->setCheckable(true);
                action->setChecked(configuration.key == configurator.GetSelectedGlobalConfiguration());
                action->setEnabled(enabled_layers);

                menu->addAction(action);

                this->connect(action, SIGNAL(triggered()), mapper_layers, SLOT(map()));
                mapper_layers->setMapping(action, i);
            }

            this->connect(mapper_layers, &QSignalMapper::mappedInt, this, &MainWindow::OnLayersChanged);

            menu->addSeparator();

            QAction* tray_override_loader = new QAction("Override System Vulkan Loader Log", this);
            tray_override_loader->setFont(font);
            tray_override_loader->setCheckable(true);
            tray_override_loader->setChecked(configurator.loader_log_enabled);
            this->connect(tray_override_loader, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLog);
            menu->addAction(tray_override_loader);

            menu->addSeparator();

            QAction* tray_quit_action = new QAction("&Quit Vulkan Configurator", this);
            tray_quit_action->setIcon(::Get(configurator.current_theme_mode, ::ICON_EXIT));
            tray_quit_action->setFont(font);
            this->connect(tray_quit_action, &QAction::triggered, qApp, &QCoreApplication::quit);
            menu->addAction(tray_quit_action);

            if (VKC_PLATFORM == PLATFORM_LINUX) {
                if (this->_tray_icon != nullptr) {
                    delete this->_tray_icon;
                }
                this->_tray_icon = new QSystemTrayIcon(this);
                this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::OnIconActivated);
            }

            this->_tray_icon->setContextMenu(menu);

            if (configurator.layers_override_enabled || configurator.driver_override_enabled || configurator.loader_log_enabled ||
                configurator.loader_log_enabled) {
                this->_tray_icon->setIcon(QIcon(":/resourcefiles/vkconfig-on.png"));
            } else {
                this->_tray_icon->setIcon(QIcon(":/resourcefiles/vkconfig-off.png"));
            }

            this->_tray_icon->show();
        }
    }

    if (configurator.layers_override_enabled || configurator.driver_override_enabled || configurator.loader_log_enabled) {
        this->setWindowIcon(QIcon(":/resourcefiles/vkconfig-on.png"));
    } else {
        this->setWindowIcon(QIcon(":/resourcefiles/vkconfig-off.png"));
    }
}

void MainWindow::OnIconActivated(QSystemTrayIcon::ActivationReason reason) {
    switch (reason) {
        default:
            break;
        case QSystemTrayIcon::Context:
            break;
        case QSystemTrayIcon::DoubleClick:
            if (this->isMinimized() || this->isHidden()) {
                this->hide();
                this->showNormal();
                this->setWindowState(Qt::WindowState::WindowActive);
                this->UpdateUI(UPDATE_REBUILD_UI);
            } else {
                this->hide();
            }
            break;
    }
}

void MainWindow::OnTrayActionShow() {
    this->hide();
    this->showNormal();
    this->UpdateUI(UPDATE_REBUILD_UI);
}

void MainWindow::OnTrayActionOverrideLayers(bool toggled) {
    Configurator& configurator = Configurator::Get();
    configurator.layers_override_enabled = toggled;
    configurator.Override(OVERRIDE_AREA_ALL);

    this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI);
}

void MainWindow::OnLayersChanged(int index) {
    Configurator& configurator = Configurator::Get();
    configurator.SetActiveConfigurationName(configurator.configurations.available_configurations[index].key);
    configurator.Override(OVERRIDE_AREA_ALL);

    this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI);
}

void MainWindow::OnTrayActionOverrideDevice(bool toggled) {
    Configurator& configurator = Configurator::Get();
    configurator.driver_override_mode = DRIVER_MODE_SINGLE;
    configurator.driver_override_enabled = toggled;
    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI);
}

void MainWindow::OnDeviceChanged(int index) {
    Configurator& configurator = Configurator::Get();
    configurator.driver_override_info = ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[index]);
    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI);
}

void MainWindow::OnTrayActionOverrideLog(bool toggled) {
    Configurator& configurator = Configurator::Get();
    configurator.loader_log_enabled = toggled;
    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->tabs[TAB_DIAGNOSTIC]->UpdateUI(UPDATE_REBUILD_UI);
}

/// The only thing we need to do here is clear the configuration if
/// the user does not want it active.
void MainWindow::closeEvent(QCloseEvent* event) {
    Configurator& configurator = Configurator::Get();

    QGuiApplication::setQuitOnLastWindowClosed(!configurator.GetUseSystemTray());

    if (configurator.GetUseSystemTray()) {
        // Alert the user to the current state of the vulkan configurator and
        // give them the option to not shutdown.
        if (!(configurator.Get(HIDE_MESSAGE_USE_SYSTEM_TRAY))) {
            std::string shut_down_state;

            if (configurator.layers_override_enabled) {
                shut_down_state =
                    "Vulkan Layers override will remain in effect while Vulkan Configurator remain active in the system tray.";
            } else {
                shut_down_state =
                    "No Vulkan layers override will be active when Vulkan Configurator remain active in the system tray.";
            }

            QMessageBox alert(this);
            alert.setWindowTitle("Vulkan Configurator will remain in the system tray");
            alert.setText(shut_down_state.c_str());
            alert.setIcon(QMessageBox::Question);
            alert.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
            alert.setDefaultButton(QMessageBox::Ok);
            alert.setCheckBox(new QCheckBox("Do not show again."));

            QPalette palette, palette_saved = this->ui->preferences_keep_running->palette();
            palette.setColor(QPalette::WindowText, QColor(Qt::red));
            this->ui->preferences_keep_running->setPalette(palette);

            this->ui->tab_widget->setCurrentIndex(TAB_PREFERENCES);

            int ret_val = alert.exec();
            if (alert.checkBox()->isChecked()) {
                configurator.Set(HIDE_MESSAGE_USE_SYSTEM_TRAY);
            }

            this->ui->preferences_keep_running->setPalette(palette_saved);

            if (ret_val == QMessageBox::Cancel) {
                event->ignore();  // Not closing the window
                return;
            }
        }
    }

    this->tabs[this->ui->tab_widget->currentIndex()]->CleanUI();

    configurator.window_geometry = this->saveGeometry();
    configurator.window_state = this->saveState();

    QMainWindow::closeEvent(event);
}
/*
void MainWindow::showEvent(QShowEvent *event) {
    (void)event;

    this->UpdateUI(UPDATE_REBUILD_UI);

    event->accept();
}
*/
bool MainWindow::eventFilter(QObject* target, QEvent* event) {
    if (this->tabs[this->ui->tab_widget->currentIndex()] == nullptr) {
        return true;
    }

    return this->tabs[this->ui->tab_widget->currentIndex()]->EventFilter(target, event);
}

void MainWindow::changeEvent(QEvent* event) {
    if (event->type() == QEvent::ThemeChange) {
        Configurator& configurator = Configurator::Get();

        static_cast<TabPreferences*>(this->tabs[TAB_PREFERENCES].get())->on_theme_mode_changed(configurator.current_theme_mode);
    }
}

void MainWindow::on_tab_widget_currentChanged(int index) {
    assert(index >= 0);
    this->tabs[index]->UpdateUI(UPDATE_REBUILD_UI);
}
