#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <InputAction/InputAction.hpp>
#include <InputManager/InputManager.hpp>
#include <InputMethod/InputMethod.hpp>
#include <InputMethod/KeyboardInput/KeyboardInput.hpp>
#include <Vk/ShaderConst.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/Slider/Slider.hpp>
#include <Widget/Switch/Switch.hpp>

void InputManager::registerControlFlagAction(Player::ControlFlag flag, const std::string &action)
{
    registerActionHandler(
        action,
        [this, flag]()
        {
            worldDisplay.player->controlFlags += flag;
        },
        [this, flag]()
        {
            worldDisplay.player->controlFlags -= flag;
        }
    );
}

InputManager::InputManager(WorldDisplay &worldDisplay) : worldDisplay(worldDisplay)
{
    inputMethods.push_back(std::make_unique<KeyboardInput>());

    registerControlFlagAction(Player::ControlFlag::Forward, InputAction::forward);
    registerControlFlagAction(Player::ControlFlag::Back, InputAction::backwards);
    registerControlFlagAction(Player::ControlFlag::Left, InputAction::left);
    registerControlFlagAction(Player::ControlFlag::Right, InputAction::right);
    registerControlFlagAction(Player::ControlFlag::Crouch, InputAction::crouch);
    registerControlFlagAction(Player::ControlFlag::Sprint, InputAction::sprint);

    registerActionHandler(
        InputAction::jump,
        [&worldDisplay]()
        {
            worldDisplay.player->controlFlags += Player::ControlFlag::Jump;
            if (!worldDisplay.player->isOnGround)
            {
                worldDisplay.player->controlFlags += Player::ControlFlag::Jetpack;
            }
        },
        [&worldDisplay]()
        {
            worldDisplay.player->controlFlags -= Player::ControlFlag::Jump;
            worldDisplay.player->controlFlags -= Player::ControlFlag::Jetpack;
        }
    );

    registerActionHandler(
        InputAction::inventory,
        [&worldDisplay]()
        {
            if (Widget::focused == nullptr || !Widget::focused->isTextInput())
            {
                if (!worldDisplay.isWidgetSet())
                {
                    worldDisplay.player->onOpenInventory();
                }
                else
                {
                    worldDisplay.setOverlayWidget(nullptr);
                }
            }
        },
        [](){}
    );

    registerActionHandler(
        InputAction::map,
        [&worldDisplay]()
        {
            if (Widget::focused == nullptr || !Widget::focused->isTextInput())
            {
                if (!worldDisplay.isWidgetSet())
                {
                    worldDisplay.getPlayer()->onOpenMap();
                }
                else
                {
                    worldDisplay.setOverlayWidget(nullptr);
                }
            }
        },
        [](){}
    );

    registerActionHandler(
        InputAction::dropItem,
        [&worldDisplay]()
        {
            if (Widget::focused == nullptr || !Widget::focused->isTextInput())
            {
                worldDisplay.player->onDrop();
            }
        },
        [](){}
    );

    registerActionHandler(
        InputAction::debugOverlay,
        [&worldDisplay]()
        {
            worldDisplay.showDebugInfo = !worldDisplay.showDebugInfo;
        },
        [](){}
    );

    registerActionHandler(
        InputAction::gpuDebugMode,
        [&worldDisplay]()
        {
            if (envVars.devMode)
            {
                worldDisplay.gpuDebugMode = (worldDisplay.gpuDebugMode + 1) % GLSL_DEBUGMODE_COUNT;
            }
        },
        [](){}
    );

    registerActionHandler(
        InputAction::debugPopup,
        [&worldDisplay]()
        {
            if (!envVars.devMode)
            {
                return;
            }

            if (Widget::focused == nullptr || !Widget::focused->isTextInput())
            {
                if (!worldDisplay.isWidgetSet())
                {
                    worldDisplay.setOverlayWidget(
                        new Row(
                            Layout::WithProportion<1>(),
                            new Panel(),
                            
                            new Column(
                                new Row(
                                    new Label("Debug specular:"),
                                    Layout::WithProportion<1>(),
                                    new Panel(),
                                    new Switch(worldDisplay.enableDebugSpecular)
                                ),
                                new Slider<int>(
                                    worldDisplay.debugSpecular,
                                    0,
                                    255,
                                    "$+"
                                ),
                                new Row(
                                    new Label("Debug refract/micro-rough:"),
                                    Layout::WithProportion<1>(),
                                    new Panel(),
                                    new Switch(worldDisplay.enableDebugRefractValue)
                                ),
                                new Slider<int>(
                                    worldDisplay.debugRefractValue,
                                    0,
                                    255,
                                    "$+"
                                )
                            ),

                            Layout::WithProportion<1>(),
                            new Panel()
                        )
                    );
                }
                else
                {
                    worldDisplay.setOverlayWidget(nullptr);
                }
            }
        },
        [](){}
    );

    registerActionHandler(
        InputAction::thirdPerson,
        [&worldDisplay]()
        {
            worldDisplay.thirdPerson = true;
        },
        [&worldDisplay]()
        {
            worldDisplay.thirdPerson = false;
        }
    );

    registerActionHandler(
        InputAction::playerList,
        [&worldDisplay]()
        {
            worldDisplay.showPlayerList = true;
        },
        [&worldDisplay]()
        {
            worldDisplay.showPlayerList = false;
        }
    );

    registerActionHandler(
        InputAction::chat,
        [](){},
        [&worldDisplay]()
        {
            if (worldDisplay.isWidgetSet())
            {
                return;
            }

            worldDisplay.chatToSend = "";
            worldDisplay.openChatOverlay();
        }
    );

    registerActionHandler(
        InputAction::toggleGadgets,
        [](){},
        [&worldDisplay]()
        {
            if (worldDisplay.isWidgetSet())
            {
                return;
            }

            auto player = worldDisplay.getPlayer();
            player->gadgetsEnabled = !player->gadgetsEnabled;
        }
    );
}

void InputManager::onEvent(SDL_Event &event)
{
    for (const auto &method : inputMethods)
    {
        method->onEvent(*this, event);
    }
}

void InputManager::onActionStarts(const std::string &action)
{
    auto it = actionHandlers.find(action);
    if (it == actionHandlers.end())
    {
        return;
    }

    it->second.onStarts();
}

void InputManager::onActionEnds(const std::string &action)
{
    auto it = actionHandlers.find(action);
    if (it == actionHandlers.end())
    {
        return;
    }

    it->second.onEnds();
}

void InputManager::registerActionHandler(const std::string &action, ActionCallback onStarts, ActionCallback onEnds)
{
    if (actionHandlers.contains(action))
    {
        crash(formatString("Duplicate callback registered for action '$+'", action));
    }

    actionHandlers[action] = {onStarts, onEnds};
}