#include <Ogre.h>
#include <OgreConfigFile.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "StaticGeometryModule.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
#include "items.h"
#include "CharacterManagerModule.h"
#include <tracy/Tracy.hpp>

namespace ECS
{
void createNPCActionNodes(flecs::entity town, int index)
{
    ZoneScoped;
    TownNPCs &npcs = town.get_mut<TownNPCs>();
    TownNPCs::NPCData &npc = npcs.npcs.at(index);
    flecs::entity e = npc.e;
    nlohmann::json npcprops = npc.props;
    const CharacterBase &ch = e.get<CharacterBase>();
    Ogre::SceneNode *n = ECS::get<CharacterModule>().characterNodes.at(e);
    Ogre::Vector3 characterPos = n->_getDerivedPosition();
    Ogre::Quaternion characterRot = n->_getDerivedOrientation();
    if (npc.actionNodes.size() > 0) {
        int i;
        for (i = 0; i < npc.actionNodes.size(); i++) {
            auto &anode = npc.actionNodes[i];
            Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.3f +
                           Ogre::Vector3::UNIT_Y;
            if (i == 1)
                offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.3f +
                     Ogre::Vector3::UNIT_Y;
            anode.position = characterPos + characterRot * offset;
            anode.rotation = characterRot;
            to_json(anode.props["position"], anode.position);
            to_json(anode.props["rotation"], anode.rotation);
        }
        return;
    }
    {
        ActionNodeList::ActionNode anode;
        anode.props["action"] = "talk";
        anode.props["action_text"] = "Talk";
        anode.action = "talk";
        anode.action_text = "Talk";
        Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.25f +
                       Ogre::Vector3::UNIT_Y * 1.5f;
        anode.position = characterPos + characterRot * offset;
        anode.rotation = characterRot;
        anode.radius = 0.6f;
        anode.height = 1.0f;
        to_json(anode.props["position"], anode.position);
        to_json(anode.props["rotation"], anode.rotation);

        anode.props["radius"] = anode.radius;
        anode.props["height"] = anode.height;
        anode.props["town"] = town.id();
        anode.props["index"] = index;
        anode.props["npc"] = npcprops;
        anode.dynamic = true;
        npc.actionNodes.push_back(anode);
    }
    {
        ActionNodeList::ActionNode anode;
        anode.props["action"] = "action";
        anode.props["action_text"] = "Action";
        anode.action = "action";
        anode.action_text = "Action";
        Ogre::Vector3 offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.2f +
                       Ogre::Vector3::UNIT_Y;
        anode.position = characterPos + characterRot * offset;
        anode.rotation = characterRot;
        anode.radius = 0.3f;
        anode.height = 1.0f;
        to_json(anode.props["position"], anode.position);
        to_json(anode.props["rotation"], anode.rotation);
        anode.props["radius"] = anode.radius;
        anode.props["height"] = anode.height;
        anode.props["town"] = town.id();
        anode.props["index"] = index;
        anode.props["npc"] = npcprops;
        anode.dynamic = true;
        npc.actionNodes.push_back(anode);
    }
}
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
{
	ecs.module<CharacterManagerModule>();
    ecs.component<TownNPCs>();
    ecs.import <CharacterModule>();
	ecs.import <CharacterAnimationModule>();
    ecs.import <PhysicsModule>();
    ecs.import <PlayerActionModule>();
    ecs.component<TownCharacterHolder>();
    ecs.component<LivesIn>();
    ecs.system<TerrainItem, TownNPCs>("UpdateCharacters")
        .kind(flecs::OnUpdate)
        .interval(1.0f)
        .write<CharacterBase>()
        .write<CharacterLocation>()
        .write<Character>()
        .write<LivesIn>()
        .each([this](flecs::entity town, TerrainItem &item,
                 TownNPCs &npcs) {
            ZoneScopedN("UpdateCharacters");
            if (!player.is_valid())
                return;
            if (!player.has<CharacterBase>())
                return;

            flecs::entity player =
                ECS::get<CharacterManagerModule>().getPlayer();
            if (!player.is_valid())
                return;
            if (!player.has<CharacterBase>())
                return;
            Ogre::Vector3 cameraPos =
                ECS::get<Camera>()
                    .mCameraNode->_getDerivedPosition();
            for (auto &npc : npcs.npcs) {
                int index = npc.first;
                TownNPCs::NPCData &data = npc.second;
                Ogre::Vector3 npcPosition = data.position;
                Ogre::Quaternion npcOrientation =
                    data.orientation;
                if (cameraPos.squaredDistance(npcPosition) <
                    10000.0f) {
                    if (!data.e.is_valid()) {
                        data.e = createCharacterData(
                            data.modelFace,
                            data.modelHair,
                            data.modelTop,
                            data.modelBottom,
                            data.modelFeet,
                            data.position,
                            data.orientation);
                        data.e.add<LivesIn>(town);
                        break;
                    }
                }
#if 0
                if (cameraPos.squaredDistance(npcPosition) >
                    22500.0f) {
                    if (data.e.is_valid()) {
                        data.e.destruct();
                        data.e = flecs::entity();
                        break;
                    }
                }
#endif
            }
            for (auto &npc : npcs.npcs) {
                int index = npc.first;
                TownNPCs::NPCData &data = npc.second;
                Ogre::Vector3 npcPosition = data.position;
                Ogre::Quaternion npcOrientation =
                    data.orientation;
                if (cameraPos.squaredDistance(npcPosition) <
                    10000.0f) {
                    if (data.e.is_valid()) {
                        if (data.e.has<CharacterBase>() &&
                            data.e.has<LivesIn>(town))
                            createNPCActionNodes(
                                town, index);
                    }
                }
            }
            town.modified<TownNPCs>();
        });
}
flecs::entity
CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
				     const Ogre::Quaternion &rotation)
{
    ZoneScoped;
    static int count = 0;
    OgreAssert(count == 0, "overspawn");
    OgreAssert(!player.is_valid(), "Player already created");
	player = ECS::get().entity("player");
    OgreAssert(player.is_valid(), "Can't create player");
    {
        ZoneScopedN("PlayerCreate");

        player.add<Player>();
        ECS::get_mut<CharacterModule>().createCharacter(
            player, position, rotation, "male_Face.mesh",
            "male_Hair001.mesh", "male_BodyTop.mesh",
            "male_BodyBottom.mesh", "male_BodyFeet.mesh");
        ECS::modified<CharacterModule>();
        count++;
    }
    return player;
}

flecs::entity CharacterManagerModule::createCharacterData(
    const Ogre::String &modelFace, const Ogre::String &modelHair,
    const Ogre::String &modelTop, const Ogre::String &modelBottom,
    const Ogre::String &modelFeet, const Ogre::Vector3 &position,
    const Ogre::Quaternion &rotation)
{
    ZoneScoped;
    flecs::entity e = ECS::get().entity();
    ECS::get_mut<CharacterModule>().createCharacter(e, position, rotation,
                            modelFace, modelHair,
                            modelTop, modelBottom,
                            modelFeet);
    ECS::modified<CharacterModule>();
    return e;
}

void CharacterManagerModule::registerTownCharacters(flecs::entity town)
{
    ZoneScoped;
    {
        Ogre::MeshPtr maleMesh = Ogre::MeshManager::getSingleton().load(
            "normal-male.glb", "Characters");
        Ogre::MeshPtr femaleMesh =
            Ogre::MeshManager::getSingleton().load(
                "normal-female.glb", "Characters");
    }
    Ogre::String props = StaticGeometryModule::getItemProperties(town);
    nlohmann::json j = nlohmann::json::parse(props);
    nlohmann::json npcs = nlohmann::json::array();
    if (town.has<TownNPCs>())
        return;
    if (j.find("npcs") == j.end())
        return;
    npcs = j["npcs"];
    std::cout << npcs.dump(4) << std::endl;
    if (npcs.size() == 0)
        return;
    int index = 0;
    std::map<int, TownNPCs::NPCData> npcMap;
    for (auto &npc : npcs) {
        Ogre::Vector3 npcPosition;
        Ogre::Quaternion npcOrientation;
        from_json(npc["position"], npcPosition);
        from_json(npc["orientation"], npcOrientation);
        TownNPCs::NPCData npcData;
        npcData.e = flecs::entity();
        npcData.modelFace = npc["slot_face"].get<Ogre::String>();
        npcData.modelHair = npc["slot_hair"].get<Ogre::String>();
        npcData.modelTop = npc["slot_top"].get<Ogre::String>();
        npcData.modelBottom = npc["slot_bottom"].get<Ogre::String>();
        npcData.modelFeet = npc["slot_feet"].get<Ogre::String>();
        npcData.orientation = npcOrientation;
        npcData.position = npcPosition;
        npcData.props = npc;
        npcMap[index] = npcData;
        index++;
    }
    OgreAssert(npcMap.size() > 0, "no npcs registered");
    town.set<TownNPCs>({ npcMap });
}

}
