/*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 "MainMenuTab.hpp"

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

namespace
{
    enum RootIdentifiers : std::uint8_t
    {
        Play = 0,
        Multiplayer = 1,
        Versus = 2,
        Options = 3,
        Exit = 4
    };

    enum RoundIdentifiers : std::uint8_t
    {
        SingleRound = 1,
        TwoToWin = 2,
        ThreeToWin = 3,
        FourToWin = 4,
        FiveToWin = 5
    };

    enum PlayerIdentifiers : std::uint8_t
    {
        TwoPlayers = 2,
        ThreePlayers = 3,
        FourPlayers = 4,
        FivePlayers = 5,
        SixPlayers = 6
    };

    std::vector<MainMenuTab::Entry> makeRootEntries()
    {
        return
        {
            MainMenuTab::Entry(RootIdentifiers::Play, _(MenuPlay)),
            MainMenuTab::Entry(RootIdentifiers::Versus, _(MenuVersus)),
            MainMenuTab::Entry(RootIdentifiers::Multiplayer,
                               _(MenuMultiplayer)),
            MainMenuTab::Entry(RootIdentifiers::Options, _(MenuOptions)),
            MainMenuTab::Entry(RootIdentifiers::Exit, _(MenuExit))
        };
    }

    std::vector<MainMenuTab::Entry> makeRoundEntries()
    {
        return
        {
            MainMenuTab::Entry(RoundIdentifiers::SingleRound,
                               _(MenuSingleRound)),
            MainMenuTab::Entry(RoundIdentifiers::TwoToWin, _(Menu2Rounds)),
            MainMenuTab::Entry(RoundIdentifiers::ThreeToWin, _(Menu3Rounds)),
            MainMenuTab::Entry(RoundIdentifiers::FourToWin, _(Menu4Rounds)),
            MainMenuTab::Entry(RoundIdentifiers::FiveToWin, _(Menu5Rounds))
        };
    }

    std::vector<MainMenuTab::Entry> makePlayerEntries()
    {
        return
        {
            MainMenuTab::Entry(PlayerIdentifiers::TwoPlayers, _(Menu2Players)),
            MainMenuTab::Entry(PlayerIdentifiers::ThreePlayers, _(Menu3Players)),
            MainMenuTab::Entry(PlayerIdentifiers::FourPlayers, _(Menu4Players)),
            MainMenuTab::Entry(PlayerIdentifiers::FivePlayers, _(Menu5Players)),
            MainMenuTab::Entry(PlayerIdentifiers::SixPlayers, _(Menu6Players))
        };
    }

    /// MenuPhaseTab selection index when the selection has not yet been made
    constexpr std::uint8_t NO_SELECTION = 255;
}

//////////////////////
///MenuPhaseEntries///
//////////////////////
MainMenuTab::MenuPhaseEntries::MenuPhaseEntries(
    const std::vector<Entry> &entries,
    const TextButtonSkin &skin,
    float spacing) :
Widget(), m_selection(NO_SELECTION), m_width(skin.skin.box.width),
m_height(static_cast<float>(entries.size()) *
         (skin.skin.box.height + spacing) - spacing),
m_spacing(spacing)
{
    initEntries(entries, skin);
}

void MainMenuTab::MenuPhaseEntries::initEntries(
    const std::vector<Entry> &entries,
    const TextButtonSkin &skin)
{
    float position = 0.f;
    float skinWidth = 0.f;
    for (const Entry &entry : entries)
    {
        TextButton entryButton(entry.name, skin);
        entryButton.setYPosition(position);

        m_entries.emplace_back(entry.identifier, entryButton);
        position += skin.skin.box.height + m_spacing;

        //Check for expanded text buttons
        if (entryButton.getWidth() > skinWidth)
            skinWidth = entryButton.getWidth();
    }

    //Re-draw choices with expanded skin if needed
    constexpr float skinEpsilon = .1f;
    if (skinWidth > skin.skin.box.width + skinEpsilon)
    {
        TextButtonSkin expandedSkin = skin;
        expandedSkin.skin.box.width = skinWidth;
        initEntries(entries, expandedSkin);
    }
}

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

    for (std::pair<std::uint8_t, TextButton> &entry : m_entries)
        entry.second.draw();

    Drawable::popMatrix();
}

bool MainMenuTab::MenuPhaseEntries::mouseRelease(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);
    for (std::pair<std::uint8_t, TextButton> &entry : m_entries)
    {
        if (entry.second.mouseRelease(localMouse))
        {
            m_selection = entry.first;
            return true;
        }
    }

    return false;
}

bool MainMenuTab::MenuPhaseEntries::mouseClick(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);

    for (std::pair<std::uint8_t, TextButton> &entry : m_entries)
        entry.second.mouseClick(localMouse);

    return Widget::contains(localMouse);
}

bool MainMenuTab::MenuPhaseEntries::mouseMove(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);

    for (std::pair<std::uint8_t, TextButton> &entry : m_entries)
        entry.second.mouseMove(localMouse);

    return Widget::contains(localMouse);
}

void MainMenuTab::MenuPhaseEntries::unfocus()
{
    for (std::pair<std::uint8_t, TextButton> &entry : m_entries)
        entry.second.setFocus(false);
}

void MainMenuTab::MenuPhaseEntries::reset()
{
    m_selection = NO_SELECTION;
}

std::uint8_t MainMenuTab::MenuPhaseEntries::getSelection() const
{
    return m_selection;
}

float MainMenuTab::MenuPhaseEntries::getWidth() const
{
    return m_width;
}

float MainMenuTab::MenuPhaseEntries::getHeight() const
{
    return m_height;
}

float MainMenuTab::MenuPhaseEntries::getSpacing() const
{
    return m_spacing;
}

/////////////////
///MainMenuTab///
/////////////////
MainMenuTab::MainMenuTab(float width, float height, float spacing,
                         const Color &color, const TextButtonSkin &skin) :
Tab(width, height, color),
m_phase(Phase::Root),
m_rootEntries(makeRootEntries(), skin, spacing),
m_selectRoundsEntries(makeRoundEntries(), skin, spacing),
m_selectPlayersEntries(makePlayerEntries(), skin, spacing),
m_result(Result::None)
{
    positionEntries(m_rootEntries);
    positionEntries(m_selectRoundsEntries);
    positionEntries(m_selectPlayersEntries);
}

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

    m_background.draw();

    if (m_phase == Phase::Root)
        m_rootEntries.draw();
    else if (m_phase == Phase::SelectRoundsToWin)
        m_selectRoundsEntries.draw();
    else if (m_phase == Phase::SelectPlayerCount)
        m_selectPlayersEntries.draw();

    Drawable::popMatrix();
}

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

    if (m_phase == Phase::Root &&
        m_rootEntries.mouseRelease(localMouse))
    {
        processRootSelection();
    }
    else if (m_phase == Phase::SelectRoundsToWin &&
             m_selectRoundsEntries.mouseRelease(localMouse))
    {
        processRoundSelection();
    }
    else if (m_phase == Phase::SelectPlayerCount &&
             m_selectPlayersEntries.mouseRelease(localMouse))
    {
        processPlayerSelection();
    }

    return Widget::contains(localMouse);
}

bool MainMenuTab::mouseClick(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);

    if (m_phase == Phase::Root)
        m_rootEntries.mouseRelease(localMouse);
    else if (m_phase == Phase::SelectRoundsToWin)
        m_selectRoundsEntries.mouseRelease(localMouse);
    else if (m_phase == Phase::SelectPlayerCount)
        m_selectPlayersEntries.mouseRelease(localMouse);

    return Widget::contains(localMouse);
}

bool MainMenuTab::mouseMove(const Vector &mouseCoords)
{
    Vector localMouse = Widget::localCoords(mouseCoords);

    if (m_phase == Phase::Root)
        m_rootEntries.mouseMove(localMouse);
    else if (m_phase == Phase::SelectRoundsToWin)
        m_selectRoundsEntries.mouseMove(localMouse);
    else if (m_phase == Phase::SelectPlayerCount)
        m_selectPlayersEntries.mouseMove(localMouse);

    return Widget::contains(localMouse);
}

void MainMenuTab::positionEntries(MenuPhaseEntries &entries)
{
    entries.setPosition((getWidth() - entries.getWidth()) / 2.f,
                        entries.getSpacing());
}

void MainMenuTab::processRootSelection()
{
    const std::uint8_t selection = m_rootEntries.getSelection();
    if (selection != NO_SELECTION)
    {
        if (selection == RootIdentifiers::Play)
        {
            m_fightRules.setMode(FightRules::Mode::Default);
            m_phase = Phase::SelectPlayerCount;
        }
        else if (selection == RootIdentifiers::Versus)
        {
            m_fightRules.setMode(FightRules::Mode::Versus);
            m_phase = Phase::SelectRoundsToWin;
        }
        else if (selection == RootIdentifiers::Multiplayer)
            m_result = Result::Multiplayer;
        else if (selection == RootIdentifiers::Options)
            m_result = Result::Options;
        else if (selection == RootIdentifiers::Exit)
            m_result = Result::Exit;

        m_rootEntries.unfocus();
        m_rootEntries.reset();
    }
}

void MainMenuTab::processPlayerSelection()
{
    const std::uint8_t selection = m_selectPlayersEntries.getSelection();
    if (selection != NO_SELECTION)
    {
        m_fightRules.setPlayerCount(selection);
        m_phase = Phase::SelectRoundsToWin;

        m_selectPlayersEntries.unfocus();
        m_selectPlayersEntries.reset();
    }
}

void MainMenuTab::processRoundSelection()
{
    const std::uint8_t selection = m_selectRoundsEntries.getSelection();
    if (selection != NO_SELECTION)
    {
        m_fightRules.setRoundsToWin(selection);
        m_result = Result::LocalGame;

        m_selectRoundsEntries.unfocus();
        m_selectRoundsEntries.reset();
    }
}

void MainMenuTab::goBack()
{
    m_result = Result::None;

    if (m_phase == Phase::SelectPlayerCount ||
            (m_phase == Phase::SelectRoundsToWin &&
             m_fightRules.getMode() == FightRules::Mode::Versus))
    {
        m_phase = Phase::Root;
    }
    else if (m_phase == Phase::SelectRoundsToWin &&
             m_fightRules.getMode() == FightRules::Mode::Default)
    {
        m_phase = Phase::SelectPlayerCount;
    }
}

bool MainMenuTab::hasResult() const
{
    return (m_result != Result::None);
}

MainMenuTab::Result MainMenuTab::getResult() const
{
    return m_result;
}

const FightRules &MainMenuTab::getFightRules() const
{
    return m_fightRules;
}
