/*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 "Arena.hpp"
#include "Engines/Physics/PhysicsWorld.hpp"
#include "Engines/Sound/SoundInterface.hpp"
#include "Platform.hpp"
#include "Network/Serialization/PhysicsObjectStatus.hpp"

#include "Tools/Log.hpp"
#include "Tools/BuildValues.hpp" ///< Generated at build time
#include <pugixml.hpp>
#include <algorithm>

///////////
///Arena///
///////////
Arena::Arena() : m_width(1920.f), m_height(1080.f), m_ground(1000.f),
m_groundMaterial(MATERIAL_NONE), m_gravity(3000.f), m_airFriction(2250.f),
m_minCameraWidth(1920.f)
{

}

void Arena::addPlatform(const Box &srcBox, const Vector &drawSize,
                        const Box &bodyBox, const Vector &spawnPosition,
                        const std::string &material, bool hitable, bool fixed)
{
    m_platformsList.emplace_back();

    Platform &platform = m_platformsList.back();
    platform.setSourceBox(srcBox);
    platform.setDrawSize(drawSize);
    platform.setBodyBox(bodyBox);
    platform.setSpawnPosition(spawnPosition);
    platform.setMaterial(material);
    platform.setHitable(hitable);
    platform.setFixed(fixed);
}

void Arena::applyPlatformStatus(const std::vector<PhysicsObjectStatus> &status)
{
    if (status.empty())
        return; //Nothing to do

    std::size_t i = 0;
    for (Platform &platform : m_platformsList)
    {
        if (platform.isFixed()) //Only non-fixed platforms have updates
            continue;

        platform.applyPhysicsStatus(status[i]);
        i++;

        if (i >= status.size())
            break;
    }
}

void Arena::applyToPhysics(PhysicsWorld &physicsWorld) const
{
    physicsWorld.setWorldBox( Box(0.f, 0.f, m_width, m_ground) );
    physicsWorld.setGravityForce(m_gravity);
    physicsWorld.setAirFriction(m_airFriction);
    physicsWorld.setGroundMaterial(m_groundMaterial);
}

void Arena::applySoundInterface(SoundInterface *soundInterface)
{
    for (Platform &platform : m_platformsList)
        platform.setSoundInterface(soundInterface);
}

void Arena::subscribePlatforms(PhysicsWorld &physicsWorld)
{
    for (Platform &platform : m_platformsList)
    {
        const Vector &spawn = platform.getSpawnPosition();
        const Box &bodyBox = platform.getBodyBox();

        platform.setPhysicsWorld(physicsWorld);
        platform.setWidth(bodyBox.width);
        platform.setHeight(bodyBox.height);
        platform.setPosition(spawn.x, spawn.y - platform.getBox().height);
        platform.setVelocity(Vector(0.f, 0.f));

        physicsWorld.addObject(platform);
    }
}

void Arena::unsubscribePlatforms(PhysicsWorld &physicsWorld) const
{
    for (const Platform &platform : m_platformsList)
        physicsWorld.removeObject(platform);
}

const std::list<Platform> &Arena::getPlatforms() const
{
    return m_platformsList;
}

std::vector<PhysicsObjectStatus> Arena::getPlatformStatus() const
{
    std::vector<PhysicsObjectStatus> platformStatus;
    platformStatus.reserve(m_platformsList.size());

    for (const Platform &platform : m_platformsList)
    {
        if (platform.isFixed()) //Only exchange updates of non-fixed platforms
            continue;

        PhysicsObjectStatus status;
        status.readFromObject(platform);

        platformStatus.push_back(status);
    }

    return platformStatus;
}

void Arena::setBackground(const std::string &texturePath, const Box &source)
{
    m_texturePath = texturePath;
    m_background = source;
}

const std::string &Arena::getTexturePath() const
{
    return m_texturePath;
}

const Box &Arena::getBakgroundSource() const
{
    return m_background;
}

void Arena::setAmbient(const std::string &ambient)
{
    m_ambient = ambient;
}

const std::string &Arena::getAmbient() const
{
    return m_ambient;
}

void Arena::setWidth(float width)
{
    m_width = width;
}

float Arena::getWidth() const
{
    return m_width;
}

void Arena::setHeight(float height)
{
    m_height = height;
}

float Arena::getHeight() const
{
    return m_height;
}

void Arena::setGround(float ground)
{
    m_ground = ground;
}

float Arena::getGround() const
{
    return m_ground;
}

void Arena::setGroundMaterial(const std::string &material)
{
    m_groundMaterial = material;
}

const std::string &Arena::getGroundMaterial() const
{
    return m_groundMaterial;
}

void Arena::setGravity(float gravity)
{
    m_gravity = gravity;
}

void Arena::setAirFriction(float airFriction)
{
    m_airFriction = airFriction;
}

void Arena::setMinCameraWidth(float minWidth)
{
    if (minWidth > 0.f)
        m_minCameraWidth = minWidth;
}

float Arena::getMinCameraWidth() const
{
    return m_minCameraWidth;
}

void Arena::setPlayerSpawn(std::size_t ix, const Vector &playerSpawn)
{
    m_playerSpawns[ix] = playerSpawn;
}

bool Arena::getPlayerSpawn(std::size_t ix, Vector &target) const
{
    std::map<std::size_t, Vector>::const_iterator it = m_playerSpawns.find(ix);
    if (it != m_playerSpawns.end())
    {
        target = it->second;
        return true;
    }

    return false;
}

////////////////
///ArenaTools///
////////////////
Arena ArenaTools::load(const std::string &name)
{
    //Locate absolute path to xml data
    const std::string xmlPath = BuildValues::data("cfg/arenas.xml");

    //pugixml initialization
    pugi::xml_document xmlFile;
    pugi::xml_parse_result result = xmlFile.load_file(xmlPath.c_str());
    if (!result)
    {
        Log::err("Could not open arena description file: " +
                 std::string(result.description()));
        return Arena();
    }

    Arena arena;
    pugi::xml_node arenaNode;
    for (arenaNode = xmlFile.child("main").child("arena");
         arenaNode;
         arenaNode = arenaNode.next_sibling("arena"))
    {
        const std::string itemName(arenaNode.attribute("pathname").value());
        if (itemName == name)
        {
            //Arena information
            arena.setAmbient(arenaNode.attribute("ambient").as_string());
            arena.setWidth(arenaNode.attribute("width").as_float(1920.f));
            arena.setHeight(arenaNode.attribute("height").as_float(1080.f));
            arena.setGround(arenaNode.attribute("ground").as_float(1000.f));
            arena.setGravity(arenaNode.attribute("gravity").as_float(2000.f));
            arena.setAirFriction(
                arenaNode.attribute("airFriction").as_float(2250.f));
            arena.setMinCameraWidth(
                arenaNode.attribute("minCameraWidth").as_float(1920.f));
            arena.setGroundMaterial(
                arenaNode.attribute("material").as_string(MATERIAL_NONE));

            //Background
            pugi::xml_node backgroundNode = arenaNode.child("background");
            if (backgroundNode)
            {
                arena.setBackground("gfx/arenas/" + itemName + ".png",
                    Box(backgroundNode.attribute("srcX").as_float(0.f),
                        backgroundNode.attribute("srcY").as_float(0.f),
                        backgroundNode.attribute("srcWidth").as_float(0.f),
                        backgroundNode.attribute("srcHeight").as_float(0.f)));
            }

            //Platforms
            pugi::xml_node platformNode;
            for (platformNode = arenaNode.child("platforms").child("platform");
                 platformNode;
                 platformNode = platformNode.next_sibling("platform"))
            {
                const float
                    srcX = platformNode.attribute("srcX").as_float(0.f),
                    srcY = platformNode.attribute("srcY").as_float(0.f),
                    srcWidth = platformNode.attribute("srcWidth").as_float(0.f),
                    srcHeight = platformNode.attribute("srcHeight").as_float(0.f);

                const float
                    spawnX = platformNode.attribute("x").as_float(0.f),
                    spawnY = platformNode.attribute("y").as_float(0.f),
                    drawWidth = platformNode.attribute("width").as_float(0.f),
                    drawHeight = platformNode.attribute("height").as_float(0.f);

                const float
                    bodyX = platformNode.attribute("bodyX").as_float(0.f),
                    bodyY = platformNode.attribute("bodyY").as_float(0.f),
                    bodyWidth = platformNode.attribute("bodyWidth").as_float(0.f),
                    bodyHeight = platformNode.attribute("bodyHeight").as_float(0.f);

                const std::string material =
                    platformNode.attribute("material").as_string(MATERIAL_NONE);

                const bool
                    hitable = platformNode.attribute("hitable").as_bool(false),
                    fixed = platformNode.attribute("fixed").as_bool(false);

                arena.addPlatform(Box(srcX, srcY, srcWidth, srcHeight),
                                  Vector(drawWidth, drawHeight),
                                  Box(bodyX, bodyY, bodyWidth, bodyHeight),
                                  Vector(spawnX, spawnY),
                                  material, hitable, fixed);
            }

            //Spawns
            pugi::xml_node spawnNode;
            for (spawnNode = arenaNode.child("spawns").child("spawn");
                 spawnNode;
                 spawnNode = spawnNode.next_sibling("spawn"))
            {
                arena.setPlayerSpawn(spawnNode.attribute("playerIx").as_uint(0),
                    Vector(spawnNode.attribute("x").as_float(0.f),
                           spawnNode.attribute("y").as_float(0.f)));
            }
        }
    }

    return arena;
}

std::vector<std::string> ArenaTools::fetchNames()
{
    const std::string xmlPath = BuildValues::data("cfg/arenas.xml");
    pugi::xml_document xmlFile;
    if (!xmlFile.load_file(xmlPath.c_str()))
        return std::vector<std::string>();

    std::vector<std::string> arenas;
    pugi::xml_node arenaNode;
    for (arenaNode = xmlFile.child("main").child("arena");
         arenaNode;
         arenaNode = arenaNode.next_sibling("arena"))
    {
        arenas.emplace_back(arenaNode.attribute("pathname").value());
    }

    return arenas;
}

bool ArenaTools::validateName(const std::vector<std::string> &names,
                              const std::string &value)
{
    return (std::find(names.begin(), names.end(), value) != names.end());
}
