/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "KeyBindTab.hpp"
#include "Configuration/Configuration.hpp"
#include "Engines/Resources/TextureManager.hpp"
#include "Graphics/Resources/Font.hpp"
#include "Engines/Sound/SoundEngine.hpp"
#include "Structs/JoystickData.hpp"

#include "Tools/ScaledPixel.hpp"
#include "Tools/Log.hpp"
#include "Tools/Format.hpp"
#include "Tools/I18n.hpp" ///< _ macro for i18n

KeyBindTab::KeyBindTab(Configuration &configuration, std::size_t playerIx,
                       TextureManager &manager, Font &titleFont,
                       Font &labelFont, SoundEngine &soundEngine,
                       float viewWidth) :
Tab(ScaledPixel{viewWidth}(425.f),
    ScaledPixel{viewWidth}(347.f),
    Color(50, 93, 151)),
m_viewWidth(viewWidth), m_configuration(&configuration),
m_textureManager(&manager), m_labelFont(&labelFont),
m_soundEngine(&soundEngine), m_playerIx(playerIx)
{
    const ScaledPixel sp(viewWidth);

    Sprite chooser_default(manager.getTexture("gfx/ui/ui.png",
                                              Texture::MinMagFilter::Nearest),
        Box(30.f / 512.f, 128.f / 512.f, 1.f / 512.f, 1.f / 512.f)),
    chooser_hover(manager.getTexture("gfx/ui/ui.png"),
        Box(25.f / 512.f, 128.f / 512.f, 1.f / 512.f, 1.f / 512.f)),
    chooser_selected(manager.getTexture("gfx/ui/ui.png"),
        Box(25.f / 512.f, 128.f / 512.f, 1.f / 512.f, 1.f / 512.f));

    //Title text
    m_titleText.setText(_f(OptionsControlsBinderID,
                           std::make_pair("binder_id", m_playerIx)));
    m_titleText.setFont(titleFont);
    m_titleText.setColor(Color(255, 255, 255));
    m_titleText.setPosition((getWidth() - m_titleText.getWidth()) / 2.f,
                            sp(10.f));

    //Active device chooser
    m_activeGameDeviceText.setText(_(OptionsControlsActiveDevice));
    m_activeGameDeviceText.setFont(labelFont);
    m_activeGameDeviceText.setColor(Color(200, 200, 200));

    SkinBox button_skin(sp(200.f), sp(30.f), chooser_default,
                        chooser_hover, chooser_selected);
    SkinSound button_sounds(soundEngine, "audio/ui/menuFocus.ogg",
                            "audio/ui/menuClick.ogg");
    TextButtonSkin chooserSkin(labelFont, Color(255, 255, 255),
                               button_skin, button_sounds);

    const std::string keyboardName = _(OptionsControlsKeyboard);
    const float chooserSpacing = ScaledPixel{m_viewWidth}(10.f);
    m_deviceChooser = Chooser(chooserSkin, { keyboardName }, 0);

    m_activeGameDeviceText.setPosition((Tab::getWidth() - chooserSpacing -
        m_activeGameDeviceText.getWidth() - m_deviceChooser.getWidth()) / 2.f,
        m_titleText.getYPosition() + m_titleText.getHeight() + sp(25.f));

    m_deviceChooser.setPosition(
        m_activeGameDeviceText.getXPosition() +
            m_activeGameDeviceText.getWidth() + chooserSpacing,
        m_activeGameDeviceText.getYPosition() +
            (m_activeGameDeviceText.getHeight() -
             m_deviceChooser.getHeight()) / 2.f);

    //Key binders
    m_selectionToDevice[0] = DEVICE_KEYBOARD;
    m_bindersData[DEVICE_KEYBOARD] = BinderData(keyboardName,
                                                PlayerKeys::undefinedKeys());

    PlayerKeys initialKeys; //Keys to initialize the Key Binders
    m_configuration->getPlayerKeys(m_playerIx, initialKeys);

    m_keyBinders[DEVICE_KEYBOARD] = MultipleKeyBinder(DEVICE_KEYBOARD,
                                                      initialKeys,
                                                      manager,
                                                      labelFont,
                                                      soundEngine,
                                                      viewWidth);
    m_keyBinders[DEVICE_KEYBOARD].setPosition(
        (getWidth() - m_keyBinders[DEVICE_KEYBOARD].getWidth()) / 2.f,
        m_deviceChooser.getYPosition() + m_deviceChooser.getHeight() +
            sp(10.f));

    m_resetGameController = TextButton(_(OptionsControlsResetJoystick),
                                       chooserSkin);
    m_resetGameController.setPosition(
        (getWidth() - m_resetGameController.getWidth()) / 2.f,
        m_keyBinders[DEVICE_KEYBOARD].getYPosition() +
            m_keyBinders[DEVICE_KEYBOARD].getHeight() + sp(10.f));

    m_currentDeviceID = DEVICE_KEYBOARD;
    adjustBinderPosition();
}

void KeyBindTab::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_background.draw();

    //Draw title text
    m_titleText.draw();
    m_activeGameDeviceText.draw();

    //Draw binders
    m_keyBinders[m_currentDeviceID].draw();

    //Draw reset game controller
    if (m_bindersData[m_currentDeviceID].resettable)
        m_resetGameController.draw();

    //Chooser: should be rendered last
    m_deviceChooser.draw();

    Drawable::popMatrix();
}

void KeyBindTab::addJoystickBinder(const JoystickData &joystickData)
{
    const int joystickID = joystickData.instanceID;
    if (m_configuration == nullptr || m_textureManager == nullptr ||
        m_labelFont == nullptr || m_soundEngine == nullptr)
    {
        Log::err("Key binders: No resources specified");
        return;
    }
    else if (m_bindersData.count(joystickID) > 0)
    {
        Log::err(Format::format("Joystick ID {} already known", joystickID));
        return;
    }

    //Load the configuration player keys if present, or save the default if not
    PlayerKeys joystickKeys = joystickData.keys;
    if (!m_configuration->getPlayerJoystickKeys(m_playerIx, joystickData.name,
                                                joystickKeys))
        m_configuration->setPlayerJoystickKeys(m_playerIx, joystickData.name,
                                               joystickKeys);

    m_keyBinders[joystickID] = MultipleKeyBinder(joystickID,
                                                 joystickKeys,
                                                 (*m_textureManager),
                                                 (*m_labelFont),
                                                 (*m_soundEngine),
                                                 m_viewWidth);
    m_keyBinders[joystickID].setPosition(
        (getWidth() - m_keyBinders[joystickID].getWidth()) / 2.f,
        m_deviceChooser.getYPosition() + m_deviceChooser.getHeight() +
        ScaledPixel{m_viewWidth}(10.f));

    m_bindersData[joystickID] = BinderData(joystickData.name,
                                           joystickData.keys);

    if (m_configuration->getPlayerDevice(m_playerIx) == joystickID)
        m_currentDeviceID = joystickID;

    resetChooser();
}

void KeyBindTab::removeJoystickBinder(int joystickID)
{
    //Do not remove keyboard input.
    if (joystickID == DEVICE_KEYBOARD)
        return;

    m_bindersData.erase(joystickID);
    m_keyBinders.erase(joystickID);

    if (joystickID == m_currentDeviceID)
        m_currentDeviceID = m_configuration->getPlayerDevice(m_playerIx);

    resetChooser();
}

bool KeyBindTab::mouseRelease(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);

    if (m_deviceChooser.mouseRelease(localMouse))
    {
        const int selectedChooser =
            m_selectionToDevice[m_deviceChooser.getSelection()];
        if (selectedChooser != m_currentDeviceID)
        {
            m_currentDeviceID = selectedChooser;
            applyActiveDevice();
            unfocusBinders();
            adjustBinderPosition();
        }
        return true;
    }

    m_keyBinders[m_currentDeviceID].mouseRelease(localMouse);

    if (m_bindersData[m_currentDeviceID].resettable &&
        m_resetGameController.mouseRelease(localMouse))
    {
        const PlayerKeys &keys = m_bindersData[m_currentDeviceID].defaultKeys;
        m_keyBinders[m_currentDeviceID].resetKeys(keys);
        applyKeys();
    }

    return Widget::contains(localMouse);
}

bool KeyBindTab::mouseClick(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);
    return Widget::contains(localMouse);
}

bool KeyBindTab::mouseMove(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);
    if (m_deviceChooser.mouseMove(localMouse))
        return true;

    m_keyBinders[m_currentDeviceID].mouseMove(localMouse);

    if (m_bindersData[m_currentDeviceID].resettable)
        m_resetGameController.mouseMove(localMouse);

    return Widget::contains(localMouse);
}

void KeyBindTab::keyEvent(const SDL_Event &event)
{
    Key key;
    int deviceID;
    if (Key::pressedFromEvent(event, key, deviceID))
    {
        m_keyBinders[m_currentDeviceID].keyDown(key, deviceID);
        applyKeys();
    }
}

void KeyBindTab::applyKeys()
{
    if (m_configuration != nullptr)
    {
        PlayerKeys mainKeys = m_keyBinders[DEVICE_KEYBOARD].getKeys();
        m_configuration->setPlayerKeys(m_playerIx, mainKeys);

        std::map<int, MultipleKeyBinder>::const_iterator it;
        for (it = m_keyBinders.begin(); it != m_keyBinders.end(); ++it)
        {
            if (it->first != DEVICE_KEYBOARD)
            {
                m_configuration->setPlayerJoystickKeys(m_playerIx,
                    m_bindersData[it->first].deviceName,
                    it->second.getKeys());
            }
        }
    }
}

void KeyBindTab::applyActiveDevice()
{
    if (m_configuration != nullptr)
        m_configuration->setPlayerDevice(m_playerIx, m_currentDeviceID);
}

void KeyBindTab::adjustBinderPosition()
{
    const float yBase = m_deviceChooser.getYPosition() +
        m_deviceChooser.getHeight() + ScaledPixel{m_viewWidth}(10.f);
    const float position = (m_bindersData[m_currentDeviceID].resettable) ?
        yBase : yBase + ScaledPixel{m_viewWidth}(20.f);

    m_keyBinders[m_currentDeviceID].setYPosition(position);
}

void KeyBindTab::resetChooser()
{
    m_selectionToDevice.clear();

    std::vector<std::string> deviceNames;
    deviceNames.reserve(m_bindersData.size());

    std::size_t i = 0;
    std::size_t defaultSelectionID = 0;
    std::map<int, KeyBindTab::BinderData>::const_iterator it;
    for (it = m_bindersData.begin(); it != m_bindersData.end(); ++it)
    {
        deviceNames.push_back(it->second.deviceName);
        m_selectionToDevice[i] = it->first;

        if (it->first == m_currentDeviceID)
            defaultSelectionID = i;

        i++;
    }

    m_deviceChooser.setData(deviceNames, defaultSelectionID);
}

void KeyBindTab::unfocusBinders()
{
    std::map<int, MultipleKeyBinder>::iterator it;
    for (it = m_keyBinders.begin(); it != m_keyBinders.end(); ++it)
        it->second.setFocus(false);
}
