/*Copyright (C) 2010-2020 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 "Fight.hpp"
#include "Engines/GameEngine.hpp"

Fight::Fight(GameEngine *gameEngine) :
m_gameEngine(gameEngine), m_window(gameEngine->getWindow()),
m_configuration(gameEngine->getConfiguration())
{
    
}

void Fight::setArena(const std::string &arenaName)
{
    m_arenaPath = arenaName;
}

void Fight::addPlayer(const std::string &playerName, int playerType)
{
    m_playersDataList.push_back( make_pair(playerType, playerName) );
}

void Fight::clearPlayers()
{
    m_playersDataList.clear();
}

void Fight::run()
{
    //Init variables
    TextureManager textureManager;
    FontManager fontManager;
    SoundBufferManager soundBufferManager;
    
    float soundEngineVolume = m_configuration->getMasterVolume() * m_configuration->getSoundVolume() / 100.f;
    SoundEngine soundEngine;
    soundEngine.setManager(soundBufferManager);
    soundEngine.setVolume(soundEngineVolume);
    
        //View
    Vector viewSize = Vector(m_window->getViewWidth(), m_window->getViewHeight());
    
        //Interface
    EndTab endDialog(*fontManager.getFont("fonts/default.ttf", 30), textureManager, soundEngine);
    endDialog.setXPosition((viewSize.x - endDialog.getWidth()) / 2);
    endDialog.setYPosition((viewSize.y - endDialog.getHeight()) / 2);
    
    PauseTab pauseDialog(*fontManager.getFont("fonts/default.ttf", 30), textureManager, soundEngine);
    pauseDialog.setXPosition((viewSize.x - pauseDialog.getWidth()) / 2);
    pauseDialog.setYPosition((viewSize.y - pauseDialog.getHeight()) / 2);
    
    FightInterface interface;
    interface.viewWidth(viewSize.x);
    interface.setFonts(*fontManager.getFont("fonts/intuitive.ttf", 30), *fontManager.getFont("fonts/default.ttf", 10));
    
    Text fpsText;
    fpsText.setFont(*fontManager.getFont("fonts/default.ttf", 18));
    fpsText.setPosition(viewSize.x - 70, 5);
    fpsText.setColor(Color(255, 255, 255));
    
        //Physics
    PhysicsWorld physicsWorld;
    
        //Arena
    Arena arena;
    arena.loadFromXML(m_arenaPath, "cfg/arenas.xml", textureManager);
    
    arena.applyToPhysics(physicsWorld);
    arena.subscribeObjects(physicsWorld);
    
        //Players
    std::list<Human> humansList;
    std::list<AI> aisList;
    
    std::vector<Player*> players;

    for (unsigned int i = 0; i < m_playersDataList.size(); i++)
    {
        Player *player;
        
        int playerType = m_playersDataList[i].first;
        std::string playerName = m_playersDataList[i].second;
        
        if (playerType == 0) //Human
        {
            Human human = Human::loadFromXML(playerName, "cfg/characters.xml");
            
            PlayerKeys keys;
            int device = m_configuration->getPlayerDevice(humansList.size() + 1); //Joystick ?
            human.setDeviceID(device);
            
            if (device == DEVICE_KEYBOARD) //Keyboard
            {
                if (m_configuration->getPlayerKeys(humansList.size() + 1, keys))
                    human.setKeys(keys);
            }
            else
            {
                if (m_configuration->getPlayerJoystickKeys(humansList.size() + 1, m_gameEngine->getJoystickManager().getName(device), keys))
                    human.setKeys(keys);
            }
            
            humansList.push_back(human);
            player = &humansList.back();
        }
        else //if (playerType == 1) //AI
        {
            AI ai = AI::loadFromXML(playerName, "cfg/characters.xml");
            
            aisList.push_back(ai);
            player = &aisList.back();
        }
        
        player->setPhysicsWorld(physicsWorld);
        player->setSoundEngine(soundEngine);
        
        player->loadGraphics(textureManager);
        
        Vector playerSpawn(i * 300, player->getBox().height);
        arena.getPlayerSpawn(i, playerSpawn);
        
        player->setPosition(playerSpawn.x, playerSpawn.y - player->getBox().height);
        
        player->setDebugHits( m_configuration->getDebugMode() );
        
        players.push_back(player);
        
        //Subscribe to engines
        physicsWorld.addObject((*player));
        interface.addPlayer((*(players.back())), textureManager);
    }
    
    //Informing AIs about who their enemies are
    std::list<AI>::iterator it;
    
    for (it = aisList.begin(); it != aisList.end(); it++)
    {
        //Everybody else is an enemy ;)
        for (unsigned int i = 0; i < players.size(); i++)
        {
            if (players[i] != &(*it))
                it->addEnemy((*players[i]));
        }
    }
    
    //Weapons
    std::vector<const Player*> const_players = toConstPlayers(players);
    WeaponMedium weapons(const_players, textureManager);
    
    for (unsigned int i = 0; i < players.size(); i++)
        players[i]->initWeapons(weapons);
    
    //Camera
    Camera interfaceCamera(Box(0, 0, viewSize.x, viewSize.y));
    
    FightCameraHandler fightCameraHdl( Box(0, 0, arena.getWidth(), arena.getHeight()),
                                            arena.getGround(),
                                            const_players,
                                            m_configuration->isCameraDynamic() ? MODE_DYNAMIC : MODE_STATIC );
    
    //Start the music
    float volume = m_configuration->getMasterVolume() * m_configuration->getMusicVolume() / 100.f;
    PlaylistPlayer::stopPlaylist(PLAYLIST_MENU);
    
    if (MusicPlayer::isPlaying("sound/music/S31-Unexpected Trouble.ogg")) //Fight Replay
        MusicPlayer::stopMusic("sound/music/S31-Unexpected Trouble.ogg");
    
    MusicPlayer::playMusic("sound/music/S31-Unexpected Trouble.ogg", volume, true);
    
    //Start screen loop
    Timer time;
    Vector mouse;
    
    bool paused = false; //Is the game paused ?
    bool finished = false; //Is the game finished ?
    
    std::string winnerName;
    unsigned int winnerIx = 0;
    
    float frameTime = 0.f;
    
    float lastTime = Timer::getTicks(); //FPS counter
    int elapsedFrames = 0;
    
    SDL_Event event;
    while (1)
    {
        //Manage time
        frameTime = time.getTime();
        time.reset();
        
        //Manage events
        while (m_window->pollEvent(event))
        {
            switch(event.type)
            {
                case SDL_QUIT: //Application closed
                    m_gameEngine->exit();
                    return;
                break;
                
                case SDL_WINDOWEVENT:
                    if (event.window.event == SDL_WINDOWEVENT_RESIZED) //Window 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_KEYDOWN: //Key pressed
                    
                    //Pause handling
                    if (!finished && event.key.keysym.sym == SDLK_ESCAPE)
                    {
                        paused = !paused;
                        
                        //Mute / Unmute the sound engine (for unpleasant looping sounds especially)
                        if (paused)
                            soundEngine.setVolume(0.f);
                        else
                            soundEngine.setVolume(soundEngineVolume);
                    }
                    
                    //FPS handling
                    if (event.key.keysym.sym == SDLK_k)
                        m_window->setFrameRate(10);
                    else if (event.key.keysym.sym == SDLK_l)
                        m_window->setFrameRate(60);
                    else if (event.key.keysym.sym == SDLK_m)
                        m_window->setFrameRate(0);
                
                break;
                
                case SDL_MOUSEBUTTONUP: //Mouse clicked
                    mouse.x = event.button.x;
                    mouse.y = event.button.y;
                    
                    if (finished)
                        endDialog.mouseClick(m_window->viewCoords(mouse));
                    else if (paused)
                        pauseDialog.mouseClick(m_window->viewCoords(mouse));
                
                break;
                
                case SDL_MOUSEMOTION: //Mouse moved
                    mouse.x = event.motion.x;
                    mouse.y = event.motion.y;
                    
                    if (finished)
                        endDialog.mouseMove(m_window->viewCoords(mouse));
                    else if (paused)
                        pauseDialog.mouseMove(m_window->viewCoords(mouse));
                
                break;
            }
            
            //Human key events
            for (std::list<Human>::iterator it = humansList.begin(); it != humansList.end(); it++)
            {
                //Take key action.
                if (!paused && !it->isKo() && !it->isHit())
                    it->keyEvent(event);
                
                //Update key state.
                it->updateKeyState(event);
            }
        }
        
        //End of fight
        if (!finished)
        {
            unsigned int alive = 0;
            for (unsigned int i = 0; i < players.size(); i++)
            {
                if (!players[i]->isKo())
                {
                    alive++;
                    
                    if (alive == 1)
                    {
                        winnerName = players[i]->getName();
                        winnerIx = i + 1;
                    }
                }
            }
            
            if (alive <= 1)
            {
                finished = true;
                
                if (alive == 1)
                {
                    //winnerName and winnerIx represent the winner
                    //(last player not ko)
                    std::ostringstream winnerText;
                    winnerText << winnerName << " (#" << winnerIx << ") wins !";
                    
                    endDialog.setWinner(winnerText.str());
                }
                else
                    endDialog.setWinner("Draw game !");
            }
        }
        
        if (finished && endDialog.chosen())
        {
            unsigned int result = endDialog.result();
            
            if (result == END_EXIT)
                m_gameEngine->initMainMenu();
            
            return;
        }
        
        if (paused && pauseDialog.chosen())
        {
            unsigned int result = pauseDialog.result();
            
            if (result == PAUSE_RESUME)
            {
                paused = false;
                soundEngine.setVolume(soundEngineVolume);
                
                pauseDialog.reinitialize();
            }
            else if (result == PAUSE_RESTART)
                return; //Game engine will restart current screen.
            else if (result == PAUSE_EXIT)
            {
                m_gameEngine->initMainMenu();
                return;
            }
        }
                
        //Update physics
        if (!paused) physicsWorld.update(frameTime);
        
        //Update weapons
        if (!paused) weapons.update(frameTime);
        
        //Update sounds
        soundEngine.update(frameTime);
        
        //Update players
        if (!paused)
        {
            for (unsigned int i = 0; i < players.size(); i++)
            {
                if (!players[i]->isKo() && !players[i]->isHit()) players[i]->act();
                players[i]->update(frameTime);
            }
        }
        
        interface.update();
        
        //FPS counter
        elapsedFrames++;
        if (Timer::getTicks() - lastTime >= 250)
        {
            std::ostringstream convertFramerate;
            convertFramerate << "FPS: ";
            convertFramerate << static_cast<int>( (elapsedFrames / (Timer::getTicks() - lastTime)) * 1000 );
            fpsText.setText(convertFramerate.str());
            
            lastTime = Timer::getTicks();
            elapsedFrames = 0;
        }
        
        //Display
            //Clear
        m_window->clear();
        
            //Graphic part
        fightCameraHdl.look(viewSize.x, viewSize.y);
        
        arena.draw();
        for (unsigned int i = 0; i < players.size(); i++)
        {
            players[i]->draw();
            if (!paused) players[i]->updateGraphics(frameTime);
        }
        
        weapons.drawWeapons();
        
        //Debug physics
        if (m_configuration->getDebugMode())
            physicsWorld.debug();
        
            //Interface part
        interfaceCamera.look(viewSize.x, viewSize.y);
        
        if (finished)
            endDialog.draw();
        else if (paused)
            pauseDialog.draw();
        
        interface.draw();
        fpsText.draw();
        
            //Render
        m_window->flush();
    }
}

std::vector<const Player*> Fight::toConstPlayers(const std::vector<Player*> &players)
{
    std::vector<const Player*> res;
    
    for (unsigned int i = 0; i < players.size(); i++)
        res.push_back(players[i]);
    
    return res;
}
