/*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 "ClientFightScene.hpp"
#include "Engines/Resources/TextureManager.hpp"
#include "Engines/Sound/SoundInterface.hpp"
#include "Network/Serialization/PhysicsObjectStatus.hpp"
#include "Network/Serialization/DamageStatus.hpp"
#include "Network/Serialization/EtherStatus.hpp"
#include "Structs/Vector.hpp"

#include "Graphics/Drawable/Polygon.hpp" ///< Used for physics box drawing

ClientFightScene::ClientFightScene(TextureManager &textureManager,
                                   Ambient *ambientPlayer,
                                   SoundInterface *soundInterface,
                                   const DisplayOptions &displayOptions) :
FightScene(ambientPlayer, soundInterface), m_textureManager(&textureManager),
m_displayOptions(displayOptions)
{
    m_weaponsDrawer.attachTo(m_weapons, textureManager);
    m_cameraHandler.setMode(m_displayOptions.cameraMode);
}

void ClientFightScene::uninitialize()
{
    FightScene::uninitializeCore();
    clearPlayers();
    m_arenaDrawer.detach();

    cameraUpdateArena();
    cameraUpdatePlayers();
}

void ClientFightScene::reset()
{
    FightScene::reset();
    resetAIs();
}

void ClientFightScene::setArena(const std::string &arenaName)
{
    FightScene::setArena(arenaName);
    m_arenaDrawer.attachTo(m_arena, (*m_textureManager));
    cameraUpdateArena();
}

void ClientFightScene::clearPlayers()
{
    FightScene::clearPlayersCore();

    //Controllers
    m_humansControl.clear();
    m_aisControl.clear();
    m_netControl.clear();

    //Drawers
    m_playerDrawers.clear();
    cameraUpdatePlayers();
}

void ClientFightScene::setupPlayer(Player &player)
{
    PlayerDrawer drawer;
    drawer.attachTo(player, (*m_textureManager), m_displayOptions.showHitboxes);
    m_playerDrawers.push_back(drawer);

    resetAIs();
    cameraUpdatePlayers();

    player.addCallbackReceiver((*this));
}

void ClientFightScene::playerHitTriggered()
{
    //Trigger a camera shake when a hit was triggered
    //(both after a local player hit, and after a manual hit trigger such as
    // in networked games)
    if (m_displayOptions.cameraShake)
        m_cameraHandler.shake();
}

//Ignored player hit callback: Misses manual hit triggers for networked games
void ClientFightScene::playerWasHit(float, HitDirection, const Player*) {}

void ClientFightScene::resetAIs()
{
    const std::vector<const Player*> players = FightScene::getPlayers();
    for (AIController &ai : m_aisControl)
        ai.setEnemies(players);
}

void ClientFightScene::addAIPlayer(const std::string &name)
{
    if (Player *ai = FightScene::addPlayer(name))
    {
        m_aisControl.emplace_back();
        m_aisControl.back().attach((*ai));

        setupPlayer((*ai));
    }
}

void ClientFightScene::addHumanPlayer(const std::string &name, int deviceID,
                                      const PlayerKeys &keys)
{
    if (Player *human = FightScene::addPlayer(name))
    {
        HumanController controller;
        controller.setDeviceID(deviceID);
        controller.setKeys(keys);

        m_humansControl.push_back(controller);
        m_humansControl.back().attach((*human));

        setupPlayer((*human));
    }
}

ClientPlayerInterface *ClientFightScene::addNetClientPlayer(
    const std::string &name,
    PlayerActionSender *actionSender,
    int deviceID, const PlayerKeys &keys,
    const ClientInputState &state)
{
    if (Player *netClient = FightScene::addPlayer(name))
    {
        NetClientController controller(actionSender, state);
        controller.setDeviceID(deviceID);
        controller.setKeys(keys);

        m_netControl.push_back(controller);
        m_netControl.back().attach((*netClient));

        setupPlayer((*netClient));
        return &(m_netControl.back());
    }

    return nullptr;
}

void ClientFightScene::updatePlayerControllers()
{
    for (HumanController &human : m_humansControl)
        human.act();

    for (AIController &ai : m_aisControl)
        ai.act();

    for (NetClientController &netClient : m_netControl)
        netClient.act();
}

void ClientFightScene::processKeyEvent(const SDL_Event &event)
{
    if (m_started)
    {
        //Trigger event on pressed/unpressed keys
        for (HumanController &human : m_humansControl)
            human.keyEvent(event);

        for (NetClientController &netClient : m_netControl)
            netClient.keyEvent(event);
    }
}

void ClientFightScene::updateKeyState(const SDL_Event &event)
{
    //Update key state based on
    //the pressed/unpressed keyboard or joystick keys
    for (HumanController &human : m_humansControl)
        human.updateKeyState(event);

    for (NetClientController &netClient : m_netControl)
        netClient.updateKeyState(event);
}

void ClientFightScene::draw()
{
    m_arenaDrawer.draw();

    for (PlayerDrawer &playerDrawer : m_playerDrawers)
        playerDrawer.draw();

    m_weaponsDrawer.draw();

    if (m_displayOptions.showHitboxes)
        drawPhysicsBoxes();
}

void ClientFightScene::lookCamera(const Vector &viewSize, float frameTime)
{
    m_cameraHandler.look(viewSize.x, viewSize.y, frameTime,
                         m_arena.getMinCameraWidth());
}

void ClientFightScene::cameraUpdateArena()
{
    m_cameraHandler.setArena(
        Box(0.f, 0.f, m_arena.getWidth(), m_arena.getHeight()),
        m_arena.getGround());
}

void ClientFightScene::cameraUpdatePlayers()
{
    m_cameraHandler.setPlayers(getPlayers());
}

void ClientFightScene::drawPhysicsBoxes() const
{
    const std::vector<PhysicsObject*> &physicsObjects = m_physicsWorld.getObjects();
    for (const PhysicsObject *object : physicsObjects)
    {
        if (object != nullptr)
        {
            Box box = object->getBox();
            Polygon hitBox = Polygon::rectangle(box.width, box.height,
                                                Color(0, 0, 0, 0),
                                                Color(122, 122, 122));
            hitBox.setPosition(box.left, box.top);
            hitBox.draw();
        }
    }
}

void ClientFightScene::applyPlatformStatus(
    const std::vector<PhysicsObjectStatus> &status)
{
    m_arena.applyPlatformStatus(status);
}

void ClientFightScene::applyWeaponEtherStatus(const EtherStatus &status)
{
    m_weapons.applyStatus(m_physicsWorld, status);
}

void ClientFightScene::applyDamageStatus(const std::vector<DamageStatus> &status,
                                         const Timer::TimePoint &clientTime)
{
    DamageStatus::applyDamageStatusList(m_damageDealer, clientTime,
                                        status, m_players);
}

const Box &ClientFightScene::getCameraView() const
{
    return m_cameraHandler.getCurrentView();
}
