/*
 * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
 *  Copyright (C) 2020 Rosalie Wanders <rosalie@mailbox.org>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3.
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
#include "MainDialog.hpp"
#include "Widget/ControllerWidget.hpp"
#include "Utilities/QtKeyToSdl2Key.hpp"

#include <RMG-Core/Core.hpp>

#include <SDL.h>
#include <QTimer>

using namespace UserInterface;

MainDialog::MainDialog(QWidget* parent, Thread::SDLThread* sdlThread, bool romConfig) : QDialog(parent)
{
    this->setupUi(this);
    this->setWindowIcon(QIcon(":Resource/RMG.png"));

    // setup SDL thread
    this->sdlThread = sdlThread;
    connect(this->sdlThread, &Thread::SDLThread::OnInputDeviceFound, this,
        &MainDialog::on_SDLThread_DeviceFound);
    connect(this->sdlThread, &Thread::SDLThread::OnDeviceSearchFinished, this,
        &MainDialog::on_SDLThread_DeviceSearchFinished);

    // setup EventFilter
    this->eventFilter = new EventFilter(this);
    connect(this->eventFilter, &EventFilter::on_EventFilter_KeyPressed, this,
            &MainDialog::on_EventFilter_KeyPressed);
    connect(this->eventFilter, &EventFilter::on_EventFilter_KeyReleased, this,
            &MainDialog::on_EventFilter_KeyReleased);

    // each tab needs its own ControllerWidget
    for (int i = 0; i < this->tabWidget->count(); i++)
    {
        Widget::ControllerWidget* widget = new Widget::ControllerWidget(this, this->eventFilter);
        widget->SetOnlyLoadGameProfile(romConfig);
        widget->SetSettingsSection("Player " + QString::number(i + 1), "Rosalie's Mupen GUI - Input Plugin Profile " + QString::number(i));
        widget->LoadSettings();
        this->tabWidget->widget(i)->layout()->addWidget(widget);
        controllerWidgets.push_back(widget);
        connect(widget, &Widget::ControllerWidget::CurrentInputDeviceChanged, this,
            &MainDialog::on_ControllerWidget_CurrentInputDeviceChanged);
        connect(widget, &Widget::ControllerWidget::RefreshInputDevicesButtonClicked, this,
            &MainDialog::on_ControllerWidget_RefreshInputDevicesButtonClicked);
        connect(widget, &Widget::ControllerWidget::UserProfileAdded, this,
            &MainDialog::on_ControllerWidget_UserProfileAdded);
        connect(widget, &Widget::ControllerWidget::UserProfileRemoved, this,
            &MainDialog::on_ControllerWidget_UserProfileRemoved);
    }

    // always add keyboard device
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->AddInputDevice("None",      (int)InputDeviceType::None);
        controllerWidget->AddInputDevice("Automatic", (int)InputDeviceType::Automatic);
        controllerWidget->AddInputDevice("Keyboard",  (int)InputDeviceType::Keyboard);
        controllerWidget->SetInitialized(true);
    }

    // fill device list at least once
    this->on_ControllerWidget_RefreshInputDevicesButtonClicked();

    this->inputPollTimer = new QTimer(this);
    connect(this->inputPollTimer, &QTimer::timeout, this, &MainDialog::on_InputPollTimer_triggered);
    this->inputPollTimer->start(50);
}

MainDialog::~MainDialog()
{
    this->closeInputDevice();
}

void MainDialog::addInputDevice(QString deviceName, int deviceNum)
{
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->AddInputDevice(deviceName, deviceNum);
    }
}

void MainDialog::removeInputDevice(QString deviceName, int deviceNum)
{
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->RemoveInputDevice(deviceName, deviceNum);
    }
}

void MainDialog::openInputDevice(QString deviceName, int deviceNum)
{
    SDL_JoystickID joystickId;
    Widget::ControllerWidget* controllerWidget;
    controllerWidget = this->controllerWidgets.at(this->tabWidget->currentIndex());

    // we don't need to open a keyboard
    if (deviceNum == (int)InputDeviceType::None ||
        deviceNum == (int)InputDeviceType::Keyboard)
    {
        this->currentDeviceName = "";
        this->currentDeviceNum  = deviceNum;
        controllerWidget->SetCurrentJoystickID(this->currentDeviceNum);
        return;
    }

    // handle automatic mode
    if (deviceNum == (int)InputDeviceType::Automatic)
    {
        int currentIndex = this->tabWidget->currentIndex();
        if (currentIndex < this->inputDeviceList.size())
        { // use device when there's one
            deviceNum = this->inputDeviceList.at(currentIndex).deviceNum;
        }
        else
        { // no device found, fallback to keyboard
            this->currentDeviceName = "";
            this->currentDeviceNum  = (int)InputDeviceType::Keyboard;
            controllerWidget->SetCurrentJoystickID(this->currentDeviceNum);
            return;
        }
    }

    if (SDL_IsGameController(deviceNum) == SDL_TRUE)
    {
        this->currentJoystick = nullptr;
        this->currentController = SDL_GameControllerOpen(deviceNum);
    }
    else
    {
        this->currentJoystick = SDL_JoystickOpen(deviceNum);
        this->currentController = nullptr;
    }

    this->currentDeviceNum = deviceNum;
    joystickId = SDL_JoystickGetDeviceInstanceID(deviceNum);
    controllerWidget->SetCurrentJoystickID(joystickId);
    controllerWidget->SetIsCurrentJoystickGameController(currentController != nullptr);
}

void MainDialog::closeInputDevice()
{
    if (this->currentJoystick != nullptr)
    {
        SDL_JoystickClose(this->currentJoystick);
        this->currentJoystick = nullptr;
    }

    if (this->currentController != nullptr)
    {
        SDL_GameControllerClose(this->currentController);
        this->currentController = nullptr;
    }
}

void MainDialog::on_InputPollTimer_triggered()
{
    Widget::ControllerWidget* controllerWidget;
    controllerWidget = controllerWidgets.at(this->tabWidget->currentIndex());

    // if the current controller widget
    // is disabled (by the user), 
    // we don't need to do anything
    if (!controllerWidget->IsPluggedIn())
    {
        this->sdlThread->SetAction(SDLThreadAction::None);
        return;
    }

    // verify/update the SDL thread action state
    switch (this->sdlThread->GetCurrentAction())
    {
        case SDLThreadAction::GetInputDevices:
            // don't do anything when we're querying the input devices
            return;
        case SDLThreadAction::None:
            // when the SDL thread is doing nothing, pump the sdl events instead
            sdlThread->SetAction(SDLThreadAction::SDLPumpEvents);
            break;
        default:
            break;
    }

    // check if controller has been disconnected,
    // if so, keep trying to re-open it
    if ((this->currentJoystick != nullptr && !SDL_JoystickGetAttached(this->currentJoystick)) ||
        (this->currentController != nullptr && !SDL_GameControllerGetAttached(this->currentController)))
    {
        this->closeInputDevice();
        this->openInputDevice(this->currentDeviceName, this->currentDeviceNum);
    }

    // process SDL events
    SDL_Event event;
    while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, 0, SDL_LASTEVENT) == 1)
    {
        controllerWidget->on_MainDialog_SdlEvent(&event);
    }

    controllerWidget->on_MainDialog_SdlEventPollFinished();
}

void MainDialog::on_ControllerWidget_CurrentInputDeviceChanged(ControllerWidget* widget, QString deviceName, int deviceNum)
{
    Widget::ControllerWidget* currentWidget;
    currentWidget = controllerWidgets.at(this->tabWidget->currentIndex());

    // ignore input device changes
    // when it's not the current controllerwidget
    if (widget != currentWidget)
    {
        return;
    }

    this->closeInputDevice();

    // only open device,
    // when needed
    if (deviceNum != (int)InputDeviceType::None &&
        deviceNum != (int)InputDeviceType::Keyboard)
    {
        this->openInputDevice(deviceName, deviceNum);
    }
}

void MainDialog::on_ControllerWidget_RefreshInputDevicesButtonClicked()
{
    if (this->updatingDeviceList)
    {
        return;
    }

    this->updatingDeviceList = true;
    this->inputDeviceList.clear();
    this->sdlThread->SetAction(SDLThreadAction::GetInputDevices);
}

void MainDialog::on_ControllerWidget_UserProfileAdded(QString name, QString section)
{
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->AddUserProfile(name, section);
    }
}

void MainDialog::on_ControllerWidget_UserProfileRemoved(QString name, QString section)
{
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->RemoveUserProfile(name, section);
    }
}

void MainDialog::on_tabWidget_currentChanged(int index)
{
    QString deviceName;
    int     deviceNum;
    Widget::ControllerWidget* controllerWidget;

    // save previous tab's user profile
    if (previousTabWidgetIndex != -1 &&
        previousTabWidgetIndex != index)
    {
        controllerWidget = controllerWidgets.at(previousTabWidgetIndex);
        controllerWidget->SaveUserProfileSettings();
    }

    // update previous index
    previousTabWidgetIndex = index;

    // prepare switched tab
    controllerWidget = controllerWidgets.at(index);
    controllerWidget->ClearControllerImage();

    this->closeInputDevice();

    // re-load user profile settings
    controllerWidget->LoadUserProfileSettings();

    // retrieve current input device
    controllerWidget->GetCurrentInputDevice(deviceName, deviceNum);

    // only open device when needed
    if (deviceNum != (int)InputDeviceType::None &&
        deviceNum != (int)InputDeviceType::Keyboard)
    {
        this->openInputDevice(deviceName, deviceNum);
    }
}

void MainDialog::on_SDLThread_DeviceFound(QString deviceName, int deviceNum)
{
    inputDevice_t inputDevice = {deviceName, deviceNum};
    this->inputDeviceList.append(inputDevice);
}

void MainDialog::on_SDLThread_DeviceSearchFinished(void)
{
    // compare inputDeviceList with oldInputDeviceList
    // and signal the changes
    for (auto& inputDevice : this->inputDeviceList)
    {
        if (!this->oldInputDeviceList.contains(inputDevice))
        {
            this->addInputDevice(inputDevice.deviceName, inputDevice.deviceNum);
        }
    }
    for (auto& inputDevice : this->oldInputDeviceList)
    {
        if (!this->inputDeviceList.contains(inputDevice))
        {
            this->removeInputDevice(inputDevice.deviceName, inputDevice.deviceNum);
        }
    }

    // update old device list
    this->oldInputDeviceList = this->inputDeviceList;

    // we can refresh safely again
    this->updatingDeviceList = false;

    // device search has been completed,
    // signal that each widget can check device settings
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->CheckInputDeviceSettings();
    }
}

void MainDialog::on_EventFilter_KeyPressed(QKeyEvent *event)
{
    int key = Utilities::QtKeyToSdl2Key(event->key());
    int mod = Utilities::QtModKeyToSdl2ModKey(event->modifiers());

    SDL_KeyboardEvent keyboardEvent;
    keyboardEvent.state = SDL_PRESSED;
    keyboardEvent.type = SDL_KEYDOWN;
    keyboardEvent.keysym.scancode = (SDL_Scancode)key;
    keyboardEvent.keysym.sym = (SDL_Keycode)key;
    keyboardEvent.keysym.mod = mod;

    SDL_Event sdlEvent;
    sdlEvent.key = keyboardEvent;
    sdlEvent.type = SDL_KEYDOWN;

    SDL_PeepEvents(&sdlEvent, 1, SDL_ADDEVENT, 0, 0);
}

void MainDialog::on_EventFilter_KeyReleased(QKeyEvent *event)
{
    int key = Utilities::QtKeyToSdl2Key(event->key());
    int mod = Utilities::QtModKeyToSdl2ModKey(event->modifiers());

    SDL_KeyboardEvent keyboardEvent;
    keyboardEvent.state = SDL_RELEASED;
    keyboardEvent.type = SDL_KEYUP;
    keyboardEvent.keysym.scancode = (SDL_Scancode)key;
    keyboardEvent.keysym.sym = (SDL_Keycode)key;
    keyboardEvent.keysym.mod = mod;

    SDL_Event sdlEvent;
    sdlEvent.key = keyboardEvent;
    sdlEvent.type = SDL_KEYUP;

    SDL_PeepEvents(&sdlEvent, 1, SDL_ADDEVENT, 0, 0);
}

void MainDialog::accept(void)
{
    Widget::ControllerWidget* controllerWidget;
    int currentIndex = this->tabWidget->currentIndex();

    for (int i = 0; i < this->controllerWidgets.count(); i++)
    {
        if (i != currentIndex)
        {
            controllerWidget = this->controllerWidgets.at(i);
            controllerWidget->SaveSettings();
        }
    }

    // the widget which has focus should be saved last
    controllerWidget = this->controllerWidgets.at(currentIndex);
    controllerWidget->SaveSettings();

    CoreSettingsSave();

    QDialog::accept();
}

void MainDialog::reject(void)
{
    for (auto& controllerWidget : this->controllerWidgets)
    {
        controllerWidget->RevertSettings();
    }

    CoreSettingsSave();

    QDialog::reject();
}
