//  SPDX-License-Identifier: MIT
//
//  ES-DE Frontend
//  GuiDetectDevice.cpp
//
//  Detect input devices (keyboards, joysticks and gamepads).
//

#include "guis/GuiDetectDevice.h"

#include "InputManager.h"
#include "Window.h"
#include "components/TextComponent.h"
#include "guis/GuiInputConfig.h"
#include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h"

#define HOLD_TIME 1000.0f

GuiDetectDevice::GuiDetectDevice(bool firstRun,
                                 bool forcedConfig,
                                 const std::function<void()>& doneCallback)
    : mFirstRun {firstRun}
    , mForcedConfig {forcedConfig}
    , mRenderer {Renderer::getInstance()}
    , mBackground {":/graphics/frame.svg"}
    , mGrid {glm::ivec2 {1, 5}}
{
    mHoldingConfig = nullptr;
    mHoldTime = 0;
    mDoneCallback = doneCallback;

    addChild(&mBackground);
    addChild(&mGrid);

    // Title.
    mTitle = std::make_shared<TextComponent>(
        firstRun ? _("WELCOME") : _("CONFIGURE INPUT DEVICE"),
        Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
        ALIGN_CENTER);
    mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {1, 1},
                   GridFlags::BORDER_BOTTOM);

    // Device info.
    std::stringstream deviceInfo;
    int numDevices {InputManager::getInstance().getNumJoysticks()};

    if (numDevices > 0) {
        deviceInfo << Utils::String::format(
            _n("%i GAMEPAD DETECTED", "%i GAMEPADS DETECTED", numDevices), numDevices);
    }
    else {
        deviceInfo << _("NO GAMEPADS DETECTED");
    }

    if (numDevices > 1 && Settings::getInstance()->getBool("InputOnlyFirstController"))
        deviceInfo << " " << _("(ONLY ACCEPTING INPUT FROM FIRST CONTROLLER)");

    mDeviceInfo = std::make_shared<TextComponent>(deviceInfo.str(), Font::get(FONT_SIZE_SMALL),
                                                  mMenuColorSecondary, ALIGN_CENTER);
    mGrid.setEntry(mDeviceInfo, glm::ivec2 {0, 1}, false, true);

    // Message.
    if (numDevices > 0) {
        mMsg1 = std::make_shared<TextComponent>(_("HOLD A BUTTON ON YOUR DEVICE TO CONFIGURE IT"),
                                                Font::get(FONT_SIZE_SMALL), mMenuColorPrimary,
                                                ALIGN_CENTER);
    }
    else {
        mMsg1 = std::make_shared<TextComponent>(_("HOLD A BUTTON ON YOUR KEYBOARD TO CONFIGURE IT"),
                                                Font::get(FONT_SIZE_SMALL), mMenuColorPrimary,
                                                ALIGN_CENTER);
    }

    mGrid.setEntry(mMsg1, glm::ivec2 {0, 2}, false, true);

    const std::string msg2str {
        firstRun ? _("PRESS ESC TO SKIP (OR THE QUIT SHORTCUT TO QUIT AT ANY TIME)") :
                   _("PRESS ESC TO CANCEL")};
    mMsg2 = std::make_shared<TextComponent>(msg2str, Font::get(FONT_SIZE_SMALL), mMenuColorPrimary,
                                            ALIGN_CENTER);
    mGrid.setEntry(mMsg2, glm::ivec2 {0, 3}, false, true);

    // Currently held device.
    mDeviceHeld =
        std::make_shared<TextComponent>("", Font::get(FONT_SIZE_MEDIUM), 0xFFFFFFFF, ALIGN_CENTER);
    mGrid.setEntry(mDeviceHeld, glm::ivec2 {0, 4}, false, true);

    // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent
    // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
    const float aspectValue {1.778f / mRenderer->getScreenAspectRatio()};
    const float width {glm::clamp(0.60f * aspectValue, 0.50f,
                                  (mRenderer->getIsVerticalOrientation() ? 0.85f : 0.80f)) *
                       mRenderer->getScreenWidth()};

    setSize(width, mRenderer->getScreenHeight() * 0.5f);
    setPosition((mRenderer->getScreenWidth() - mSize.x) / 2.0f,
                (mRenderer->getScreenHeight() - mSize.y) / 2.0f);
}

void GuiDetectDevice::onSizeChanged()
{
    mBackground.fitTo(mSize);

    mGrid.setSize(mSize);
    mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y);
    mGrid.setRowHeightPerc(2, mMsg1->getFont()->getHeight() / mSize.y);
    mGrid.setRowHeightPerc(3, mMsg2->getFont()->getHeight() / mSize.y);
}

bool GuiDetectDevice::input(InputConfig* config, Input input)
{
    if (!mFirstRun && input.device == DEVICE_KEYBOARD && input.type == TYPE_KEY && input.value &&
        input.id == SDLK_ESCAPE) {
        if (mDoneCallback)
            mDoneCallback();
        // Cancel the configuration.
        delete this; // Delete GUI element.
        return true;
    }
    // First run, but the user chooses to skip the configuration. This will default to the
    // built-in keyboard mappings.
    else if (mFirstRun && input.device == DEVICE_KEYBOARD && input.type == TYPE_KEY &&
             input.value && input.id == SDLK_ESCAPE) {
        if (mDoneCallback)
            mDoneCallback();
        delete this; // Delete GUI element.
        return true;
    }

    if (input.type == TYPE_BUTTON || input.type == TYPE_AXIS || input.type == TYPE_KEY ||
        input.type == TYPE_CEC_BUTTON) {
        if (input.value && mHoldingConfig == nullptr) {
            // Started holding.
            mHoldingConfig = config;
            mHoldTime = static_cast<int>(HOLD_TIME);
            mDeviceHeld->setText(_(Utils::String::toUpper(config->getDeviceName()).c_str()));
        }
        else if (!input.value && mHoldingConfig == config) {
            // Cancel.
            mHoldingConfig = nullptr;
            mDeviceHeld->setText("");
        }
    }
    return true;
}

void GuiDetectDevice::update(int deltaTime)
{
    if (mHoldingConfig) {
        // If ES-DE starts and if a known device is connected after startup, then skip
        // controller configuration unless the flag to force the configuration was passed
        // on the command line.
        if (!mForcedConfig && mFirstRun &&
            Utils::FileSystem::exists(InputManager::getConfigPath()) &&
            InputManager::getInstance().getNumConfiguredDevices() > 0) {
            if (mDoneCallback)
                mDoneCallback();
            delete this; // Delete GUI element.
        }
        else {
            mHoldTime -= deltaTime;
            // Fade in device name.
            const float t {std::fabs((static_cast<float>(mHoldTime) / HOLD_TIME) - 1.0f)};
            mDeviceHeld->setColor(mMenuColorDetectDeviceHeld |
                                  static_cast<unsigned char>(t * 255.0f));
            if (mHoldTime <= 0) {
                // A device was selected.
                mWindow->pushGui(new GuiInputConfig(mHoldingConfig, true, mDoneCallback));
                delete this;
            }
        }
    }
}