/*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 "NetworkedGame.hpp"
#include "Engines/GameEngine.hpp"
#include "Graphics/Window.hpp"
#include "Configuration/Configuration.hpp"

#include "Engines/Resources/TextureManager.hpp"
#include "Engines/Resources/FontManager.hpp"
#include "Engines/Resources/JoystickManager.hpp"
#include "Engines/Resources/SoundBufferManager.hpp"
#include "Engines/Sound/SoundEngine.hpp"
#include "Network/Client/Client.hpp"
#include "Animation/Animation.hpp" ///< AnimationLag
#include "Player/Attack.hpp" ///< AttackLag
#include "Tools/Timer.hpp"
#include "Tools/Log.hpp"
#include "Tools/Format.hpp"

namespace
{
    /// Flag to only print the ENet library version once
    bool loggedENetVersion = false;
}

NetworkedGame::NetworkedGame(GameEngine &gameEngine) :
m_gameEngine(&gameEngine), m_window(&(gameEngine.getWindow())),
m_configuration(&(gameEngine.getConfiguration()))
{

}

bool NetworkedGame::initialize(Client &client) const
{
    //Initialize ENet and client host
    if (enet_initialize() != 0)
    {
        Log::err("Could not initialize ENet");
        return false;
    }

    if (!loggedENetVersion)
    {
        ENetVersion version = enet_linked_version();
        Log::info(Format::format("ENet {}.{}.{}",
                                 ENET_VERSION_GET_MAJOR(version),
                                 ENET_VERSION_GET_MINOR(version),
                                 ENET_VERSION_GET_PATCH(version)));
        loggedENetVersion = true;
    }

    if (!client.init())
    {
        Log::err("Could not initialize the client");
        enet_deinitialize();
        return false;
    }

    //Enable lag mitigation heuristics for animation and attack callbacks
    AnimationLag::enableLagMitigation();
    AttackLag::enableLagMitigation();

    return true;
}

void NetworkedGame::deinitialize() const
{
    enet_deinitialize();

    AnimationLag::disableLagMitigation();
    AttackLag::disableLagMitigation();
}

void NetworkedGame::run()
{
    const Viewport &viewport = m_window->getViewport();

    TextureManager textureManager;
    FontManager fontManager;
    SoundBufferManager soundBufferManager;

    SoundEngine soundEngine;
    soundEngine.setManager(soundBufferManager);
    soundEngine.setVolume(m_configuration->outputSoundVolume());

    Client client((*m_configuration), viewport,
                  m_gameEngine->getJoystickManager(),
                  textureManager, fontManager, soundEngine);
    if (!initialize(client))
    {
        m_gameEngine->initMainMenu();
        return;
    }

    //Start screen loop
    Timer time;
    SDL_Event event;
    while (1)
    {
        //Manage events
        while (m_window->pollEvent(event))
        {
            switch(event.type)
            {
                case SDL_QUIT: //Application closed
                    client.disconnectNow();
                    m_gameEngine->exit();
                    deinitialize();
                    return;
                break;

                case SDL_WINDOWEVENT:
                    if (event.window.event == SDL_WINDOWEVENT_RESIZED)
                        m_window->resizeView();
                break;

                case SDL_JOYDEVICEADDED:
                    m_gameEngine->getJoystickManager()
                                 .loadJoystick(event.jdevice.which);
                break;

                case SDL_JOYDEVICEREMOVED:
                    m_gameEngine->getJoystickManager()
                                 .removeJoystick(event.jdevice.which);
                    m_configuration->updatePlayerDevices(event.jdevice.which);
                break;

                case SDL_MOUSEBUTTONUP: //Mouse released
                    client.mouseRelease(viewport.project(event.button.x,
                                                         event.button.y));
                break;

                case SDL_MOUSEMOTION: //Mouse moved
                    client.mouseMove(viewport.project(event.button.x,
                                                      event.button.y));
                break;

                default:
                break;
            }

            client.keyEvent(event);
            if (client.willExit())
            {
                m_gameEngine->initMainMenu();
                deinitialize();
                return;
            }
        }

        //Manage time and update
        float frameTime = time.getElapsed();
        time.reset();

        client.update(frameTime);

        //Display
        m_window->clear();
        client.draw(frameTime);
        m_window->flush();
    }
}
