#include <iostream>
#include <Ogre.h>
#include <OgreImGuiOverlay.h>
#include <OgreTerrainGroup.h>
#include <OgreRTShaderSystem.h>
#include <OgreMeshLodGenerator.h>
#include <Procedural.h>
#include <nlohmann/json.hpp>
#include "Components.h"
#include "GameData.h"
#include "StaticGeometryModule.h"
#include "EditorGizmoModule.h"
#include "TerrainModule.h"
#include "physics.h"
#include "PhysicsModule.h"
#include "LuaData.h"
#include "harbour.h"
#include "temple.h"
#include "town.h"
#include "items.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
namespace Items
{
void runScriptsForAllTowns()
{
    ZoneScoped;
    std::pair<flecs::entity, Ogre::String> selected_item;
    std::list<std::pair<flecs::entity, Ogre::String> > items;
    StaticGeometryModule::getItemsProperties(&items);
    for (const auto &item : items) {
        nlohmann::json j = nlohmann::json::parse(item.second);
        Ogre::String itemType = j["type"].get<Ogre::String>();
        if (itemType == "town") {
            Items::runAllScriptsForTown(item.first);
            if (item.first.has<TerrainItemNode>())
                Geometry::updateItemGeometry(item.first);
        }
    }
    StaticGeometryModule::saveItems();
}
void showItemPopup(const std::pair<flecs::entity, Ogre::String> &item)
{
    ZoneScoped;
    Ogre::String popupLabel =
		"EditPopup" + Ogre::StringConverter::toString(item.first.id());
	if (ImGui::BeginPopup(popupLabel.c_str())) {
		Ogre::String prop =
			StaticGeometryModule::getItemProperties(item.first);
		nlohmann::json j = nlohmann::json::parse(prop);
		Ogre::String itemType = j["type"].get<Ogre::String>();
        bool changed = false;
        bool set = false;
        bool offset = false;
        Ogre::Vector3 offsetVal(0, 0, 0);
        int i;
        struct OffsetButton {
            Ogre::String label;
            Ogre::Vector3 offset;
            ImGuiDir direction;
        };
        struct OffsetButton buttons_large[] = {
            { "ElevateUp", { 0, 1, 0 }, ImGuiDir::ImGuiDir_Up },
            { "ElevateDown", { 0, -1, 0 }, ImGuiDir::ImGuiDir_Down },
            { "X+", { 1, 0, 0 }, ImGuiDir::ImGuiDir_None },
            { "X-", { -1, 0, 0 }, ImGuiDir::ImGuiDir_None },
            { "Z+", { 0, 0, 1 }, ImGuiDir::ImGuiDir_None },
            { "Z-", { 0, 0, -1 }, ImGuiDir::ImGuiDir_None },
        };
        struct OffsetButton buttons_small[] = {
            { "SmallElevateUp",
              { 0, 0.1f, 0 },
              ImGuiDir::ImGuiDir_Up },
            { "SmallElevateDown",
              { 0, -0.1f, 0 },
              ImGuiDir::ImGuiDir_Down },
            { "0.1 X+", { 0.1f, 0, 0 }, ImGuiDir::ImGuiDir_None },
            { "0.1 X-", { -0.1f, 0, 0 }, ImGuiDir::ImGuiDir_None },
            { "0.1 Z+", { 0, 0, 0.1f }, ImGuiDir::ImGuiDir_None },
            { "0.1 Z-", { 0, 0, -0.1f }, ImGuiDir::ImGuiDir_None },
        };

        if (ImGui::SmallButton("Set position from cursor")) {
            changed = true;
            set = true;
        }
        auto buttonGroup = [&](struct OffsetButton *button, int count) {
            int i;
            for (i = 0; i < count; i++) {
                if (i > 0)
                    ImGui::SameLine();
                if (button[i].direction !=
                    ImGuiDir::ImGuiDir_None) {
                    if (ImGui::ArrowButton(
                            button[i].label.c_str(),
                            button[i].direction)) {
                        offsetVal = button[i].offset;
                        offset = true;
                        changed = true;
                    }
                } else {
                    if (ImGui::SmallButton(
                            button[i].label.c_str())) {
                        offsetVal = button[i].offset;
                        offset = true;
                        changed = true;
                    }
                }
            }
        };
        static float windowWidth = 1.6f;
        static float windowHeight = 2.0f;
        static float windowDepth = 0.12f;
        static float windowFrameWidth = 0.2f;
        ImGui::Text("Windows tools...");
        char windowStyleName[32] = { 0 };
        ImGui::InputFloat("windowWidth", &windowWidth);
        ImGui::InputFloat("windowHeight", &windowHeight);
        ImGui::InputFloat("windowDepth", &windowDepth);
        ImGui::InputFloat("windowFrameWidth", &windowFrameWidth);
        if (ImGui::InputText("windowStyleAdd", windowStyleName,
                     sizeof(windowStyleName))) {
        }
        if (ImGui::SmallButton("Add")) {
        }

        ImGui::Separator();
        buttonGroup(buttons_large,
                sizeof(buttons_large) / sizeof(buttons_large[0]));
        buttonGroup(buttons_small,
                sizeof(buttons_small) / sizeof(buttons_small[0]));
        if (changed) {
            Ogre::Vector3 position =
                item.first.get<TerrainItem>().position;
            Ogre::Quaternion orientation =
                item.first.get<TerrainItem>().orientation;
            if (set) {
                position = ECS::get<EditorGizmo>()
                           .sceneNode
                           ->_getDerivedPosition();
                orientation =
                    ECS::get<EditorGizmo>()
                        .sceneNode
                        ->_getDerivedOrientation();
            } else if (offset)
                position += offsetVal;
            item.first.get_mut<TerrainItem>().position = position;
            item.first.get_mut<TerrainItem>().orientation =
                orientation;
            item.first.modified<TerrainItem>();
            StaticGeometryModule::saveItems();
            StaticGeometryModule::updateItemGeometry(item.first);
        }
		if (itemType == "harbour")
			createHarbourPopup(item);
		else if (itemType == "temple")
			createTemplePopup(item);
		else if (itemType == "town")
			createTownPopup(item);
		ImGui::EndPopup();
	}
}
void showItemButtons(const std::pair<flecs::entity, Ogre::String> &item)
{
    ZoneScoped;
    ImGui::SameLine();
	Ogre::String upd_label =
		"Update Height##" +
		Ogre::StringConverter::toString(item.first.id());
	if (ImGui::SmallButton(upd_label.c_str())) {
		TerrainItem &uitem = item.first.get_mut<TerrainItem>();
		uitem.position.y =
			ECS::get<Terrain>()
				.mTerrainGroup->getHeightAtWorldPosition(
					uitem.position);
		StaticGeometryModule::saveItems();
	}
	ImGui::SameLine();
	Ogre::String edit_label =
		"Edit##" + Ogre::StringConverter::toString(item.first.id());
	Ogre::String popupLabel =
		"EditPopup" + Ogre::StringConverter::toString(item.first.id());
	if (ImGui::SmallButton(edit_label.c_str()))
		ImGui::OpenPopup(popupLabel.c_str());
	ImGui::SameLine();
	Ogre::String del_label =
		"delete##" + Ogre::StringConverter::toString(item.first.id());
	if (ImGui::SmallButton(del_label.c_str())) {
		item.first.destruct();
		StaticGeometryModule::saveItems();
	}
	ImGui::Spacing();
}
void createItemsMenu()
{
    ZoneScoped;
    if (ImGui::BeginMenu("Harbour")) {
		Items::createHarbourMenu();
		ImGui::EndMenu();
	}
	if (ImGui::BeginMenu("Temple")) {
		Items::createTempleMenu();
		ImGui::EndMenu();
	}
	if (ImGui::BeginMenu("Town")) {
		Items::createTownMenu();
		ImGui::EndMenu();
    }
}
static void _setCameraSelectedPos(flecs::entity e)
{
    Ogre::Vector3 cursorPos;
    Ogre::Quaternion cursorOrientation;
    // setCursorSelectedPos(e, cursorPos, cursorOrientation);
    StaticGeometryModule::getItemPositionAndRotation(e, cursorPos,
                             cursorOrientation);
    ECS::get<EditorGizmo>().sceneNode->_setDerivedPosition(cursorPos);
    ECS::get<EditorGizmo>().sceneNode->_setDerivedOrientation(
        cursorOrientation);
    Ogre::Vector3 cameraPos =
        ECS::get<Camera>().mCameraPivot->_getDerivedPosition();
    Ogre::Vector3 cameraOffset =
        cursorOrientation * Ogre::Vector3::UNIT_Z * 30.0f;
    cameraPos.x = cursorPos.x;
    cameraPos.z = cursorPos.z;
    cameraPos += cameraOffset;
    cameraPos.y = TerrainModule::get_height(
                  ECS::get<Terrain>().mTerrainGroup, cameraPos) +
              10.0f;
    Ogre::TerrainGroup *tg = ECS::get<Terrain>().mTerrainGroup;
    long x, y;
    tg->convertWorldPositionToTerrainSlot(cameraPos, &x, &y);
    if (tg->getTerrain(x, y) && tg->getTerrain(x, y)->isLoaded()) {
        float height = tg->getHeightAtWorldPosition(cameraPos);
        cameraPos.y = height + 10.0f;
    }
    ECS::get<Camera>().mCameraPivot->_setDerivedPosition(cameraPos);
    ECS::get<Camera>().mCameraPivot->_setDerivedOrientation(
        cursorOrientation);
    cameraPos = ECS::get<Camera>().mCameraGoal->_getDerivedPosition();
    ECS::get<Camera>().mCameraNode->_setDerivedPosition(cameraPos);
    ECS::get<Camera>().mCameraNode->_setDerivedOrientation(
        ECS::get<Camera>().mCameraGoal->_getDerivedOrientation());
    // updateHeightmap();
}
void drawRectPreview(nlohmann::json::json_pointer ptr,
             const nlohmann::json &item)
{
    // Reserve a square area spanning across the table
    ImVec2 canvas_p0 = ImGui::GetCursorScreenPos();
    ImVec2 canvas_sz = ImVec2(ImGui::GetContentRegionAvail().x, 200);
    ImVec2 canvas_p1 =
        ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);
    Ogre::String rectName = ptr.back();
    nlohmann::json::json_pointer tmpptr = ptr;
    tmpptr.pop_back();
    const nlohmann::json &colorRects = item[tmpptr];

    ImDrawList *draw_list = ImGui::GetWindowDrawList();
    // Draw background
    draw_list->AddRectFilled(canvas_p0, canvas_p1,
                 IM_COL32(50, 50, 50, 255));

    for (auto &[name, data] : colorRects.items()) {
        std::cout << data.dump() << std::endl;
        // Map 0..1 to Screen Pixels
        ImVec2 p_min =
            ImVec2(canvas_p0.x + (float)data["left"] * canvas_sz.x,
                   canvas_p0.y + (float)data["top"] * canvas_sz.y);
        ImVec2 p_max = ImVec2(
            canvas_p0.x + (float)data["right"] * canvas_sz.x,
            canvas_p0.y + (float)data["bottom"] * canvas_sz.y);

        // Handle Color (assuming rgba are 0..255 or 0..1)
        ImVec4 col = ImVec4(data["color"]["r"], data["color"]["g"],
                    data["color"]["b"], data["color"]["a"]);

        // Draw the rect
        draw_list->AddRectFilled(p_min, p_max,
                     ImGui::ColorConvertFloat4ToU32(col));

        // Outline if selected
        if (rectName == name)
            draw_list->AddRect(p_min, p_max,
                       IM_COL32(255, 255, 0, 255), 0.0f, 0,
                       2.0f);
    }
    ImGui::Dummy(canvas_sz); // Advance cursor
}
}
namespace Geometry
{
void setupLods(Ogre::LodConfig &config)
{
    ZoneScoped;
    int count = 0;
	// config.advanced.useCompression = false;
    config.advanced.useVertexNormals = true;
	config.advanced.preventPunchingHoles = true;
	config.advanced.preventBreakingLines = true;
	config.createGeneratedLodLevel(10, 0.15f);
    //    config.createGeneratedLodLevel(50, 0.25f);
    // config.createGeneratedLodLevel(100, 0.36f);
    //    config.createGeneratedLodLevel(200, 0.50f);
    config.createGeneratedLodLevel(500, 0.85f);
	config.advanced.useBackgroundQueue = false;
    std::cout << "mesh name: " << config.mesh->getName() << std::endl;
    bool crash = false;
    for (count = 0; count < config.mesh->getSubMeshes().size(); count++) {
        Ogre::SubMesh *submesh = config.mesh->getSubMeshes()[count];
        std::cout << "unprocessed submesh: " << count << " " << submesh
              << std::endl;
        if (submesh)
            submesh->parent = config.mesh.get();
        else
            crash = true;
    }
    Ogre::MeshLodGenerator::getSingleton().generateLodLevels(config);
    for (count = 0; count < config.mesh->getSubMeshes().size(); count++) {
        Ogre::SubMesh *submesh = config.mesh->getSubMeshes()[count];
        std::cout << "submesh: " << count << " " << submesh
              << std::endl;
        if (submesh)
            submesh->parent = config.mesh.get();
        else
            crash = true;
    }
    OgreAssert(!crash, "no submesh");
}

Ogre::StaticGeometry *createStaticGeometry(flecs::entity e)
{
    ZoneScoped;
    Ogre::String props = e.get<TerrainItem>().properties;
	nlohmann::json jp = nlohmann::json::parse(props);
	if (jp.find("type") != jp.end()) {
		Ogre::String itemType = jp["type"].get<Ogre::String>();
		Ogre::String geoName = itemType + "_" +
				       Ogre::StringConverter::toString(e.id());
		OgreAssert((ECS::get<EngineData>().mScnMgr->hasStaticGeometry(
				   geoName)) == false,
			   "" + geoName + " already exists for some reason");
		Ogre::StaticGeometry *geo =
			ECS::get<EngineData>().mScnMgr->createStaticGeometry(
				geoName);
		return geo;
	}
	return nullptr;
}
void createItemGeometry(flecs::entity e)
{
    ZoneScoped;
    OgreAssert(!e.has<TerrainItemNode>(), "Geometry already created");
	std::cout << "creating geometry for item: " << e.id() << std::endl;
	Ogre::String props = e.get<TerrainItem>().properties;
	nlohmann::json jp = nlohmann::json::parse(props);
	Ogre::SceneNode *itemNode = ECS::get<EngineData>()
					    .mScnMgr->getRootSceneNode()
					    ->createChildSceneNode();
	itemNode->_setDerivedPosition(e.get<TerrainItem>().position);
	itemNode->_setDerivedOrientation(e.get<TerrainItem>().orientation);
	if (jp.find("staticMesh") != jp.end()) {
		Ogre::String meshName = jp["staticMesh"].get<Ogre::String>();
		Ogre::MeshPtr mesh =
			Ogre::MeshManager::getSingleton().getByName(meshName);
		if (mesh) {
			Ogre::Entity *ent =
				ECS::get<EngineData>().mScnMgr->createEntity(
					mesh);
			itemNode->attachObject(ent);
		}
	} else if (jp.find("type") != jp.end()) {
		Ogre::String itemType = jp["type"].get<Ogre::String>();
		std::cout << "type: " << itemType << std::endl;
		std::cout << "props: " << props << std::endl;
		Ogre::StaticGeometry *geo = createStaticGeometry(e);
		if (geo) {
			geo->setRegionDimensions(Ogre::Vector3(140, 140, 140));
			Ogre::Vector3 geoposition =
				itemNode->_getDerivedPosition();
			geoposition.y = 0.0f;
			geo->setOrigin(geoposition);
		}
		if (itemType == "harbour") {
			OgreAssert(geo, "Can't create static geometry");
			Geometry::createHarbour(e, itemNode, geo);
			e.set<TerrainItemNode>({ itemNode, geo });
		} else if (itemType == "temple") {
			OgreAssert(geo, "Can't create static geometry");
			createTemple(e, itemNode, geo);
			e.set<TerrainItemNode>({ itemNode, geo });
		} else if (itemType == "town") {
			OgreAssert(geo, "Can't create static geometry");
            createTown(e, itemNode, geo);
			e.set<TerrainItemNode>({ itemNode, geo });
			std::cout << " town created: " << e.id() << std::endl;
		} else {
			OgreAssert(geo, "Can't create static geometry");
			e.set<TerrainItemNode>({ itemNode, geo });
			OgreAssert(false, "Unknown item type: " + itemType);
		}
	} else {
		std::cout << "can't build item" << std::endl;
		std::cout << "props: " << props << std::endl;
		OgreAssert(false, "can't create item");
	}
}

void destroyItemGeometry(flecs::entity e)
{
    ZoneScoped;
    OgreAssert(e.has<TerrainItemNode>(), "No geometry created");
#if 0
    ECS::get<EngineData>().mScnMgr->destroyStaticGeometry()
    e.get<TerrainItemNode>().geo->destroy();
    e.get_mut<TerrainItemNode>().geo = nullptr;
    e.modified<TerrainItemNode>();
#endif
	e.remove<TerrainItemNode>();
}
void updateItemGeometry(flecs::entity e)
{
    ZoneScoped;
    OgreAssert(e.has<TerrainItem>(), "not terrain item");
    if (e.has<TerrainItemNode>())
        destroyItemGeometry(e);
    createItemGeometry(e);
}

flecs::entity createMeshGeometry(const Ogre::String &meshName,
				 flecs::entity parente,
				 Ogre::SceneNode *sceneNode,
				 Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
		meshName,
		Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	OgreAssert(mesh, "mesh " + meshName + " not found");
	Ogre::LodConfig config(mesh);
	setupLods(config);
	Ogre::Entity *ent = ECS::get<EngineData>().mScnMgr->createEntity(
		"Ent" + meshName, mesh);
	geo->addEntity(ent, sceneNode->_getDerivedPosition(),
		       sceneNode->_getDerivedOrientation());
	ECS::get<EngineData>().mScnMgr->destroyEntity(ent);
	JPH::ShapeRefC shape =
		JoltPhysicsWrapper::getSingleton().createMeshShape(mesh);
	JPH::BodyID id = JoltPhysicsWrapper::getSingleton().createBody(
		shape.GetPtr(), 0, sceneNode, JPH::EMotionType::Static,
		Layers::NON_MOVING);
	flecs::entity e = ECS::get()
				  .entity()
				  .child_of(parente)
				  .set<JPH::BodyID>(id)
				  .set<TerrainItemMeshNode>({ sceneNode, geo });
	JoltPhysicsWrapper::getSingleton().addBody(id,
						   JPH::EActivation::Activate);
    return e;
}

void registerTownItem(flecs::entity e)
{
    registerTown(e);
}

}
}
