#include <QString>
#include <QFileDialog>
#include <QCloseEvent>
#include <QActionGroup>
#include <QDesktopServices>
#include "settingsdialog.h"
#include "plugindialog.h"
#include "hotkeydialog.h"
#include "cheats.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "interface/common.h"
#include "vidext.h"
#include "netplay/createroom.h"
#include "netplay/joinroom.h"

#include "osal/osal_preproc.h"
#include "interface/core_commands.h"
#include "interface/version.h"
#include "m64p_common.h"
#include "version.h"

#define RECENT_SIZE 10

#define SETTINGS_VER 2

m64p_video_extension_functions vidExtFunctions = {17,
                                                  qtVidExtFuncInit,
                                                  qtVidExtFuncQuit,
                                                  qtVidExtFuncListModes,
                                                  qtVidExtFuncListRates,
                                                  qtVidExtFuncSetMode,
                                                  qtVidExtFuncSetModeWithRate,
                                                  qtVidExtFuncGLGetProc,
                                                  qtVidExtFuncGLSetAttr,
                                                  qtVidExtFuncGLGetAttr,
                                                  qtVidExtFuncGLSwapBuf,
                                                  qtVidExtFuncSetCaption,
                                                  qtVidExtFuncToggleFS,
                                                  qtVidExtFuncResizeWindow,
                                                  qtVidExtFuncGLGetDefaultFramebuffer,
                                                  qtVidExtFuncInitWithRenderMode,
                                                  qtVidExtFuncGetVkSurface,
                                                  qtVidExtFuncGetVkInstExtensions};

void MainWindow::updatePlugins()
{
#ifdef PLUGIN_DIR_PATH
    QString pluginPath = PLUGIN_DIR_PATH;
#else
    QString pluginPath = QCoreApplication::applicationDirPath();
#endif
    QDir PluginDir(pluginPath);
    PluginDir.setFilter(QDir::Files);
    QStringList Filter;
    Filter.append("");
    QStringList current;
    QString default_value;

    if (!settings->contains("inputPlugin"))
    {
        Filter.replace(0, "*-input-*");
        current = PluginDir.entryList(Filter);
        default_value = "simple64-input-qt";
        default_value += OSAL_DLL_EXTENSION;
        if (current.isEmpty())
            settings->setValue("inputPlugin", "dummy");
        else if (current.indexOf(default_value) != -1)
            settings->setValue("inputPlugin", default_value);
        else
            settings->setValue("inputPlugin", current.at(0));
    }

    ui->actionController_Configuration->setEnabled(settings->value("inputPlugin").toString().contains("-qt"));
}

void MainWindow::updateGB(Ui::MainWindow *ui)
{
    QMenu *GB = new QMenu(this);
    GB->setTitle("Game Boy Cartridges");
    ui->menuFile->insertMenu(ui->actionTake_Screenshot, GB);

    QAction *fileSelect = new QAction(this);
    QString current = settings->value("Player1GBROM").toString();
    fileSelect->setText("Player 1 ROM: " + current);
    GB->addAction(fileSelect);
    connect(fileSelect, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB ROM File"), NULL, tr("GB ROM Files (*.gb *.gbc)"));
        if (!filename.isNull()) {
            settings->setValue("Player1GBROM", filename);
            QString current = filename;
            fileSelect->setText("Player 1 ROM: " + current);
        } });

    QAction *fileSelect2 = new QAction(this);
    current = settings->value("Player1GBRAM").toString();
    fileSelect2->setText("Player 1 SAV: " + current);
    GB->addAction(fileSelect2);
    connect(fileSelect2, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB RAM File"), NULL, tr("GB SAV Files (*.sav *.ram)"));
        if (!filename.isNull()) {
            settings->setValue("Player1GBRAM", filename);
            QString current = filename;
            fileSelect2->setText("Player 1 SAV: " + current);
        } });

    QAction *clearSelect = new QAction(this);
    clearSelect->setText("Clear Player 1 Selections");
    GB->addAction(clearSelect);
    connect(clearSelect, &QAction::triggered, [=]()
            {
        settings->remove("Player1GBROM");
        settings->remove("Player1GBRAM");
        fileSelect->setText("Player 1 ROM: ");
        fileSelect2->setText("Player 1 SAV: "); });
    GB->addSeparator();

    fileSelect = new QAction(this);
    current = settings->value("Player2GBROM").toString();
    fileSelect->setText("Player 2 ROM: " + current);
    GB->addAction(fileSelect);
    connect(fileSelect, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB ROM File"), NULL, tr("GB ROM Files (*.gb *.gbc)"));
        if (!filename.isNull()) {
            settings->setValue("Player2GBROM", filename);
            QString current = filename;
            fileSelect->setText("Player 2 ROM: " + current);
        } });

    fileSelect2 = new QAction(this);
    current = settings->value("Player2GBRAM").toString();
    fileSelect2->setText("Player 2 SAV: " + current);
    GB->addAction(fileSelect2);
    connect(fileSelect2, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB RAM File"), NULL, tr("GB SAV Files (*.sav *.ram)"));
        if (!filename.isNull()) {
            settings->setValue("Player2GBRAM", filename);
            QString current = filename;
            fileSelect2->setText("Player 2 SAV: " + current);
        } });

    clearSelect = new QAction(this);
    clearSelect->setText("Clear Player 2 Selections");
    GB->addAction(clearSelect);
    connect(clearSelect, &QAction::triggered, [=]()
            {
        settings->remove("Player2GBROM");
        settings->remove("Player2GBRAM");
        fileSelect->setText("Player 2 ROM: ");
        fileSelect2->setText("Player 2 SAV: "); });
    GB->addSeparator();

    fileSelect = new QAction(this);
    current = settings->value("Player3GBROM").toString();
    fileSelect->setText("Player 3 ROM: " + current);
    GB->addAction(fileSelect);
    connect(fileSelect, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB ROM File"), NULL, tr("GB ROM Files (*.gb *.gbc)"));
        if (!filename.isNull()) {
            settings->setValue("Player3GBROM", filename);
            QString current = filename;
            fileSelect->setText("Player 3 ROM: " + current);
        } });

    fileSelect2 = new QAction(this);
    current = settings->value("Player3GBRAM").toString();
    fileSelect2->setText("Player 3 SAV: " + current);
    GB->addAction(fileSelect2);
    connect(fileSelect2, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB RAM File"), NULL, tr("GB SAV Files (*.sav *.ram)"));
        if (!filename.isNull()) {
            settings->setValue("Player3GBRAM", filename);
            QString current = filename;
            fileSelect2->setText("Player 3 SAV: " + current);
        } });

    clearSelect = new QAction(this);
    clearSelect->setText("Clear Player 3 Selections");
    GB->addAction(clearSelect);
    connect(clearSelect, &QAction::triggered, [=]()
            {
        settings->remove("Player3GBROM");
        settings->remove("Player3GBRAM");
        fileSelect->setText("Player 3 ROM: ");
        fileSelect2->setText("Player 3 SAV: "); });
    GB->addSeparator();

    fileSelect = new QAction(this);
    current = settings->value("Player4GBROM").toString();
    fileSelect->setText("Player 4 ROM: " + current);
    GB->addAction(fileSelect);
    connect(fileSelect, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB ROM File"), NULL, tr("GB ROM Files (*.gb *.gbc)"));
        if (!filename.isNull()) {
            settings->setValue("Player4GBROM", filename);
            QString current = filename;
            fileSelect->setText("Player 4 ROM: " + current);
        } });

    fileSelect2 = new QAction(this);
    current = settings->value("Player4GBRAM").toString();
    fileSelect2->setText("Player 4 SAV: " + current);
    GB->addAction(fileSelect2);
    connect(fileSelect2, &QAction::triggered, [=]()
            {
        QString filename = QFileDialog::getOpenFileName(this,
            tr("GB RAM File"), NULL, tr("GB SAV Files (*.sav *.ram)"));
        if (!filename.isNull()) {
            settings->setValue("Player4GBRAM", filename);
            QString current = filename;
            fileSelect2->setText("Player 4 SAV: " + current);
        } });

    clearSelect = new QAction(this);
    clearSelect->setText("Clear Player 4 Selections");
    GB->addAction(clearSelect);
    connect(clearSelect, &QAction::triggered, [=]()
            {
        settings->remove("Player4GBROM");
        settings->remove("Player4GBRAM");
        fileSelect->setText("Player 4 ROM: ");
        fileSelect2->setText("Player 4 SAV: "); });
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
                                          ui(new Ui::MainWindow)
{
    verbose = 0;
    nogui = 0;
    run_test = 0;
    test_key_value = 225; // Shift/A
    ui->setupUi(this);
    this->setWindowTitle("simple64    build: " + QStringLiteral(GUI_VERSION).mid(0, 7));

    QString ini_path = QDir(QCoreApplication::applicationDirPath()).filePath("simple64-gui.ini");
    settings = new QSettings(ini_path, QSettings::IniFormat, this);

    if (!settings->isWritable())
        settings = new QSettings("simple64", "gui", this);

    if (!settings->contains("version") || settings->value("version").toInt() != SETTINGS_VER)
    {
        settings->clear();
        settings->setValue("version", SETTINGS_VER);
    }

    restoreGeometry(settings->value("geometry").toByteArray());
    restoreState(settings->value("windowState").toByteArray());

    QActionGroup *my_slots_group = new QActionGroup(this);
    QAction *my_slots[10];
    OpenRecent = new QMenu(this);
    QMenu *SaveSlot = new QMenu(this);
    OpenRecent->setTitle("Open Recent");
    SaveSlot->setTitle("Change Save Slot");
    ui->menuFile->insertMenu(ui->actionSave_State, OpenRecent);
    ui->menuFile->insertSeparator(ui->actionSave_State);
    ui->menuFile->insertMenu(ui->actionSave_State_To, SaveSlot);
    ui->menuFile->insertSeparator(ui->actionSave_State_To);
    for (int i = 0; i < 10; ++i)
    {
        my_slots[i] = new QAction(this);
        my_slots[i]->setCheckable(true);
        my_slots[i]->setText("Slot " + QString::number(i));
        my_slots[i]->setActionGroup(my_slots_group);
        SaveSlot->addAction(my_slots[i]);
        QAction *temp_slot = my_slots[i];
        connect(temp_slot, &QAction::triggered, [=](bool checked)
                {
            if (checked) {
                (*CoreDoCommand)(M64CMD_STATE_SET_SLOT, i, NULL);
            } });
    }

    updateOpenRecent();
    updateGB(ui);

    // migrate from old $CONFIG_PATH$ default
    if (settings->value("configDirPath").toString().startsWith("$"))
        settings->remove("configDirPath");

#ifdef CONFIG_DIR_PATH
    settings->setValue("configDirPath", CONFIG_DIR_PATH);
#endif

    updatePlugins();

    if (!settings->contains("volume"))
        settings->setValue("volume", 100);
    VolumeAction *volumeAction = new VolumeAction(tr("Volume"), this);
    connect(volumeAction->slider(), &QSlider::valueChanged, this, &MainWindow::volumeValueChanged);
    volumeAction->slider()->setValue(settings->value("volume").toInt());
    ui->menuEmulation->insertAction(ui->actionMute, volumeAction);

    coreLib = nullptr;
    gfxPlugin = nullptr;
    rspPlugin = nullptr;
    audioPlugin = nullptr;
    inputPlugin = nullptr;
    loadCoreLib();
    loadPlugins();

    if (coreLib)
    {
        m64p_handle coreConfigHandle;
        m64p_error res = (*ConfigOpenSection)("Core", &coreConfigHandle);
        if (res == M64ERR_SUCCESS)
        {
            int current_slot = (*ConfigGetParamInt)(coreConfigHandle, "CurrentStateSlot");
            my_slots[current_slot]->setChecked(true);
        }
    }

    setupDiscord();
    FPSLabel = new QLabel(this);
    statusBar()->addPermanentWidget(FPSLabel);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::updateApp()
{
#ifdef _AUTOUPDATE
    QString disable_update = qEnvironmentVariable("SIMPLE64_DISABLE_UPDATER");
    if (!disable_update.isEmpty())
        return;
#ifndef __APPLE__
    QNetworkAccessManager *updateManager = new QNetworkAccessManager(this);
    connect(updateManager, &QNetworkAccessManager::finished,
            this, &MainWindow::updateReplyFinished);

    updateManager->get(QNetworkRequest(QUrl("https://api.github.com/repos/simple64/simple64/releases/latest")));
#endif
#endif
}

void MainWindow::setupDiscord()
{
    QLibrary *discordLib = new QLibrary((QDir(QCoreApplication::applicationDirPath()).filePath("discord_game_sdk")), this);

    memset(&discord_app, 0, sizeof(discord_app));

    DiscordCreateParams params;
    DiscordCreateParamsSetDefault(&params);
    params.client_id = 770838334015930398LL;
    params.flags = DiscordCreateFlags_NoRequireDiscord;
    params.event_data = &discord_app;

    typedef EDiscordResult (*CreatePrototype)(DiscordVersion version, struct DiscordCreateParams *params, struct IDiscordCore **result);
    CreatePrototype createFunction = (CreatePrototype)discordLib->resolve("DiscordCreate");
    if (createFunction)
        createFunction(DISCORD_VERSION, &params, &discord_app.core);

    if (discord_app.core)
    {
        discord_app.activities = discord_app.core->get_activity_manager(discord_app.core);

        QTimer *timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, &MainWindow::discordCallback);
        timer->start(1000);
    }
}

struct Discord_Application *MainWindow::getDiscordApp()
{
    return &discord_app;
}

void MainWindow::updateDiscordActivity(struct DiscordActivity activity)
{
    if (discord_app.activities)
        discord_app.activities->update_activity(discord_app.activities, &activity, &discord_app, nullptr);
}

void MainWindow::clearDiscordActivity()
{
    if (discord_app.activities)
        discord_app.activities->clear_activity(discord_app.activities, &discord_app, nullptr);
}

void MainWindow::discordCallback()
{
    if (discord_app.core)
        discord_app.core->run_callbacks(discord_app.core);
}

void MainWindow::updateDownloadFinished(QNetworkReply *reply)
{
    if (!reply->error())
    {
        QTemporaryDir dir;
        dir.setAutoRemove(false);
        if (dir.isValid())
        {
            download_message->done(QDialog::Accepted);
            QString fullpath = dir.filePath(reply->url().fileName());
            QFile file(fullpath);
            file.open(QIODevice::WriteOnly);
            file.write(reply->readAll());
            file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
            file.close();
            QProcess process;
            process.setProgram(fullpath);
            QStringList arguments = {QCoreApplication::applicationDirPath()};
            process.setArguments(arguments);
            process.startDetached();
            reply->deleteLater();
            QCoreApplication::quit();
        }
    }
    reply->deleteLater();
}

void MainWindow::updateReplyFinished(QNetworkReply *reply)
{
    if (!reply->error())
    {
        QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll());
        QJsonObject json = json_doc.object();
        if (json.value("target_commitish").toString() != QString(GUI_VERSION))
        {
            QMessageBox::StandardButton reply;
            reply = QMessageBox::question(this, "Update Available", "A newer version is available, update?", QMessageBox::Yes | QMessageBox::No);
            if (reply == QMessageBox::Yes)
            {
                QNetworkAccessManager *updateManager = new QNetworkAccessManager(this);
                connect(updateManager, &QNetworkAccessManager::finished,
                        this, &MainWindow::updateDownloadFinished);
#ifdef _WIN32
                QNetworkRequest req(QUrl("https://github.com/simple64/simple64-updater/releases/latest/download/simple64-updater.exe"));
#else
                QNetworkRequest req(QUrl("https://github.com/simple64/simple64-updater/releases/latest/download/simple64-updater"));
#endif
                req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
                updateManager->get(req);
                download_message = new QMessageBox(this);
                download_message->setStandardButtons(QMessageBox::NoButton);
                download_message->setText("Downloading updater");
                download_message->show();
            }
        }
    }
    reply->deleteLater();
}

void MainWindow::volumeValueChanged(int value)
{
    if (value != settings->value("volume").toInt())
    {
        settings->setValue("volume", value);
        settings->sync();
        (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_AUDIO_VOLUME, &value);
    }
}

void MainWindow::setVerbose()
{
    verbose = 1;
}

void MainWindow::setNoGUI()
{
    nogui = 1;
    if (!menuBar()->isNativeMenuBar())
        menuBar()->hide();
    statusBar()->hide();
}

int MainWindow::getNoGUI()
{
    return nogui;
}

void MainWindow::setTest(int value)
{
    run_test = value;
}

int MainWindow::getTest()
{
    return run_test;
}

int MainWindow::getVerbose()
{
    return verbose;
}

void MainWindow::resizeMainWindow(int Width, int Height)
{
    QSize size = this->size();
    Height += (menuBar()->isNativeMenuBar() ? 0 : ui->menuBar->height()) + ui->statusBar->height();
    if (size.width() != Width || size.height() != Height)
        resize(Width, Height);
}

void MainWindow::toggleFS(int force)
{
    if (coreLib == nullptr)
        return;

    int response = M64VIDEO_NONE;
    if (force == M64VIDEO_NONE)
        (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_VIDEO_MODE, &response);
    if (response == M64VIDEO_WINDOWED || force == M64VIDEO_FULLSCREEN)
    {
        if (!menuBar()->isNativeMenuBar())
            menuBar()->hide();
        statusBar()->hide();
        showFullScreen();
    }
    else if (response == M64VIDEO_FULLSCREEN || force == M64VIDEO_WINDOWED)
    {
        if (!nogui)
        {
            if (!menuBar()->isNativeMenuBar())
                menuBar()->show();
            statusBar()->show();
        }
        showNormal();
    }
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    stopGame();

    closePlugins();
    closeCoreLib();

    if (discord_app.activities)
        discord_app.activities->clear_activity(discord_app.activities, &discord_app, nullptr);
    if (discord_app.core)
    {
        discord_app.core->run_callbacks(discord_app.core);
        discord_app.core->destroy(discord_app.core);
    }

    settings->setValue("geometry", saveGeometry());
    settings->setValue("windowState", saveState());

    event->accept();
}

void MainWindow::updateOpenRecent()
{
    OpenRecent->clear();
    QAction *recent[RECENT_SIZE];
    QStringList list = settings->value("RecentROMs2").toStringList();
    if (list.isEmpty())
    {
        OpenRecent->setEnabled(false);
        return;
    }

    OpenRecent->setEnabled(true);
    for (int i = 0; i < list.size() && i < RECENT_SIZE; ++i)
    {
        recent[i] = new QAction(this);
        recent[i]->setText(list.at(i));
        OpenRecent->addAction(recent[i]);
        QAction *temp_recent = recent[i];
        connect(temp_recent, &QAction::triggered, [=]()
                { openROM(list.at(i), "", 0, 0, QJsonObject()); });
    }
    OpenRecent->addSeparator();

    QAction *clearRecent = new QAction(this);
    clearRecent->setText("Clear List");
    OpenRecent->addAction(clearRecent);
    connect(clearRecent, &QAction::triggered, [=]()
            {
        settings->remove("RecentROMs2");
        updateOpenRecent(); });
}

void MainWindow::showMessage(QString message)
{
    QMessageBox *msgBox = new QMessageBox(this);
    msgBox->setText(message);
    msgBox->show();
}

void MainWindow::createVkWindow(QVulkanInstance *vulkan_inst)
{
    my_window = new VkWindow;
    my_window->setVulkanInstance(vulkan_inst);

    QWidget *container = QWidget::createWindowContainer(my_window, this);
    container->setFocusPolicy(Qt::StrongFocus);

    my_window->setCursor(Qt::BlankCursor);

    setCentralWidget(container);

    my_window->installEventFilter(&keyPressFilter);
    this->installEventFilter(&keyPressFilter);
    frame_timer = new QTimer(this);
    connect(frame_timer, &QTimer::timeout, this, &MainWindow::updateFrameCount);
    frame_timer->start(1000);
    frame_count = 0;
}

void MainWindow::deleteVkWindow()
{
    QWidget *container = new QWidget(this);
    setCentralWidget(container);
    delete my_window;
    frame_timer->stop();
    frame_timer->deleteLater();
    FPSLabel->clear();
}

void MainWindow::killThread()
{
    printf("Application hung, exiting\n");
    exit(1); // Force kill the application, it's stuck
}

void MainWindow::stopGame()
{
    if (!coreLib)
        return;

    int response;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_EMU_STATE, &response);
    if (response != M64EMU_STOPPED)
    {
        (*CoreDoCommand)(M64CMD_STOP, 0, NULL);
        kill_timer = new QTimer(this);
        kill_timer->setInterval(5000);
        kill_timer->setSingleShot(true);
        connect(kill_timer, &QTimer::timeout, this, &MainWindow::killThread);
        kill_timer->start();
        while (workerThread->isRunning())
            QCoreApplication::processEvents();
        kill_timer->stop();
        kill_timer->deleteLater();
    }
}

void MainWindow::resetCore()
{
    closePlugins();
    closeCoreLib();
    loadCoreLib();
    loadPlugins();
}

void MainWindow::openROM(QString filename, QString netplay_ip, int netplay_port, int netplay_player, QJsonObject cheats)
{
    stopGame();

    logViewer.clearLog();

    resetCore();

    workerThread = new WorkerThread(netplay_ip, netplay_port, netplay_player, cheats, this);
    workerThread->setFileName(filename);

    QStringList list;
    if (settings->contains("RecentROMs2"))
        list = settings->value("RecentROMs2").toStringList();
    list.removeAll(filename);
    list.prepend(filename);
    if (list.size() > RECENT_SIZE)
        list.removeLast();
    settings->setValue("RecentROMs2", list);
    updateOpenRecent();

    workerThread->start();
}

void MainWindow::on_actionOpen_ROM_triggered()
{
    QString filename = QFileDialog::getOpenFileName(this,
                                                    tr("Open ROM"), settings->value("ROMdir").toString(), tr("ROM Files (*.n64 *.N64 *.z64 *.Z64 *.v64 *.V64 *.rom *.ROM *.zip *.ZIP *.7z)"));
    if (!filename.isNull())
    {
        QFileInfo info(filename);
        settings->setValue("ROMdir", info.absoluteDir().absolutePath());
        openROM(filename, "", 0, 0, QJsonObject());
    }
}

void MainWindow::on_actionPlugin_Paths_triggered()
{
    SettingsDialog *settings = new SettingsDialog(this);
    settings->show();
}

void MainWindow::on_actionStop_Game_triggered()
{
    stopGame();
}

void MainWindow::on_actionExit_triggered()
{
    this->close();
}

void MainWindow::on_actionPlugin_Settings_triggered()
{
    PluginDialog *settings = new PluginDialog(this);
    settings->show();
}

void MainWindow::on_actionPause_Game_triggered()
{
    int response;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_EMU_STATE, &response);
    if (response == M64EMU_RUNNING)
        (*CoreDoCommand)(M64CMD_PAUSE, 0, NULL);
    else if (response == M64EMU_PAUSED)
        (*CoreDoCommand)(M64CMD_RESUME, 0, NULL);
}

void MainWindow::on_actionMute_triggered()
{
    int response;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_AUDIO_MUTE, &response);
    if (response == 0)
    {
        response = 1;
        (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_AUDIO_MUTE, &response);
    }
    else if (response == 1)
    {
        response = 0;
        (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_AUDIO_MUTE, &response);
    }
}

void MainWindow::on_actionHard_Reset_triggered()
{
    (*CoreDoCommand)(M64CMD_RESET, 1, NULL);
}

void MainWindow::on_actionSoft_Reset_triggered()
{
    (*CoreDoCommand)(M64CMD_RESET, 0, NULL);
}

void MainWindow::on_actionTake_Screenshot_triggered()
{
    (*CoreDoCommand)(M64CMD_TAKE_NEXT_SCREENSHOT, 0, NULL);
}

void MainWindow::on_actionSave_State_triggered()
{
    (*CoreDoCommand)(M64CMD_STATE_SAVE, 1, NULL);
}

void MainWindow::on_actionLoad_State_triggered()
{
    (*CoreDoCommand)(M64CMD_STATE_LOAD, 1, NULL);
}

void MainWindow::on_actionToggle_Fullscreen_triggered()
{
    int response;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_VIDEO_MODE, &response);
    if (response == M64VIDEO_WINDOWED)
    {
        response = M64VIDEO_FULLSCREEN;
        (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_VIDEO_MODE, &response);
    }
    else if (response == M64VIDEO_FULLSCREEN)
    {
        response = M64VIDEO_WINDOWED;
        (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_VIDEO_MODE, &response);
    }
}

void MainWindow::on_actionCheats_triggered()
{
    QString gameName = getCheatGameName();
    if (!gameName.isEmpty())
    {
        CheatsDialog *cheats = new CheatsDialog(gameName, this);
        cheats->show();
    }
    else
    {
        QMessageBox msgBox;
        msgBox.setText("Game must be running.");
        msgBox.exec();
    }
}

void MainWindow::on_actionSave_State_To_triggered()
{
    QString filename = QFileDialog::getSaveFileName(this,
                                                    tr("Save State File"), NULL, NULL);
    if (!filename.isNull())
    {
        if (!filename.contains(".st"))
            filename.append(".state");
        (*CoreDoCommand)(M64CMD_STATE_SAVE, 1, filename.toUtf8().data());
    }
}

void MainWindow::on_actionLoad_State_From_triggered()
{
    QString filename = QFileDialog::getOpenFileName(this,
                                                    tr("Open Save State"), NULL, tr("State Files (*.st* *.pj*)"));
    if (!filename.isNull())
    {
        (*CoreDoCommand)(M64CMD_STATE_LOAD, 1, filename.toUtf8().data());
    }
}

void MainWindow::on_actionController_Configuration_triggered()
{
    if (!coreLib)
        return;

    int response;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_EMU_STATE, &response);
    if (response == M64EMU_STOPPED)
        resetCore();

    typedef void (*Config_Func)();
    Config_Func PluginConfig = (Config_Func)osal_dynlib_getproc(inputPlugin, "PluginConfig");
    if (PluginConfig)
        PluginConfig();
}

void MainWindow::on_actionHotkey_Configuration_triggered()
{
    HotkeyDialog *settings = new HotkeyDialog(this);
    settings->show();
}

void MainWindow::on_actionToggle_Speed_Limiter_triggered()
{
    int value;
    (*CoreDoCommand)(M64CMD_CORE_STATE_QUERY, M64CORE_SPEED_LIMITER, &value);
    value = !value;
    (*CoreDoCommand)(M64CMD_CORE_STATE_SET, M64CORE_SPEED_LIMITER, &value);
}

void MainWindow::on_actionView_Log_triggered()
{
    logViewer.show();
}

void MainWindow::on_actionCreate_Room_triggered()
{
    CreateRoom *createRoom = new CreateRoom(this);
    createRoom->show();
}

void MainWindow::on_actionJoin_Room_triggered()
{
    JoinRoom *joinRoom = new JoinRoom(this);
    joinRoom->show();
}

void MainWindow::on_actionSupport_on_Patreon_triggered()
{
    QDesktopServices::openUrl(QUrl("https://www.patreon.com/loganmc10"));
}

void MainWindow::on_actionSupport_on_GithubSponser_triggered()
{
    QDesktopServices::openUrl(QUrl("https://github.com/sponsors/loganmc10"));
}

void MainWindow::on_actionOpen_Discord_Channel_triggered()
{
    QDesktopServices::openUrl(QUrl("https://discord.gg/tsR3RtYynZ"));
}

WorkerThread *MainWindow::getWorkerThread()
{
    return workerThread;
}

VkWindow *MainWindow::getVkWindow()
{
    return my_window;
}

QSettings *MainWindow::getSettings()
{
    return settings;
}

LogViewer *MainWindow::getLogViewer()
{
    return &logViewer;
}

m64p_dynlib_handle MainWindow::getAudioPlugin()
{
    return audioPlugin;
}

m64p_dynlib_handle MainWindow::getRspPlugin()
{
    return rspPlugin;
}

m64p_dynlib_handle MainWindow::getInputPlugin()
{
    return inputPlugin;
}

m64p_dynlib_handle MainWindow::getGfxPlugin()
{
    return gfxPlugin;
}

void MainWindow::closeCoreLib()
{
    if (coreLib != nullptr)
    {
        (*ConfigSaveFile)();
        (*CoreShutdown)();
        osal_dynlib_close(coreLib);
        coreLib = nullptr;
    }
}

void MainWindow::loadCoreLib()
{
#ifdef CORE_LIBRARY_PATH
    QString corePath = CORE_LIBRARY_PATH;
#else
    QString corePath = QCoreApplication::applicationDirPath();
#endif
    m64p_error res = osal_dynlib_open(&coreLib, QDir(corePath).filePath(OSAL_DEFAULT_DYNLIB_FILENAME).toUtf8().constData());

    if (res != M64ERR_SUCCESS)
    {
        QMessageBox msgBox;
        msgBox.setText("Failed to load core library");
        msgBox.exec();
        return;
    }

    CoreStartup = (ptr_CoreStartup)osal_dynlib_getproc(coreLib, "CoreStartup");
    CoreShutdown = (ptr_CoreShutdown)osal_dynlib_getproc(coreLib, "CoreShutdown");
    CoreDoCommand = (ptr_CoreDoCommand)osal_dynlib_getproc(coreLib, "CoreDoCommand");
    CoreAttachPlugin = (ptr_CoreAttachPlugin)osal_dynlib_getproc(coreLib, "CoreAttachPlugin");
    CoreDetachPlugin = (ptr_CoreDetachPlugin)osal_dynlib_getproc(coreLib, "CoreDetachPlugin");
    CoreOverrideVidExt = (ptr_CoreOverrideVidExt)osal_dynlib_getproc(coreLib, "CoreOverrideVidExt");

    ConfigGetUserConfigPath = (ptr_ConfigGetUserConfigPath)osal_dynlib_getproc(coreLib, "ConfigGetUserConfigPath");
    ConfigSaveFile = (ptr_ConfigSaveFile)osal_dynlib_getproc(coreLib, "ConfigSaveFile");
    ConfigGetParameterHelp = (ptr_ConfigGetParameterHelp)osal_dynlib_getproc(coreLib, "ConfigGetParameterHelp");
    ConfigGetParamInt = (ptr_ConfigGetParamInt)osal_dynlib_getproc(coreLib, "ConfigGetParamInt");
    ConfigGetParamFloat = (ptr_ConfigGetParamFloat)osal_dynlib_getproc(coreLib, "ConfigGetParamFloat");
    ConfigGetParamBool = (ptr_ConfigGetParamBool)osal_dynlib_getproc(coreLib, "ConfigGetParamBool");
    ConfigGetParamString = (ptr_ConfigGetParamString)osal_dynlib_getproc(coreLib, "ConfigGetParamString");
    ConfigSetParameter = (ptr_ConfigSetParameter)osal_dynlib_getproc(coreLib, "ConfigSetParameter");
    ConfigDeleteSection = (ptr_ConfigDeleteSection)osal_dynlib_getproc(coreLib, "ConfigDeleteSection");
    ConfigOpenSection = (ptr_ConfigOpenSection)osal_dynlib_getproc(coreLib, "ConfigOpenSection");
    ConfigSaveSection = (ptr_ConfigSaveSection)osal_dynlib_getproc(coreLib, "ConfigSaveSection");
    ConfigListParameters = (ptr_ConfigListParameters)osal_dynlib_getproc(coreLib, "ConfigListParameters");
    ConfigGetSharedDataFilepath = (ptr_ConfigGetSharedDataFilepath)osal_dynlib_getproc(coreLib, "ConfigGetSharedDataFilepath");

    CoreAddCheat = (ptr_CoreAddCheat)osal_dynlib_getproc(coreLib, "CoreAddCheat");

    QString qtConfigDir = settings->value("configDirPath").toString();

    if (!qtConfigDir.isEmpty())
        (*CoreStartup)(CORE_API_VERSION, qtConfigDir.toUtf8().constData() /*Config dir*/, QCoreApplication::applicationDirPath().toUtf8().constData(), (char *)"Core", DebugCallback, NULL, NULL);
    else
        (*CoreStartup)(CORE_API_VERSION, NULL /*Config dir*/, QCoreApplication::applicationDirPath().toUtf8().constData(), (char *)"Core", DebugCallback, NULL, NULL);

    CoreOverrideVidExt(&vidExtFunctions);
}

void MainWindow::closePlugins()
{
    ptr_PluginShutdown PluginShutdown;

    if (gfxPlugin != nullptr)
    {
        PluginShutdown = (ptr_PluginShutdown)osal_dynlib_getproc(gfxPlugin, "PluginShutdown");
        (*PluginShutdown)();
        osal_dynlib_close(gfxPlugin);
        gfxPlugin = nullptr;
    }
    if (audioPlugin != nullptr)
    {
        PluginShutdown = (ptr_PluginShutdown)osal_dynlib_getproc(audioPlugin, "PluginShutdown");
        (*PluginShutdown)();
        osal_dynlib_close(audioPlugin);
        audioPlugin = nullptr;
    }
    if (inputPlugin != nullptr)
    {
        PluginShutdown = (ptr_PluginShutdown)osal_dynlib_getproc(inputPlugin, "PluginShutdown");
        (*PluginShutdown)();
        osal_dynlib_close(inputPlugin);
        inputPlugin = nullptr;
    }
    if (rspPlugin != nullptr)
    {
        PluginShutdown = (ptr_PluginShutdown)osal_dynlib_getproc(rspPlugin, "PluginShutdown");
        (*PluginShutdown)();
        osal_dynlib_close(rspPlugin);
        rspPlugin = nullptr;
    }
}

typedef void (*ptr_VolumeSetLevel)(int level);
void MainWindow::loadPlugins()
{
    if (coreLib == nullptr)
        return;

    ptr_VolumeSetLevel VolumeSetLevel;
    ptr_PluginStartup PluginStartup;
    ptr_PluginGetVersion PluginGetVersion;
    m64p_error res;
    QMessageBox msgBox;
#ifdef PLUGIN_DIR_PATH
    QString pluginPath = PLUGIN_DIR_PATH;
#else
    QString pluginPath = QCoreApplication::applicationDirPath();
#endif
    QString file_path;
    QString plugin_path;

    file_path = "simple64-video-parallel";
    file_path += OSAL_DLL_EXTENSION;
    plugin_path = QDir(pluginPath).filePath(file_path);

    res = osal_dynlib_open(&gfxPlugin, plugin_path.toUtf8().constData());
    if (res != M64ERR_SUCCESS)
    {
        msgBox.setText("Failed to load video plugin");
        msgBox.exec();
        return;
    }

    PluginGetVersion = (ptr_PluginGetVersion)osal_dynlib_getproc(gfxPlugin, "PluginGetVersion");
    const char *pluginName;
    (*PluginGetVersion)(NULL, NULL, NULL, &pluginName, NULL);
    if (strcmp("parallel", pluginName))
    {
        osal_dynlib_close(gfxPlugin);
        gfxPlugin = nullptr;
        msgBox.setText("Incorrect GFX plugin");
        msgBox.exec();
        return;
    }

    PluginStartup = (ptr_PluginStartup)osal_dynlib_getproc(gfxPlugin, "PluginStartup");
    (*PluginStartup)(coreLib, (char *)"Video", DebugCallback);

    file_path = "simple64-audio-sdl2";
    file_path += OSAL_DLL_EXTENSION;
    plugin_path = QDir(pluginPath).filePath(file_path);

    res = osal_dynlib_open(&audioPlugin, plugin_path.toUtf8().constData());
    if (res != M64ERR_SUCCESS)
    {
        msgBox.setText("Failed to load audio plugin");
        msgBox.exec();
        return;
    }

    PluginGetVersion = (ptr_PluginGetVersion)osal_dynlib_getproc(audioPlugin, "PluginGetVersion");
    (*PluginGetVersion)(NULL, NULL, NULL, &pluginName, NULL);
    if (strcmp("simple64 SDL2 Audio Plugin", pluginName))
    {
        osal_dynlib_close(audioPlugin);
        audioPlugin = nullptr;
        msgBox.setText("Incorrect Audio plugin");
        msgBox.exec();
        return;
    }

    PluginStartup = (ptr_PluginStartup)osal_dynlib_getproc(audioPlugin, "PluginStartup");
    (*PluginStartup)(coreLib, (char *)"Audio", DebugCallback);
    int volume = 100;
    if (settings->contains("volume"))
        volume = settings->value("volume").toInt();
    VolumeSetLevel = (ptr_VolumeSetLevel)osal_dynlib_getproc(audioPlugin, "VolumeSetLevel");
    (*VolumeSetLevel)(volume);

    res = osal_dynlib_open(&inputPlugin, QDir(pluginPath).filePath(settings->value("inputPlugin").toString()).toUtf8().constData());
    if (res != M64ERR_SUCCESS)
    {
        msgBox.setText("Failed to load input plugin");
        msgBox.exec();
        return;
    }
    PluginStartup = (ptr_PluginStartup)osal_dynlib_getproc(inputPlugin, "PluginStartup");
    if (settings->value("inputPlugin").toString().contains("-qt"))
        (*PluginStartup)(coreLib, this, DebugCallback);
    else
        (*PluginStartup)(coreLib, (char *)"Input", DebugCallback);

    file_path = "simple64-rsp-parallel";
    file_path += OSAL_DLL_EXTENSION;
    plugin_path = QDir(pluginPath).filePath(file_path);

    res = osal_dynlib_open(&rspPlugin, plugin_path.toUtf8().constData());
    if (res != M64ERR_SUCCESS)
    {
        msgBox.setText("Failed to load rsp plugin");
        msgBox.exec();
        return;
    }

    PluginGetVersion = (ptr_PluginGetVersion)osal_dynlib_getproc(rspPlugin, "PluginGetVersion");
    (*PluginGetVersion)(NULL, NULL, NULL, &pluginName, NULL);
    if (strcmp("ParaLLEl RSP", pluginName))
    {
        osal_dynlib_close(rspPlugin);
        rspPlugin = nullptr;
        msgBox.setText("Incorrect RSP plugin");
        msgBox.exec();
        return;
    }

    PluginStartup = (ptr_PluginStartup)osal_dynlib_getproc(rspPlugin, "PluginStartup");
    (*PluginStartup)(coreLib, (char *)"RSP", DebugCallback);
}

m64p_dynlib_handle MainWindow::getCoreLib()
{
    return coreLib;
}

void MainWindow::addFrameCount()
{
    ++frame_count;
}

void MainWindow::simulateInput()
{
    if (run_test % 2)
        (*CoreDoCommand)(M64CMD_SEND_SDL_KEYDOWN, test_key_value, NULL);
    else
    {
        (*CoreDoCommand)(M64CMD_SEND_SDL_KEYUP, test_key_value, NULL);
        if (test_key_value == 40)
            test_key_value = 225; // Enter/Start
        else
            test_key_value = 40;
    }
    printf("%u\n", frame_count);
    if (run_test == 1)
    {
        if (frame_count == 0)
            abort();
        else
            (*CoreDoCommand)(M64CMD_STOP, 0, NULL);
    }
    --run_test;
}

void MainWindow::updateFrameCount()
{
    if (run_test)
        simulateInput();
    QString FPS = QString("%1 FPS").arg(frame_count);
    if (m_cheatsEnabled)
    {
        FPS.prepend("Cheats Enabled    ");
    }
    FPSLabel->setText(FPS);
    frame_count = 0;
}

void MainWindow::setCheats(QJsonObject cheatsData, bool netplay)
{
    if (!netplay)
    {
        QString gameName = getCheatGameName();
        QJsonObject gameData = loadCheatData(gameName);
        cheatsData = getCheatsFromSettings(gameName, gameData);
    }
    m_cheatsEnabled = loadCheats(cheatsData);
}
