/*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 "LocalFightController.hpp"
#include "Configuration/Configuration.hpp"
#include "Engines/Resources/JoystickManager.hpp"
#include "Engines/Resources/TextureManager.hpp"
#include "Engines/Sound/AmbientPlayer.hpp"
#include "Engines/Sound/SoundEngine.hpp"
#include "Player/Player.hpp"
#include "Fight/FightRules.hpp"
#include "GUI/Fight/RoundAnnouncer.hpp"
#include "GUI/Fight/FightInterface.hpp"
#include "GUI/Fight/RoundResultInfo.hpp"
#include "PauseTab.hpp"
#include "EndTab.hpp"
#include "Structs/Character.hpp"
#include "Structs/Vector.hpp"

#include "Structs/JoystickData.hpp"
#include "Player/PlayerKeys.hpp"
#include "Tools/ScaledPixel.hpp"
#include "Tools/Log.hpp"
#include "Tools/Format.hpp"
#include <cstddef>
#include <cassert>

LocalFightController::LocalFightController(TextureManager &textureManager,
                        AmbientPlayer &ambientPlayer,
                        SoundEngine &soundEngine,
                        const ClientFightScene::DisplayOptions &options,
                        float viewWidth) :
m_fightScene(textureManager, &ambientPlayer, &soundEngine, options),
m_paused(false), m_announcer(nullptr), m_fightInterface(nullptr),
m_roundResultInfo(nullptr), m_pauseTab(nullptr), m_endTab(nullptr),
m_textureManager(&textureManager), m_viewWidth(viewWidth)
{

}

void LocalFightController::attach(RoundAnnouncer &announcer,
                                  FightInterface &fightInterface,
                                  RoundResultInfo &roundResultInfo,
                                  PauseTab &pauseTab, EndTab &endTab)
{
    m_announcer = &announcer;
    m_fightInterface = &fightInterface;
    m_roundResultInfo = &roundResultInfo;

    m_pauseTab = &pauseTab;
    m_pauseTab->setActive(false);

    m_endTab = &endTab;
    m_endTab->setActive(false);
}

void LocalFightController::initialize(const std::string &arena,
                                      const FightRules &rules,
                                      const std::vector<Character> &characters,
                                      const Configuration &configuration,
                                      const JoystickManager &joystickManager)
{
    m_referee = Referee(rules);
    m_referee.instruct(this);

    m_fightScene.addFightModeController(rules.getMode());
    m_fightScene.setArena(arena);
    m_fightScene.reset();

    loadPlayers(characters, configuration, joystickManager);

    if (m_fightInterface != nullptr)
    {
        m_fightInterface->setPlayers(m_fightScene.getPlayers(),
                                     (*m_textureManager));
        updateFightInterfaceResults();
    }

    m_referee.ready();
}

void LocalFightController::loadPlayers(const std::vector<Character> &characters,
                                       const Configuration &configuration,
                                       const JoystickManager &joystickManager)
{
    assert(characters.size() == m_referee.getRules().getPlayerCount());

    m_fightScene.clearPlayers();

    std::size_t humanID = 1;
    for (const Character &playerData : characters)
    {
        if (playerData.type == Character::Type::Human)
        {
            const int device = configuration.getPlayerDevice(humanID);
            PlayerKeys keys;

            if (device == DEVICE_KEYBOARD) //Keyboard
                configuration.getPlayerKeys(humanID, keys);
            else
            {
                configuration.getPlayerJoystickKeys(humanID,
                    joystickManager.getJoystickData(device).name, keys);
            }

            m_fightScene.addHumanPlayer(playerData.name, device, keys);
            humanID++;
        }
        else if (playerData.type == Character::Type::AI)
            m_fightScene.addAIPlayer(playerData.name);
    }
}

void LocalFightController::updateFightInterfaceResults()
{
    assert(m_fightInterface != nullptr);
    m_fightInterface->updatePlayerResults(m_referee.getRules().getRoundsToWin(),
                                m_referee.getFightState().getWinsPerPlayer());
}


void LocalFightController::reset()
{
    m_referee.ready();

    if (m_fightInterface != nullptr)
        updateFightInterfaceResults();

    if (m_endTab != nullptr)
        m_endTab->setActive(false);
}

void LocalFightController::togglePause()
{
    if (m_paused)
        m_paused = false;
    else if (m_referee.getFightState().getPhase() == FightState::Phase::Fighting)
        m_paused = true;

    if (m_pauseTab != nullptr)
        m_pauseTab->setActive(m_paused);
}

void LocalFightController::keyInput(const SDL_Event &event)
{
    if (!m_paused)
        m_fightScene.processKeyEvent(event);

    m_fightScene.updateKeyState(event);
}

void LocalFightController::update(float frameTime)
{
    if (!m_paused)
        m_fightScene.update(frameTime);

    m_referee.observe(m_fightScene);
}

void LocalFightController::draw(float frameTime, const Vector &viewSize)
{
    m_fightScene.lookCamera(viewSize, frameTime);
    m_fightScene.draw();
}

void LocalFightController::readyRound(unsigned int roundId)
{
    Log::info(Format::format("Referee: Round {} is about to start...",
                             roundId + 1));
    m_fightScene.reset();

    if (m_roundResultInfo != nullptr)
        m_roundResultInfo->deactivate();

    if (m_announcer != nullptr)
    {
        m_announcer->announceRound(roundId, m_referee.getRules(),
                                m_referee.getFightState().getWinsPerPlayer());
    }
}

void LocalFightController::startRound()
{
    Log::info("Referee: Round started!");
    m_fightScene.start();

    if (m_announcer != nullptr)
        m_announcer->announceFight();
}

void LocalFightController::roundOver(std::size_t winnerIx, const Player &winner,
                                     std::uint8_t wonRounds)
{
    Log::info("Referee: Round over!");
    Log::info(Format::format("  Winner: {} (#{}) ({}/{} rounds to win)",
                             winner.getName(), +winnerIx + 1, +wonRounds,
                             +m_referee.getRules().getRoundsToWin()));

    if (m_fightInterface != nullptr)
        updateFightInterfaceResults();

    if (m_roundResultInfo != nullptr)
    {
        m_roundResultInfo->activate(winnerIx + 1,
                                    winner.getName(),
                                    wonRounds,
                                    m_referee.getRules().getRoundsToWin());

        m_roundResultInfo->positionToCenter();
    }
}

void LocalFightController::roundOverDraw()
{
    Log::info("Referee: Round over!\n  Result: Draw game");
    if (m_roundResultInfo != nullptr)
    {
        m_roundResultInfo->activateDraw();
        m_roundResultInfo->positionToCenter();
    }
}

void LocalFightController::gameOver()
{
    Log::info("Referee: Game over!");
    if (m_endTab != nullptr)
    {
        if (m_roundResultInfo != nullptr)
        {
            m_endTab->setXPosition(m_roundResultInfo->getXPosition() +
                                   (m_roundResultInfo->getWidth() -
                                    m_endTab->getWidth()) / 2.f);
            m_endTab->setYPosition(m_roundResultInfo->getYPosition() +
                                   m_roundResultInfo->getTextBottomLine() +
                                   ScaledPixel{m_viewWidth}(20.f));
        }

        m_endTab->setActive(true);
    }
}
