#include <iostream>
#include <Ogre.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "TerrainModule.h"
#include "SunModule.h"
#include "GUIModuleCommon.h"
#include "GUIModule.h"
#include "EditorGUIModule.h"
#include "LuaData.h"
#include "WorldMapModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "CharacterAnimationModule.h"
#include "PhysicsModule.h"
#include "EventModule.h"
#include "CharacterManagerModule.h"
#include "VehicleManagerModule.h"
#include "PlayerActionModule.h"
#include "AppModule.h"
#include "CharacterAIModule.h"
#include "QuestModule.h"
#include "world-build.h"
#include "physics.h"
#include <tracy/Tracy.hpp>

namespace ECS
{
static flecs::world ecs;
void setup_minimal()
{
    ZoneScoped;
	ecs.component<EngineData>().add(flecs::Singleton);
	ecs.component<GameData>().add(flecs::Singleton);
	ecs.component<Input>().add(flecs::Singleton);
	ecs.component<Camera>().add(flecs::Singleton);
	ecs.import <GameWorldModule>();
	ecs.import <EventModule>();
	ecs.import <CharacterManagerModule>();
	ecs.import <VehicleManagerModule>();
	ecs.import <WaterModule>();
	ecs.import <AppModule>();
	/* lots of things depend on it */
	ecs.component<Body2Entity>().add(flecs::Singleton);
}
void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
            Ogre::Camera *camera, Ogre::RenderWindow *window)
{
    ZoneScoped;
	std::cout << "Setup GameData\n";
	setup_minimal();

    std::cout << "Setup Editor\n";
    ecs.component<GameState>().add(flecs::Singleton);
	ecs.import <CharacterModule>();
	ecs.import <BoatModule>();
	ecs.import <PhysicsModule>();
	ecs.import <WaterModule>();
	ecs.import <SunModule>();
	ecs.import <TerrainModule>();
    ecs.import <GUIModule>();
	ecs.import <EventTriggerModule>();
	ecs.import <LuaModule>();
	ecs.import <WorldMapModule>();
	ecs.import <CharacterAnimationModule>();
    ecs.import <PlayerActionModule>();
    ecs.import <CharacterAIModule>();
    ecs.import <QuestModule>();
    ecs.add<ActionNodeList>();

    ecs.set<GUI>({ false, false, true, false, false, true, "", {}, -1 });

	ecs.system<EngineData>("UpdateDelta")
		.kind(flecs::OnUpdate)
		.each([](EngineData &eng) {
			eng.delta = ECS::get().delta_time();
		});
	ecs.system<EngineData>("UpdateDelay")
		.kind(flecs::OnUpdate)
        .with<GameState>()
		.with<TerrainReady>()
		.with<WaterReady>()
		.with<GroundCheckReady>()
		.each([](EngineData &eng) {
			if (eng.startupDelay >= 0.0f)
				eng.startupDelay -= eng.delta;
#ifdef VDEBUG
			if (ECS::get().has<GroundCheckReady>())
				std::cout << "ground check ready\n";
#endif
		});
#ifdef VDEBUG
    ecs.system<EngineData>("CheckStatus")
		.kind(flecs::OnUpdate)
		.run([](flecs::iter &it) {
			if (ECS::get().has<WaterReady>())
				std::cout << "water ready\n";
			if (ECS::get().has<TerrainReady>())
				std::cout << "terrain ready\n";
			if (ECS::get().has<GroundCheckReady>())
				std::cout << "ground check ready\n";
		});
#endif
    ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
                  (int)window->getHeight(), false });
	ecs.set<Camera>({ cameraNode, camera, false });
	ecs.add<GameData>();
	ecs.add<Input>();
    ecs.observer<GameState>("Game_Start_Scen_Startup")
        .event(flecs::OnAdd)
        .each([](GameState &game) {
            ZoneScopedN("Game_Start_Scen_Startup");
            ECS::get().add<WaterSurface>();
            ECS::get().set<Sun>(
                { nullptr, nullptr, nullptr, nullptr });
            ECS::get().set<Terrain>({ nullptr,
                          nullptr,
                          nullptr,
                          nullptr,
                          nullptr,
                          nullptr,
                          nullptr,
                          false,
                          { 0, 0, 0 } });
        });
	if (!ecs.has<LuaBase>())
		ecs.add<LuaBase>();
	if (!ecs.has<LuaEvent>())
		ecs.set<LuaEvent>({});

	// ecs.set<Body2Entity>({});
	std::cout << "Setup GameData done\n";
    ecs.system<Terrain, GameState>("SpawnPlayer")
        .kind(flecs::OnUpdate)
        .interval(0.5f)
        .each([&](Terrain &mterrain, GameState &game) {
            ZoneScopedN("SpawnPlayer");
            flecs::entity player =
                ECS::get<CharacterManagerModule>().getPlayer();
            if (!player.is_valid()) {
                /* Create player */
                Ogre::Vector3 position;
                JPH::BodyID id;
                long x, y;
                Ogre::TerrainGroup *tg = mterrain.mTerrainGroup;
                if (tg->isDerivedDataUpdateInProgress())
                    return;
                tg->convertWorldPositionToTerrainSlot(
                    Ogre::Vector3(0, 0, 4), &x, &y);
                Ogre::Terrain *terrain = tg->getTerrain(x, y);
                if (terrain && terrain->isLoaded()) {
                    if (PhysicsModule::raycastQuery(
                            Ogre::Vector3(0, 500, 4),
                            Ogre::Vector3(0, -500, 4),
                            position, id)) {
                        if (position.y < -10.0f &&
                            position.y > -50.0f) {
                            player =
                                ecs.get_mut<
                                       CharacterManagerModule>()
                                    .createPlayer(
                                        { position.x,
                                          position.y,
                                          position.z },
                                        Ogre::Quaternion(
                                            Ogre::Radian(
                                                Ogre::Math::
                                                    PI),
                                            Ogre::Vector3::
                                                UNIT_Y));
                            std::cout << position
                                  << std::endl;
                        }
					}
				}
            }
        });
    // FIXME: convert this later to scene setup event
    static flecs::entity new_game_run =
        ecs.system<GameState>("NewGame")
            .kind(flecs::OnUpdate)
            .interval(0.5f)
            .each([&](GameState &game) {
                ZoneScopedN("NewHame");
                flecs::entity player =
                    ECS::get<CharacterManagerModule>()
                        .getPlayer();
                if (player.is_valid() &&
                    player.has<CharacterBase>() &&
                    player.has<Character>() &&
                    player.has<Player>()) {
                    ECS::get<LuaBase>().mLua->call_handler(
                        "new_game");
                    new_game_run.destruct();
                }
            });
    ecs.get_mut<CharacterModule>().preloadMeshes();
    ecs.modified<CharacterModule>();
    std::cout << "scene setup done" << std::endl;
}
void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
			Ogre::Camera *camera, Ogre::RenderWindow *window)
{
}

void setupInventoryScene(Ogre::SceneManager *scnMgr,
			 Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
			 Ogre::RenderWindow *window)
{
}

void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
         Ogre::Camera *camera, Ogre::RenderWindow *window)
{
    ZoneScoped;
    setup_minimal();
    ecs.component<RenderWindow>().add(flecs::Singleton);
	ecs.component<EditorSceneSwitch>().add(flecs::Singleton);
	ecs.import <CharacterModule>();
	ecs.import <BoatModule>();
	ecs.import <PhysicsModule>();
	ecs.import <WaterModule>();
	ecs.import <SunModule>();
	ecs.import <TerrainModule>();
	ecs.import <EditorGUIModule>();
	ecs.import <EventTriggerModule>();
	ecs.import <LuaModule>();
	// ecs.import <WorldMapModule>();
	ecs.import <CharacterAnimationModule>();
    ecs.import <PlayerActionModule>();
    ecs.add<ActionNodeList>();

	ecs.system<EngineData>("UpdateDelta")
		.kind(flecs::OnUpdate)
		.each([](EngineData &eng) {
			eng.delta = ECS::get().delta_time();
		});
	ecs.system<EngineData>("UpdateDelay")
		.kind(flecs::OnUpdate)
		.with<TerrainReady>()
		.with<WaterReady>()
		.with<GroundCheckReady>()
		.each([](EngineData &eng) {
			if (eng.startupDelay >= 0.0f)
				eng.startupDelay -= eng.delta;
#ifdef VDEBUG
			if (ECS::get().has<GroundCheckReady>())
				std::cout << "ground check ready\n";
#endif
		});
#ifdef VDEBUG
    ecs.system<EngineData>("CheckStatus")
		.kind(flecs::OnUpdate)
		.run([](flecs::iter &it) {
			if (ECS::get().has<WaterReady>())
				std::cout << "water ready\n";
			if (ECS::get().has<TerrainReady>())
				std::cout << "terrain ready\n";
			if (ECS::get().has<GroundCheckReady>())
				std::cout << "ground check ready\n";
		});
#endif
    ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
                  (int)window->getHeight(), false });
	ecs.set<Camera>({ cameraNode, camera, false });
#if 0
    ecs.set<EditorSceneSwitch>({ 0 });
#endif
    ecs.add<GameData>();
	ecs.add<Input>();
	ecs.add<WaterSurface>();
	ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });
	ecs.set<Terrain>({ nullptr,
			   nullptr,
			   nullptr,
			   nullptr,
			   nullptr,
			   nullptr,
			   nullptr,
			   false,
			   { 0, 0, 0 } });
    ecs.set<GUI>({ true, true, true, false, false, true, "", {}, -1 });
	ecs.get_mut<GUI>().enabled = true;
	ecs.get_mut<GUI>().setWindowGrab(false);
	ecs.modified<GUI>();
	ecs.get_mut<GUI>().setWindowGrab(true);
	ecs.modified<GUI>();
}

void update(float delta)
{
    {
        ZoneScopedN("ECS");
        ecs.progress(delta);
    }
}
flecs::world get()
{
	return ecs;
}
bool Vector3::zeroLength() const
{
	float l = x * x + y * y + z * z;
	return (l < 1e-06 * 1e-06);
}

}
