#include <iostream>
#include <Ogre.h>
#include <OgreTerrainGroup.h>
#include <OgreImGuiOverlay.h>
#include <OgreRTShaderSystem.h>
#include <Procedural.h>
#include <nlohmann/json.hpp>
#include "Components.h"
#include "GameData.h"
#include "EditorGizmoModule.h"
#include "TerrainModule.h"
#include "StaticGeometryModule.h"
#include "items.h"
#include "harbour.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
namespace Items
{
/* This is editor function */
static bool findPierOffset(float &offset)
{
    ZoneScoped;
	Ogre::Vector3 basePos =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
	Ogre::Quaternion baseRot =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
	Ogre::Vector3 direction = baseRot * Ogre::Vector3(0, 0, 1);
	float length = 0.0f;
	while (length < 250.0f) {
		Ogre::Vector3 currentPosition = basePos + direction * length;
		float height = ECS::get<Terrain>()
				       .mTerrainGroup->getHeightAtWorldPosition(
					       currentPosition);
		if (height < -4.0f) {
			offset = length;
			break;
		}
		length += 2.0f;
	}
	return length < 250.0f;
}
static bool findPierOffsetAndLengthAndDepth(float &offset, float &length,
					    float &depth)
{
    ZoneScoped;
    if (!findPierOffset(offset))
		return false;
	Ogre::Vector3 basePos =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
	Ogre::Quaternion baseRot =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
	Ogre::Vector3 direction = baseRot * Ogre::Vector3(0, 0, 1);
	length = 0.0f;
	depth = 4.0f;
	while (length < 60.0f) {
		Ogre::Vector3 currentPosition =
			basePos + direction * (offset + length);
		float height = ECS::get<Terrain>()
				       .mTerrainGroup->getHeightAtWorldPosition(
					       currentPosition);
		if (depth < -height)
			depth = -height;
		if (height > -4.0f)
			break;
		length += 6.0f;
	}
	return true;
}
static void findPierHeight(float maxLength, float &height)
{
    ZoneScoped;
    Ogre::Vector3 basePos =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
	Ogre::Quaternion baseRot =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
	Ogre::Vector3 direction = baseRot * Ogre::Vector3(0, 0, 1);
	float length = 0.0f;
	height = 0.0f;
	while (length < 60.0f) {
		Ogre::Vector3 currentPosition = basePos + direction * (length);
		float dheight =
			ECS::get<Terrain>()
				.mTerrainGroup->getHeightAtWorldPosition(
					currentPosition);
		if (height < dheight)
			height = dheight;
		length += 1.0f;
	}
}
static void findPierPath(float pathLength, std::vector<Ogre::Vector3> &path)
{
    ZoneScoped;
    float minHeight = 0.2f;
	int i;
	Ogre::Vector3 basePos =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
	Ogre::Quaternion baseRot =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
	Ogre::Vector3 direction = baseRot * Ogre::Vector3(0, 0, 1);
	float length = 0.0f;
	while (length < pathLength) {
		Ogre::Vector3 currentPosition = basePos + direction * (length);
		float dheight =
			ECS::get<Terrain>()
				.mTerrainGroup->getHeightAtWorldPosition(
					currentPosition);
		if (dheight < minHeight)
			dheight = minHeight;
		Ogre::Vector3 localOffset = Ogre::Vector3(0, 0, 1) * length;
		Ogre::Vector3 localFromWorld =
			ECS::get<EditorGizmo>()
				.sceneNode->convertWorldToLocalPosition(
					{ currentPosition.x, dheight,
					  currentPosition.z });
		path.push_back(
			{ localOffset.x, localFromWorld.y, localOffset.z });
		length += 2.0f;
	}
	if (path.size() == 0) {
		path.push_back(Ogre::Vector3(0, 0, 0));
		path.push_back(Ogre::Vector3(0, 0, pathLength));
	}
	std::vector<Ogre::Vector3> tmppath = path;
	path.clear();
	for (auto &pt : tmppath) {
		if (path.size() == 0)
			path.push_back(pt);
		else {
			if (path.back().z + 0.5f >= pt.z)
				continue;
			else
				path.push_back(pt);
		}
	}
	Ogre::Vector3 lastp = path.back();
	for (i = 1; i < path.size(); i++) {
		Ogre::Vector3 &prev = path[i - 1];
		if (path[i].y > prev.y)
			continue;
		else if (path[i].y < prev.y) {
			float d = prev.y - path[i].y;
			if (d > 0.15f)
				path[i].y = prev.y - 0.15f;
		}
	}
	float dy = path.back().y - lastp.y;
	if (dy > 0)
		path.push_back({ lastp.x, lastp.y, path.back().z + dy * 3.0f });
	path.push_back({ path.back().x, path.back().y, path.back().z + 2.0f });
}
bool editHarbourDistrict(nlohmann::json &jitem)
{
    ZoneScoped;
    float plazzaRadius = 5.0f;
	float plazzaHeight = 0.2f;
	float plazzaElevation = 0.0f;
	float centerOffset = 0.0f;
	bool plazza = false;
	bool changed = false;
	struct LotData {
		float distance;
		float angle;
		float width;
		float depth;
		float elevation;
		bool valid;
		Ogre::String type;
		Ogre::String properties;
	};
	std::vector<LotData> centerBuildings;
	nlohmann::json &j = jitem;
	if (j.find("centerOffset") != j.end())
		centerOffset = j["centerOffset"].get<float>();
	if (j.find("plazza") != j.end())
		plazza = j["plazza"].get<bool>();
	if (j.find("plazzaRadius") != j.end())
		plazzaRadius = j["plazzaRadius"].get<float>();
	if (j.find("plazzaHeight") != j.end())
		plazzaHeight = j["plazzaHeight"].get<float>();
	if (j.find("plazzaElevation") != j.end())
		plazzaElevation = j["plazzaElevation"].get<float>();
	if (j.find("centerBuildings") != j.end()) {
		for (auto &jb : j["centerBuildings"]) {
			LotData data;
			data.distance = 100.0f;
			data.angle = 0;
			data.width = 50.0f;
			data.depth = 50.0f;
			data.elevation = 0.0f;
			data.type = "base";
			data.properties = "{}";
			data.valid = true;
			if (jb.find("distance") != jb.end())
				data.distance = jb["distance"].get<float>();
			if (jb.find("angle") != jb.end())
				data.angle = jb["angle"].get<float>();
			if (jb.find("width") != jb.end())
				data.width = jb["width"].get<float>();
			if (jb.find("depth") != jb.end())
				data.depth = jb["depth"].get<float>();
			if (jb.find("elevation") != jb.end())
				data.elevation = jb["elevation"].get<float>();
			if (jb.find("type") != jb.end())
				data.type = jb["type"].get<Ogre::String>();
			if (jb.find("properties") != jb.end())
				data.properties =
					jb["properties"].get<Ogre::String>();
			centerBuildings.push_back(data);
		}
	}
	if (ImGui::SliderFloat("Center Offset", &centerOffset, 0.0f, 100.0f))
		changed = true;
	if (ImGui::Checkbox("Plazza", &plazza))
		changed = true;
	if (plazza) {
		if (ImGui::SliderFloat("Plazza Radius", &plazzaRadius, 5.0f,
				       100.0f))
			changed = true;
		if (ImGui::SliderFloat("Plazza Height", &plazzaHeight, 0.2f,
				       1.0f))
			changed = true;
		if (ImGui::SliderFloat("Plazza Elevation", &plazzaElevation,
				       -5.0f, 5.0f))
			changed = true;
	}
	int count = 0;
	for (auto &b : centerBuildings) {
		ImGui::Text("Lot %d", count);
		Ogre::String ms = "##" + Ogre::StringConverter::toString(count);
		if (ImGui::SliderFloat(("Lot distance" + ms).c_str(),
				       &b.distance, 0.0f, 100.0f))
			changed = true;
		if (ImGui::SliderFloat(("Lot angle" + ms).c_str(), &b.angle,
				       0.0f, 360.0f))
			changed = true;
		if (ImGui::SliderFloat(("Width" + ms).c_str(), &b.width, 10.0f,
				       200.0f))
			changed = true;
		if (ImGui::SliderFloat(("Depth" + ms).c_str(), &b.depth, 10.0f,
				       200.0f))
			changed = true;
		if (ImGui::SliderFloat(("Elevation" + ms).c_str(), &b.elevation,
				       -10.0f, 10.0f))
			changed = true;
		if (ImGui::SmallButton(("Delete" + ms).c_str()))
			b.valid = false;
		count++;
	}
	if (ImGui::SmallButton("Add building")) {
		changed = true;
		LotData data;
		data.distance = 100.0f;
		data.angle = 0;
		data.width = 50.0f;
		data.depth = 50.0f;
		data.elevation = 0.0f;
		data.type = "base";
		data.properties = "{}";
		data.valid = true;
		centerBuildings.push_back(data);
	}
	if (changed) {
		j["centerOffset"] = centerOffset;
		j["plazza"] = plazza;
		j["plazzaRadius"] = plazzaRadius;
		j["plazzaHeight"] = plazzaHeight;
		j["plazzaElevation"] = plazzaElevation;
		nlohmann::json lots = nlohmann::json::array();
		for (const auto &d : centerBuildings) {
			nlohmann::json jdata;
			if (!d.valid)
				continue;
			jdata["distance"] = d.distance;
			jdata["angle"] = d.angle;
			jdata["width"] = d.width;
			jdata["depth"] = d.depth;
			jdata["elevation"] = d.elevation;
			jdata["type"] = d.type;
			jdata["properties"] = d.properties;
			lots.push_back(jdata);
		}
		j["centerBuildings"] = lots;
	}
	return changed;
}
void createHarbourPopup(const std::pair<flecs::entity, Ogre::String> item)
{
    ZoneScoped;
    bool lighthouse = false;
	float lighthouseDistance = 0.0f;
	float lighthouseAngle = 0.0f;
	float centerOffset = 0.0f;
	bool changed = false;

	Ogre::String prop = StaticGeometryModule::getItemProperties(item.first);
	nlohmann::json j = nlohmann::json::parse(prop);
	nlohmann::json jcenter = nlohmann::json::object();
	if (j.find("lighthouse") != j.end())
		lighthouse = j["lighthouse"].get<bool>();
	if (j.find("lighthouseDistance") != j.end())
		lighthouseDistance = j["lighthouseDistance"].get<float>();
	if (j.find("lighthouseAngle") != j.end())
		lighthouseAngle = j["lighthouseAngle"].get<float>();
	if (j.find("centerOffset") != j.end())
		centerOffset = j["centerOffset"].get<float>();
	if (j.find("center") != j.end())
		jcenter = j["center"];
	if (ImGui::Checkbox("Lighthouse", &lighthouse))
		changed = true;
	if (lighthouse) {
		if (ImGui::SliderFloat("Lighthouse Distance",
				       &lighthouseDistance, 0.0f, 100.0f))
			changed = true;
		if (ImGui::SliderFloat("Lighthouse Angle", &lighthouseAngle,
				       -90.0f, 90.0f))
			changed = true;
	}
	if (ImGui::SliderFloat("Center Offset Global", &centerOffset, 0.0f,
			       100.0f))
		changed = true;
	changed = changed || editHarbourDistrict(jcenter);
	if (changed) {
		j["lighthouse"] = lighthouse;
		j["lighthouseDistance"] = lighthouseDistance;
		j["lighthouseAngle"] = lighthouseAngle;
		j["center"] = jcenter;
		j["centerOffset"] = centerOffset;
		StaticGeometryModule::setItemProperties(item.first, j.dump());
		StaticGeometryModule::saveItems();
		StaticGeometryModule::destroyItemGeometry(item.first);
		StaticGeometryModule::createItemGeometry(item.first);
	}
	ImGui::Text("%s", j.dump(4).c_str());
}
void createHarbourItem()
{
    ZoneScoped;
    Ogre::Vector3 itemPosition =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
	Ogre::Quaternion itemOrientation =
		ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
	float pierLength, pierDepth;
	float pierOffset;
	float pierHeight;
	if (!findPierOffsetAndLengthAndDepth(pierOffset, pierLength, pierDepth))
		return;
	findPierHeight(pierOffset + pierLength, pierHeight);
	std::vector<Ogre::Vector3> pierPath;
	findPierPath(pierOffset + 2.0f, pierPath);
	flecs::entity e = StaticGeometryModule::createItem(
		itemPosition, itemOrientation, "harbour");
	Ogre::String prop = StaticGeometryModule::getItemProperties(e);
	nlohmann::json j = nlohmann::json::parse(prop);
	j["pierOffset"] = pierOffset;
	j["pierLength"] = pierLength;
	j["pierDepth"] = pierDepth;
	j["pierHeight"] = pierHeight;
	nlohmann::json p = nlohmann::json::array();
	for (const auto &pt : pierPath) {
		nlohmann::json pj;
		to_json(pj, pt);
		p.push_back(pj);
	}
	j["pierPath"] = p;
	StaticGeometryModule::setItemProperties(e, j.dump());
	// setHarbourSurface();
	StaticGeometryModule::saveItems();
	// updateWorldTexture();
	// updateHeightmap();
	// TerrainModule::save_heightmap();
}
void createHarbourMenu()
{
    ZoneScoped;
    if (ImGui::MenuItem("Create"))
		createHarbourItem();
}
}
namespace Geometry
{
void createBridge(flecs::entity e, Ogre::SceneNode *sceneNode,
		  Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    int i;
	Procedural::TriangleBuffer tb;
	Ogre::MaterialPtr harbourMaterial;
	harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
		"proceduralMaterialHarbour" +
		Ogre::StringConverter::toString(e.id()));
	float stepWidth = 12.0f;
	float stepHeight = 0.15f;
	float stepDepth = 0.25f;
	float stairsHeight = 0.0f;
	float stairsOffset = 0.0f;
	std::vector<Ogre::Vector3> pierPath;
	Ogre::String props = e.get<TerrainItem>().properties;
	nlohmann::json jp = nlohmann::json::parse(props);
	if (jp.find("stairsHeight") != jp.end())
		stairsHeight = jp["stairsHeight"].get<float>();
	if (jp.find("stairsOffset") != jp.end())
		stairsOffset = jp["stairsOffset"].get<float>();
	if (jp.find("stepHeight") != jp.end())
		stepHeight = jp["stepHeight"].get<float>();
	if (jp.find("stepDepth") != jp.end())
		stepDepth = jp["stepDepth"].get<float>();
	if (jp.find("pierPath") != jp.end()) {
		pierPath.reserve(jp["pierPath"].size());
		for (auto &jpt : jp["pierPath"]) {
			Ogre::Vector3 pt;
			from_json(jpt, pt);
			pierPath.push_back(pt);
		}
	}
#if 1
	Procedural::Shape *pierShape = new Procedural::Shape();
	pierShape->addPoint(8.f, -0.7f);
	pierShape->addPoint(6.f, 0.7f);
	pierShape->addPoint(6.05f, 0.65f);
	pierShape->addPoint(0.f, 0.6f);
	pierShape->addPoint(-6.05f, 0.65f);
	pierShape->addPoint(-6.0f, 0.7f);
	pierShape->addPoint(-8.f, -0.7f);
	pierShape->close();

	Procedural::CubicHermiteSpline3 *pierCurve;
	Procedural::Path pierCurvePath;
	if (jp.find("pierPath") != jp.end()) {
		pierPath.reserve(jp["pierPath"].size());
		pierCurve = new Procedural::CubicHermiteSpline3();
		// pierCurve = new Procedural::RoundedCornerSpline3();
#if 0
		{
			int i;
			for (i = 0; i < 5; i++) {
				Ogre::Vector3 pt;
				from_json(jp["pierPath"][i], pt);
				pierPath.push_back(pt);
				pierCurve->addPoint(
					pt, pt - Ogre::Vector3(0, 0, -1),
					pt + Ogre::Vector3(0, 0, 1));
			}
			Ogre::Vector3 pt2;
			from_json(jp["pierPath"].back(), pt2);
			pierPath.push_back(pt2);
			pierCurve->addPoint(pt2);
			for (const auto &m : pierPath) {
				std::cout << m << std::endl;
			}
		}
#endif
#if 1
		for (auto &jpt : jp["pierPath"]) {
			Ogre::Vector3 pt;
			from_json(jpt, pt);
			pierPath.push_back(pt);
			pierCurve->addPoint(pt, Ogre::Vector3(0, 0, 1));
			// pierCurve->addPoint(pt);
		}
#endif
		OgreAssert(pierPath.size() > 0, "empty path");
		// pierCurve->setNumSeg(8);
		pierCurvePath = pierCurve->realizePath();
		// V
		Procedural::Track shapeTextureTrack =
			Procedural::Track(Procedural::Track::AM_POINT)
				.addKeyFrame(0, 0.0f)
				.addKeyFrame(6, 0.1f);
		// U
		Procedural::Track pathTextureTrack =
			Procedural::Track(Procedural::Track::AM_POINT)
				.addKeyFrame(0, 0.45f)
				.addKeyFrame(6, 0.9f);
		Procedural::Extruder extruder;
		extruder.setShapeTextureTrack(&shapeTextureTrack)
			.setPathTextureTrack(&pathTextureTrack)
			.setShapeToExtrude(pierShape)
			.setExtrusionPath(&pierCurvePath)
			.setEnableNormals(true)
			.setUTile(1.0f)
			.setVTile(1.0f)
			.addToTriangleBuffer(tb);
		for (auto &v : tb.getVertices()) {
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.41f, 0.9f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
#if 0
        for (const auto &v : tb.getVertices()) {
            std::cout << "UV: " << v.mUV << std::endl;
        }
        OgreAssert(false, "uvs");
#endif
#if 0
        Ogre::LodConfig config(extrudedMesh);
        setupLods(config);
        Ogre::Entity *pathEnt =
            ECS::get<EngineData>().mScnMgr->createEntity(
                extrudedMesh);
        pathEnt->setMaterial(harbourMaterial);
        float xofft = 0.0f;
        Ogre::Vector3 worldPosition =
            sceneNode->_getDerivedPosition() +
            sceneNode->_getDerivedOrientation() *
                Ogre::Vector3::UNIT_Z * 0.0f;
        Ogre::Quaternion worldOrientation =
            sceneNode->_getDerivedOrientation();
        Ogre::Vector3 xoffset =
            worldOrientation * (Ogre::Vector3::UNIT_X * xofft);
        geo->addEntity(pathEnt, worldPosition + xoffset,
                   worldOrientation, Ogre::Vector3(1, 1, 1));
#endif
	}
#else
	for (i = 0; i < pierPath.size(); i++) {
		if (i == 0)
			continue;
		Ogre::Vector3 lvec = pierPath[i] - pierPath[i - 1];
		Ogre::Vector3 xvec = Ogre::Vector3::UNIT_Y.crossProduct(lvec);
		Ogre::Vector3 n = lvec.crossProduct(xvec).normalisedCopy();
		Ogre::Vector3 pos = (pierPath[i] + pierPath[i - 1]) * 0.5f;
		Procedural::PlaneGenerator()
			.setNormal(n)
			.setNumSegX(2)
			.setNumSegY(2)
			.setSizeX(1)
			.setSizeY(1)
			.setPosition(pos)
			.addToTriangleBuffer(tb);
	}
#endif
	Ogre::String meshName =
		"pierPathMesh" + Ogre::StringConverter::toString(e.id());
	Ogre::MeshPtr mesh =
		Ogre::MeshManager::getSingleton().getByName(meshName);
	if (mesh)
		Ogre::MeshManager::getSingleton().remove(mesh);
	mesh = tb.transformToMesh(meshName);
	Ogre::LodConfig config(mesh);
	setupLods(config);
	Ogre::Entity *pathEnt =
		ECS::get<EngineData>().mScnMgr->createEntity(mesh);
	pathEnt->setMaterial(harbourMaterial);
	float xofft = 0.0f;
	Ogre::Vector3 worldPosition = sceneNode->_getDerivedPosition() +
				      sceneNode->_getDerivedOrientation() *
					      Ogre::Vector3::UNIT_Z * 0.0f;
	Ogre::Quaternion worldOrientation = sceneNode->_getDerivedOrientation();
	Ogre::Vector3 xoffset =
		worldOrientation * (Ogre::Vector3::UNIT_X * xofft);
	geo->addEntity(pathEnt, worldPosition + xoffset, worldOrientation,
		       Ogre::Vector3(1, 1, 1));
	ECS::get<EngineData>().mScnMgr->destroyEntity(pathEnt);
}
void createPier(flecs::entity e, Ogre::SceneNode *sceneNode,
		Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    Ogre::MaterialPtr harbourMaterial;
	harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
		"proceduralMaterialHarbour" +
		Ogre::StringConverter::toString(e.id()));
	harbourMaker hm(e);
	Ogre::Vector3 position = sceneNode->_getDerivedPosition();
	Ogre::String props = e.get<TerrainItem>().properties;
	nlohmann::json jp = nlohmann::json::parse(props);
	float pierHeight = 0.0f, pierLength = 6.0f, pierDepth = 6.0f;
	if (jp.find("pierLength") != jp.end())
		pierLength = jp["pierLength"].get<float>();
	if (jp.find("pierDepth") != jp.end())
		pierDepth = jp["pierDepth"].get<float>();
	if (jp.find("pierHeight") != jp.end())
		pierHeight = jp["pierHeight"].get<float>();
	Procedural::TriangleBuffer tb;
	float plankLength = 2.0f;
	float plankWidth = 4.01f;
	float plankHeight = 0.3f;
	const float beamLength = 6.0f;
	const float beamWidth = 5.5f;
	float beamHeight = 0.3f;
	if (pierLength < 12.0f)
		pierLength = 12.0f;
	auto processGrid = [&sceneNode, &geo](float stepLength, float length,
					      float zoffset, float yofft,
					      float xofft, Ogre::Entity *ent) {
		float step = 0.0f;
		while (step < length) {
			Ogre::Vector3 worldPosition =
				sceneNode->_getDerivedPosition() +
				sceneNode->_getDerivedOrientation() *
					Ogre::Vector3::UNIT_Z *
					(step + zoffset);
			Ogre::Quaternion worldOrientation =
				sceneNode->_getDerivedOrientation();
			Ogre::Vector3 xoffset = worldOrientation *
						(Ogre::Vector3::UNIT_X * xofft);
			Ogre::Vector3 yoffset = worldOrientation *
						(Ogre::Vector3::UNIT_Y * yofft);
			geo->addEntity(ent, worldPosition + xoffset + yoffset,
				       worldOrientation,
				       Ogre::Vector3(1, 1, 1));
			step += stepLength;
		}
	};
	float xofftPlanks;
	for (xofftPlanks = -plankWidth; xofftPlanks <= plankWidth;
	     xofftPlanks += plankWidth)
		processGrid(plankLength, pierLength, plankLength / 2.0f,
			    plankHeight / 2.0f + beamHeight / 2.0f, xofftPlanks,
			    hm.planks);
	{
		Procedural::TriangleBuffer tbPillars, tbBollards;
		float step = 0.0f;
		while (step < pierLength) {
			// pillars
			Procedural::BoxGenerator()
				.setSizeX(0.5f)
				.setSizeY(pierDepth)
				.setSizeZ(0.5f)
				.setEnableNormals(true)
				.setPosition(Ogre::Vector3(
					-5.0f, -pierDepth / 2.0f + 0.5f, step))
				.addToTriangleBuffer(tbPillars);
			Procedural::BoxGenerator()
				.setSizeX(0.5f)
				.setSizeY(pierDepth)
				.setSizeZ(0.5f)
				.setEnableNormals(true)
				.setPosition(Ogre::Vector3(
					5.0f, -pierDepth / 2.0f + 0.5f, step))
				.addToTriangleBuffer(tbPillars);
			step += 6.0f;
		}
		step = pierLength - 0.5f;
		while (step >= 0.0f) {
			// bollards
			Procedural::CylinderGenerator()
				.setHeight(0.8f)
				.setRadius(0.3f)
				.setPosition(Ogre::Vector3(
					-5.3f, 0.4f + 0.5f + 0.1f, step))
				.addToTriangleBuffer(tbBollards);
			Procedural::CylinderGenerator()
				.setHeight(0.8f)
				.setRadius(0.3f)
				.setPosition(Ogre::Vector3(
					5.3f, 0.4f + 0.5f + 0.1f, step))
				.addToTriangleBuffer(tbBollards);
			step -= 12.0f;
		}
		// beams
		Procedural::BoxGenerator()
			.setSizeX(0.5f)
			.setSizeY(beamHeight)
			.setSizeZ(pierLength)
			.setEnableNormals(true)
			.setPosition(Ogre::Vector3(-5.0f,
						   0.2 + beamHeight / 2.0f,
						   pierLength / 2.0f))
			.addToTriangleBuffer(tbPillars);
		Procedural::BoxGenerator()
			.setSizeX(0.5f)
			.setSizeY(beamHeight)
			.setSizeZ(pierLength)
			.setEnableNormals(true)
			.setPosition(Ogre::Vector3(5.0f,
						   0.2 + beamHeight / 2.0f,
						   pierLength / 2.0f))
			.addToTriangleBuffer(tbPillars);
		for (auto &v : tbPillars.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV += Ogre::Vector2(0.01f, 0.01f);
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.0f, 0.1f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		for (auto &v : tbBollards.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.21f;
			v.mUV.y += 0.01f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.2f, 0.3f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		tb.append(tbPillars);
		tb.append(tbBollards);
	}
	Ogre::String meshName =
		"pier" + Ogre::StringConverter::toString(e.id());
	{
		Ogre::MeshPtr mesh =
			Ogre::MeshManager::getSingleton().getByName(meshName);
		if (mesh)
			Ogre::MeshManager::getSingleton().remove(mesh);

		mesh = tb.transformToMesh(meshName);
		Ogre::LodConfig config(mesh);
		// config.advanced.useCompression = false;
		// config.advanced.useVertexNormals = true;
		setupLods(config);
		Ogre::Entity *ent =
			ECS::get<EngineData>().mScnMgr->createEntity(mesh);
		ent->setMaterial(harbourMaterial);
		float xofft = 0.0f;
		Ogre::Vector3 worldPosition =
			sceneNode->_getDerivedPosition() +
			sceneNode->_getDerivedOrientation() *
				Ogre::Vector3::UNIT_Z * 0.0f;
		Ogre::Quaternion worldOrientation =
			sceneNode->_getDerivedOrientation();
		Ogre::Vector3 xoffset =
			worldOrientation * (Ogre::Vector3::UNIT_X * xofft);
		geo->addEntity(ent, worldPosition + xoffset, worldOrientation,
			       Ogre::Vector3(1, 1, 1));
	}
	std::cout << meshName << std::endl;
}
void createPlazza(flecs::entity e, const nlohmann::json &district,
		  Ogre::SceneNode *sceneNode, Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    Ogre::MaterialPtr harbourMaterial;
	harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
		"proceduralMaterialHarbour" +
		Ogre::StringConverter::toString(e.id()));
	Ogre::Vector3 worldPosition = sceneNode->_getDerivedPosition();
	Ogre::Quaternion worldOrientation = sceneNode->_getDerivedOrientation();
	const nlohmann::json &jp = district;
	Procedural::TriangleBuffer tb;
	float centerOffset = 0.0f;
	float plazzaRadius = 5.0f;
	float plazzaHeight = 0.2f;
	float plazzaElevation = 0.0f;
	if (jp.find("centerOffset") != jp.end())
		centerOffset = jp["centerOffset"].get<float>();
	if (jp.find("plazzaRadius") != jp.end())
		plazzaRadius = jp["plazzaRadius"].get<float>();
	if (jp.find("plazzaHeight") != jp.end())
		plazzaHeight = jp["plazzaHeight"].get<float>();
	if (jp.find("plazzaElevation") != jp.end())
		plazzaElevation = jp["plazzaElevation"].get<float>();
	if (plazzaHeight < 0.1f)
		plazzaHeight = 0.1f;
	if (plazzaRadius < 5.0f)
		plazzaRadius = 5.0f;
	Ogre::Vector3 worldPlazzaCenter = worldPosition;
#if 0
    Procedural::CylinderGenerator()
        .setHeight(plazzaHeight)
        .setRadius(plazzaRadius)
        .setEnableNormals(true)
        .setPosition(Ogre::Vector3(
            0.0f, plazzaHeight / 2.0f + plazzaElevation, 0.0f))
        .addToTriangleBuffer(tb);
#endif
	float mh = 4.0f;
	Procedural::Shape *plazzaShape = new Procedural::Shape();
	plazzaShape->addPoint(0, -plazzaHeight - mh - mh);
	plazzaShape->addPoint(plazzaRadius * 0.5f + mh,
			      -plazzaHeight - mh - mh);
	plazzaShape->addPoint(plazzaRadius + mh, -plazzaHeight - mh);
	plazzaShape->addPoint(plazzaRadius, -plazzaHeight);
	plazzaShape->addPoint(plazzaRadius, 0.0f);
	plazzaShape->addPoint(plazzaRadius - 0.1f, 0.1f);
	plazzaShape->addPoint(plazzaRadius - mh + 0.1f, plazzaHeight);
	plazzaShape->addPoint(plazzaRadius - mh, plazzaHeight + 0.1f);
	plazzaShape->addPoint(plazzaRadius * 0.5f + mh, plazzaHeight);
	plazzaShape->addPoint(0, plazzaHeight);
	Procedural::Lathe()
		.setShapeToExtrude(plazzaShape)
		.setEnableNormals(true)
		.setPosition(Ogre::Vector3(
			0.0f, plazzaHeight / 2.0f + plazzaElevation, 0.0f))
		.setNumSeg(24)
		.addToTriangleBuffer(tb);
	for (auto &v : tb.getVertices()) {
		v.mUV *= 0.08f;
		v.mUV.x += 0.41f;
		v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
		v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
	}
	Ogre::String meshName =
		"plazzaMesh" + Ogre::StringConverter::toString(e.id());
	Ogre::MeshPtr mesh =
		Ogre::MeshManager::getSingleton().getByName(meshName);
	if (mesh)
		Ogre::MeshManager::getSingleton().remove(mesh);
	mesh = tb.transformToMesh(meshName);
	Ogre::LodConfig config(mesh);
	setupLods(config);
	Ogre::Entity *pathEnt =
		ECS::get<EngineData>().mScnMgr->createEntity(mesh);
	pathEnt->setMaterial(harbourMaterial);
	geo->addEntity(pathEnt, worldPlazzaCenter, worldOrientation,
		       Ogre::Vector3(1, 1, 1));
}

void createBuildings(flecs::entity e, const nlohmann::json &district,
		     Ogre::SceneNode *sceneNode, Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    Ogre::MaterialPtr harbourMaterial;
	harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
		"proceduralMaterialHarbour" +
		Ogre::StringConverter::toString(e.id()));
	nlohmann::json jbuildings = nlohmann::json::array();
	if (district.find("centerBuildings") != district.end())
		jbuildings = district["centerBuildings"];
	Ogre::Vector3 centerPosition = sceneNode->_getDerivedPosition();
	Ogre::Quaternion centerOrientation =
		sceneNode->_getDerivedOrientation();
	int count = 0;
	float baseHeight = 4.0f;
	for (const auto &jb : jbuildings) {
		Procedural::TriangleBuffer tb;
		float angle = 0.0f;
		float depth = 50.0f;
		float width = 100.0f;
		float distance = 100.0f;
		float elevation = 0.0f;
		std::cout << jb.dump() << std::endl;
		if (jb.find("angle") != jb.end())
			angle = jb["angle"].get<float>();
		if (jb.find("depth") != jb.end())
			depth = jb["depth"].get<float>();
		if (jb.find("width") != jb.end())
			width = jb["width"].get<float>();
		if (jb.find("distance") != jb.end())
			distance = jb["distance"].get<float>();
		if (jb.find("elevation") != jb.end())
			elevation = jb["elevation"].get<float>();

		OgreAssert(width > 1 && depth > 1 && baseHeight > 1,
			   "Bad stuff happen");

		Ogre::Quaternion rotation = Ogre::Quaternion(
			Ogre::Degree(angle), Ogre::Vector3::UNIT_Y);
		Ogre::Vector3 offset = centerOrientation * rotation *
				       (Ogre::Vector3::UNIT_Z * distance);
		Procedural::BoxGenerator()
			.setSizeX(width)
			.setSizeY(baseHeight)
			.setSizeZ(depth)
			.setEnableNormals(true)
			.setPosition(Ogre::Vector3(
				0.0f, -baseHeight / 2.0f + elevation, 0.0f))
			.addToTriangleBuffer(tb);
		OgreAssert(tb.getVertices().size() > 8, "bad box");
		for (auto &v : tb.getVertices()) {
			float c = 1.0 + 1.0 / (v.mPosition.y + baseHeight +
					       elevation);
			v.mPosition.x *= c;
			v.mPosition.z *= c;
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		Ogre::String meshName =
			"lotbase" + Ogre::StringConverter::toString(count) +
			"_" + Ogre::StringConverter::toString(e.id());
		Ogre::MeshPtr mesh =
			Ogre::MeshManager::getSingleton().getByName(meshName);
		if (mesh)
			Ogre::MeshManager::getSingleton().remove(mesh);
		mesh = tb.transformToMesh(meshName);
		Ogre::LodConfig config(mesh);
		setupLods(config);
		Ogre::Entity *ent =
			ECS::get<EngineData>().mScnMgr->createEntity(
				"Ent" + meshName, mesh);
		ent->setMaterial(harbourMaterial);
		geo->addEntity(ent, centerPosition + offset, rotation,
			       Ogre::Vector3::UNIT_SCALE);
		ECS::get<EngineData>().mScnMgr->destroyEntity(ent);
		count++;
	}
}

void createHarbour(flecs::entity e, Ogre::SceneNode *sceneNode,
		   Ogre::StaticGeometry *geo)
{
    ZoneScoped;
    std::cout << "createHarbour " << e.id() << std::endl;
	Ogre::MaterialPtr harbourMaterial;
	harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
		"proceduralMaterialHarbour" +
		Ogre::StringConverter::toString(e.id()));
	if (!harbourMaterial) {
		Procedural::TextureBuffer colorAtlas(1024);
		Procedural::RectangleTexture drawAtlas(&colorAtlas);
		Ogre::ColourValue normalYellow(0.8f, 0.6f, 0, 1);
		Ogre::ColourValue roadBrown(0.4f, 0.3f, 0.3f, 1);
		Ogre::ColourValue bollardGrey(0.2f, 0.2f, 0.2f, 1);
		drawAtlas.setRectangle(Ogre::RealRect(0.0f, 0.0f, 0.4f, 1.0f))
			.setColour(normalYellow)
			.process();
		drawAtlas.setRectangle(Ogre::RealRect(0.2f, 0.0f, 0.3f, 1.0f))
			.setColour(bollardGrey)
			.process();
		drawAtlas.setRectangle(Ogre::RealRect(0.4f, 0.0f, 1.0f, 1.0f))
			.setColour(roadBrown)
			.process();
		Ogre::TexturePtr pierTexture = colorAtlas.createTexture(
			"proceduralTextureHarbour" +
			Ogre::StringConverter::toString(e.id()));
		colorAtlas.saveImage("tmp.png");
		harbourMaterial =
			Ogre::MaterialManager::getSingletonPtr()->create(
				"proceduralMaterialHarbour" +
					Ogre::StringConverter::toString(e.id()),
				Ogre::ResourceGroupManager::
					DEFAULT_RESOURCE_GROUP_NAME);
		harbourMaterial->getTechnique(0)->getPass(0)->setShininess(0);
		harbourMaterial->getTechnique(0)->getPass(0)->setDiffuse(
			Ogre::ColourValue::White);
		harbourMaterial->getTechnique(0)->getPass(0)->setSpecular(
			Ogre::ColourValue(1.0f, 1.0f, 0.9f));
		harbourMaterial->getTechnique(0)
			->getPass(0)
			->createTextureUnitState(
				"proceduralTextureHarbour" +
				Ogre::StringConverter::toString(e.id()));
		if (Ogre::RTShader::ShaderGenerator::initialize()) {
			harbourMaterial->prepare();
			Ogre::RTShader::ShaderGenerator *mShaderGenerator =
				Ogre::RTShader::ShaderGenerator::
					getSingletonPtr();
			mShaderGenerator->createShaderBasedTechnique(
				*harbourMaterial,
				Ogre::MaterialManager::DEFAULT_SCHEME_NAME,
				Ogre::RTShader::ShaderGenerator::
					DEFAULT_SCHEME_NAME);
			Ogre::RTShader::RenderState *pMainRenderState =
				mShaderGenerator->getRenderState(
					Ogre::RTShader::ShaderGenerator::
						DEFAULT_SCHEME_NAME,
					*harbourMaterial);
		}
	}
	{
		Ogre::String props = e.get<TerrainItem>().properties;
		nlohmann::json jp = nlohmann::json::parse(props);
		float pierOffset = 0.0f;
		float centerOffset = 0.0f;
		float plazzaRadius = 5.0f;
		bool plazza = false;
		nlohmann::json jcenter = nlohmann::json::object();
		if (jp.find("center") != jp.end())
			jcenter = jp["center"];
		if (jcenter.find("plazzaRadius") != jcenter.end())
			plazzaRadius = jcenter["plazzaRadius"];
		if (jp.find("pierOffset") != jp.end())
			pierOffset = jp["pierOffset"].get<float>();
		if (jp.find("plazza") != jp.end())
			plazza = jp["plazza"].get<bool>();
		if (jp.find("centerOffset") != jp.end())
			centerOffset = jp["centerOffset"].get<float>();
		Ogre::Vector3 worldPosition =
			sceneNode->_getDerivedPosition() +
			sceneNode->_getDerivedOrientation() *
				Ogre::Vector3::UNIT_Z * (pierOffset);
		float xofft = 0.0f;
		float stairLengthZ = worldPosition.y;
		float stairsOffset = 0.0f;
		stairsOffset = stairLengthZ;
		jp["stairsHeight"] = worldPosition.y;
		jp["stairsOffset"] = stairsOffset;
		e.get_mut<TerrainItem>().properties = jp.dump();
		e.modified<TerrainItem>();
		worldPosition.y = 0.0f;
		Ogre::Quaternion worldOrientation =
			sceneNode->_getDerivedOrientation();
		Ogre::Vector3 xoffset =
			worldOrientation * (Ogre::Vector3::UNIT_X * xofft);
		Ogre::Vector3 zoffset =
			worldOrientation *
			(Ogre::Vector3::UNIT_Z * (stairsOffset - 1));
		Ogre::SceneNode *elevatorNode =
			sceneNode->createChildSceneNode();
		Ogre::SceneNode *pierNode = sceneNode->createChildSceneNode();
		pierNode->_setDerivedPosition(worldPosition + zoffset +
					      xoffset);
		Ogre::Vector3 worldPlazzaCenter =
			sceneNode->_getDerivedPosition() +
			sceneNode->_getDerivedOrientation() *
				Ogre::Vector3::UNIT_Z *
				(-centerOffset - plazzaRadius + 2.0f);
		Ogre::SceneNode *centerNode = sceneNode->createChildSceneNode();
		centerNode->_setDerivedPosition(worldPlazzaCenter);
		createBridge(e, elevatorNode, geo);
		createPier(e, pierNode, geo);
		createPlazza(e, jcenter, centerNode, geo);
		createBuildings(e, jcenter, centerNode, geo);
	}
	geo->build();
}
}
}
