/*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 "VersusController.hpp"
#include "Player/Player.hpp"

/////////////////////////
///FightModeController///
/////////////////////////
FightModeControl::Controller *FightModeControl::allocate(FightRules::Mode mode)
{
    if (mode == FightRules::Mode::Versus)
        return new VersusController();

    return nullptr;
}

//////////////////////
///VersusController///
//////////////////////
namespace
{
    /// Returns whether \p players[playerIx] is looking in the wrong direction
    /// \p playerIx must be 0 or 1 and \p players.size() must be 2
    bool looksInWrongDirection(std::size_t playerIx,
                               const std::vector<Player*> &players)
    {
        bool shouldLookLeft = (players[playerIx]->getBox().getCenter().x >=
                               players[(playerIx+1)%2]->getBox().getCenter().x);

        return (shouldLookLeft != players[playerIx]->looksLeft());
    }

    /// Returns whether \p players[playerIx] is moving away
    /// from the other player
    /// \p playerIx must be 0 or 1 and \p players.size() must be 2
    bool movingAwayFromEnemy(std::size_t playerIx,
                                const std::vector<Player*> &players)
    {
        bool shouldLookLeft = (players[playerIx]->getBox().getCenter().x >=
                               players[(playerIx+1)%2]->getBox().getCenter().x);

        return (shouldLookLeft && players[playerIx]->getMovingState() ==
                                 MovingState::MovingRight) ||
               (!shouldLookLeft && players[playerIx]->getMovingState() ==
                                   MovingState::MovingLeft);

    }

    /// Cancel any started attack and reduce the speed of
    /// a player moving away from the other player
    /// \p playerIx must be 0 or 1 and \p players.size() must be 2
    void preUpdatePlayer(std::size_t playerIx,
                         const std::vector<Player*> &players)
    {
        //Moving speed (push back effect on punch and kick)
        if (movingAwayFromEnemy(playerIx, players) &&
                players[playerIx]->getAttack() != AttackIndex::Punch &&
                players[playerIx]->getAttack() != AttackIndex::Kick)
            players[playerIx]->setSpeed(players[0]->getBaseSpeed() * .7f);
        else
            players[playerIx]->setSpeed(players[0]->getBaseSpeed());

        //Attack cancellation
        if (looksInWrongDirection(playerIx, players))
        {
            if (players[playerIx]->getState() == StateIndex::Moving)
                players[playerIx]->cancelAttack();
            //Do not turn the attacker around if it has committed
            //to an attack and passed the enemy
            else if (players[playerIx]->getAttack() == AttackIndex::Default ||
                     !movingAwayFromEnemy(playerIx, players))
            {
                players[playerIx]->setLooksLeft(
                    !players[playerIx]->looksLeft(), true);
            }
        }
    }

    /// Turns the player to always face the other player
    /// (except if moving on the ground)
    void postUpdatePlayer(std::size_t playerIx,
                          const std::vector<Player*> &players)
    {
        //Do not fix looking direction post update if:
        // - the player is moving on the ground or
        // - the player is attacking (passed the enemy while attacking)
        if (players[playerIx]->getState() != StateIndex::Moving &&
            (players[playerIx]->getAttack() == AttackIndex::Default ||
             !movingAwayFromEnemy(playerIx, players)) &&
            looksInWrongDirection(playerIx, players))
        {
            players[playerIx]->setLooksLeft(
                !players[playerIx]->looksLeft(), true);
        }
    }
}

void VersusController::preUpdate(std::vector<Player*> &players) const
{
    //The versus mode only has effect for games of exactly 2 non-KO players
    if (players.size() != 2 || players[0]->isKo() || players[1]->isKo())
        return;

    preUpdatePlayer(0, players);
    preUpdatePlayer(1, players);
}

void VersusController::postUpdate(std::vector<Player*> &players) const
{
    //The versus mode only has effect for games of exactly 2 non-KO players
    if (players.size() != 2 || players[0]->isKo() || players[1]->isKo())
        return;

    postUpdatePlayer(0, players);
    postUpdatePlayer(1, players);
}
