#include <iostream>
#include <algorithm>
#include <nlohmann/json.hpp>
#include <OgreTerrainGroup.h>
#include <OgreFileSystemLayer.h>
#include <OgreRTShaderSystem.h>
#include <OgreStaticGeometry.h>
#include <OgreMeshLodGenerator.h>
#include <OgreWorkQueue.h>
#include <Procedural.h>
#include "Components.h"
#include "GameData.h"
#include "TerrainModule.h"
#include "physics.h"
#include "PhysicsModule.h"
#include "CharacterManagerModule.h"
#include "items.h"
#include "StaticGeometryModule.h"
#include "CharacterAIModule.h"
#include <tracy/Tracy.hpp>

namespace ECS
{

static bool itemsLoaded = false;
static bool furnitureLoaded = false;
static bool templatesLoaded = false;
static std::list<std::pair<long, long> > addQueue;
StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
{
    ZoneScoped;
	ecs.module<StaticGeometryModule>();
    ecs.import <CharacterManagerModule>();
    ecs.import <CharacterAIModule>();
	ecs.component<TerrainSlotParent>();
	ecs.component<TerrainItem>();
    ecs.component<FurnitureItem>();
    ecs.component<FurnitureInstance>()
        .on_remove([](flecs::entity e, FurnitureInstance &instance) {
            ZoneScoped;
            if (instance.furniture) {
                instance.furniture
                    ->destroyAllChildrenAndObjects();
                instance.furniture->getCreator()
                    ->destroySceneNode(instance.furniture);
                instance.furniture = nullptr;
            }
        })
        .on_set([](flecs::entity e, FurnitureInstance &instance) {
            ZoneScoped;
            if (instance.furniture !=
                e.get<FurnitureInstance>().furniture) {
                FurnitureInstance &f =
                    e.get_mut<FurnitureInstance>();
                if (f.furniture) {
                    f.furniture
                        ->destroyAllChildrenAndObjects();
                    f.furniture->getCreator()
                        ->destroySceneNode(f.furniture);
                }
            }
        })
        .on_add([](flecs::entity e, FurnitureInstance &instance) {
            ZoneScoped;
            instance.furniture = nullptr;
        });
    ecs.component<TerrainItemNode>().on_remove([](flecs::entity e,
						      TerrainItemNode &item) {
        ZoneScoped;
        if (item.itemNode) {
			item.itemNode->destroyAllChildrenAndObjects();
			item.itemNode->getCreator()->destroySceneNode(
				item.itemNode);
			item.itemNode = nullptr;
		}
		if (item.geo) {
			ECS::get<EngineData>().mScnMgr->destroyStaticGeometry(
				item.geo);
			item.geo = nullptr;
		}
	});
	ecs.component<TerrainItemMeshNode>();
    ecs.component<GeometryUpdateItem>();
	ecs.import <TerrainModule>();
	ecs.observer<const Terrain>("LoadTerrainItems")
		.event(flecs::OnSet)
		.each([&](const Terrain &terrain) {
            ZoneScopedN("LoadTerrainItems");
            if (terrain.mTerrainGroup && !itemsLoaded) {
				loadItems();
				itemsLoaded = true;
			}
		});
	if (!Ogre::MeshLodGenerator::getSingletonPtr())
		new Ogre::MeshLodGenerator();
    ecs.system<TerrainItem>("SetupTowns")
        .kind(flecs::OnUpdate)
        .without<TownNPCs>()
        .without<TownAI>()
        .each([&](flecs::entity e, TerrainItem &item) {
            Ogre::String props = item.properties;
            nlohmann::json jp = nlohmann::json::parse(props);
            if (jp.find("type") == jp.end())
                return;
            Ogre::String itemType = jp["type"].get<Ogre::String>();
            if (itemType == "town")
                Geometry::registerTownItem(e);
        });
	ecs.system("AddGeometryQueue").kind(flecs::OnUpdate).run([&](flecs::iter &it) {
        ZoneScopedN("AddGeometryQueue");
        std::list<flecs::entity> items;
        if (!ECS::get().has<Terrain>())
            return;
		if (!ECS::get<Terrain>().mTerrainGroup)
			return;
		if (!itemsLoaded) {
			loadItems();
			itemsLoaded = true;
			return;
		}
        if (!furnitureLoaded) {
            loadFurniture();
            furnitureLoaded = true;
        }
        if (!templatesLoaded) {
            loadTemplates();
            templatesLoaded = true;
        }
		std::list<std::pair<long, long> > output;
		while (!addQueue.empty()) {
			std::pair<long, long> item = addQueue.front();
			std::pair<long, long> slot = { item.first,
						       item.second };
			flecs::entity parent =
				ECS::get()
					.query_builder<const TerrainSlotParent>()
					.build()
					.find([&slot](const TerrainSlotParent
							      &parent) {
						return parent.slot == slot;
					});
			if (parent.is_valid()) {
				items.clear();
				ECS::get()
					.query_builder<const TerrainItem>()
					.with(flecs::ChildOf, parent)
					.without<TerrainItemNode>()
					.write<TerrainItemNode>()
					.build()
					.each([&](flecs::entity e,
						  const TerrainItem &item) {
						items.push_back(e);
					});
                for (auto e : items)
					createItemGeometry(e);
				addQueue.pop_front();
			} else {
				output.push_back(item);
				addQueue.pop_front();
			}
		}
		OgreAssert(addQueue.empty(), "queue is not empty");
		if (!output.empty())
			addQueue = output;
	});
}
void StaticGeometryModule::addGeometryForSlot(long x, long y)
{
    ZoneScoped;
    addQueue.push_back({ x, y });
}
void StaticGeometryModule::removeGeometryForSlot(long x, long y)
{
    ZoneScoped;
    std::pair<long, long> slot = { x, y };
	flecs::entity parent =
		ECS::get().query_builder<const TerrainSlotParent>().build().find(
			[&slot](const TerrainSlotParent &parent) {
				return parent.slot == slot;
			});
	if (parent.is_valid()) {
		ECS::get()
			.query_builder<const TerrainItem>()
			.with(flecs::ChildOf, parent)
			.without<TerrainItemNode>()
			.build()
			.each([](flecs::entity e, const TerrainItem &item) {
				OgreAssert(false, "Implement item geo destroy" +
							  item.properties);
			});
	}
}
flecs::entity
StaticGeometryModule::createItem(const Ogre::Vector3 &position,
				 const Ogre::Quaternion &orientation,
				 const Ogre::String &type)
{
    ZoneScoped;
    long x, y;
	ECS::get<Terrain>().mTerrainGroup->convertWorldPositionToTerrainSlot(
		position, &x, &y);
	std::pair<long, long> pos{ x, y };
	flecs::entity slot =
		ECS::get().query_builder<const TerrainSlotParent>().build().find(
			[&](const TerrainSlotParent &slot) -> bool {
				return slot.slot == pos;
			});
	if (!slot.is_valid())
		slot = ECS::get().entity().set<TerrainSlotParent>({ pos });
	flecs::entity item = ECS::get().entity().child_of(slot);
	nlohmann::json jproperties;
	jproperties["type"] = type;
	item.set<TerrainItem>({ position, orientation, jproperties.dump() });
	std::cout << "createItem: " << x << " " << y << " " << item.id()
		  << std::endl;
	return item;
}
void StaticGeometryModule::setItemProperties(flecs::entity id,
					     Ogre::String properties)
{
    ZoneScoped;
    OgreAssert(id.is_valid(), "bad id");
	id.get_mut<TerrainItem>().properties = properties;
	id.modified<TerrainItem>();
}
const Ogre::String &StaticGeometryModule::getItemProperties(flecs::entity id)
{
    ZoneScoped;
    OgreAssert(id.is_valid(), "bad id");
    return id.get<TerrainItem>().properties;
}

nlohmann::json templates;
void StaticGeometryModule::loadTemplates()
{
    ZoneScoped;
    if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
            "templates.list"))
        return;
    Ogre::String group =
        Ogre::ResourceGroupManager::getSingleton()
            .findGroupContainingResource("templates.list");
    Ogre::DataStreamPtr stream =
        Ogre::ResourceGroupManager::getSingleton().openResource(
            "templates.list", group);
    Ogre::String json = stream->getAsString();
    nlohmann::json jtemplates = nlohmann::json::parse(json);
    templates = jtemplates;
}

void StaticGeometryModule::saveTemplates()
{
    ZoneScoped;
    Ogre::String path = "resources/buildings/templates.list";
    if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
            "templates.list")) {
        Ogre::String group =
            Ogre::ResourceGroupManager::getSingleton()
                .findGroupContainingResource("templates.list");
        Ogre::FileInfoListPtr fileInfoList(
            Ogre::ResourceGroupManager::getSingleton()
                .findResourceFileInfo(group, "templates.list"));
        OgreAssert(fileInfoList->size() == 1,
               "templates.list should be there and only once");
        path = fileInfoList->at(0).archive->getName() + "/" +
               "templates.list";
        Ogre::FileSystemLayer::removeFile(path);
    }
    std::fstream fout(path.c_str(), std::ios::out);
    fout << templates.dump();
    fout.close();
}

void StaticGeometryModule::saveItems()
{
    ZoneScoped;
    Ogre::String path = "resources/buildings/items.list";
	if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
		    "items.list")) {
		Ogre::String group =
			Ogre::ResourceGroupManager::getSingleton()
				.findGroupContainingResource("items.list");
		Ogre::FileInfoListPtr fileInfoList(
			Ogre::ResourceGroupManager::getSingleton()
				.findResourceFileInfo(group, "items.list"));
		OgreAssert(fileInfoList->size() == 1,
			   "worpd_map.png should be there and only once");
		path = fileInfoList->at(0).archive->getName() + "/" +
		       "items.list";
		Ogre::FileSystemLayer::removeFile(path);
	}
	std::fstream fout(path.c_str(), std::ios::out);
	nlohmann::json jitemlist;
	ECS::get().query_builder<const TerrainItem>().build().each(
		[&](flecs::entity e, const TerrainItem &item) {
			nlohmann::json jitem;
			to_json(jitem["position"], item.position);
			to_json(jitem["orientation"], item.orientation);
			to_json(jitem["properties"], item.properties);
			jitem["objects"] = nlohmann::json::array();
			ECS::get()
				.query_builder<const TerrainItem>()
				.with(flecs::ChildOf, e)
				.build()
				.each([&](flecs::entity obje,
					  const TerrainItem &objItem) {
					nlohmann::json jobjitem;
					to_json(jobjitem["position"],
						objItem.position);
					to_json(jobjitem["orientation"],
						objItem.orientation);
					to_json(jobjitem["properties"],
						objItem.properties);
					jitem["objects"].push_back(jobjitem);
				});
			jitemlist.push_back(jitem);
		});
	fout << jitemlist.dump();
	fout.close();
}
void StaticGeometryModule::loadItems()
{
    ZoneScoped;
    if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
		    "items.list"))
		return;
	Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
				     .findGroupContainingResource("items.list");
	Ogre::DataStreamPtr stream =
		Ogre::ResourceGroupManager::getSingleton().openResource(
			"items.list", group);
	Ogre::String json = stream->getAsString();
	nlohmann::json jlist = nlohmann::json::parse(json);
	ECS::get().delete_with<TerrainItem>();
	ECS::get().delete_with<TerrainSlotParent>();

	for (const auto &v : jlist) {
		Ogre::Vector3 position;
		Ogre::Quaternion orientation;
		Ogre::String properties;
		from_json(v["position"], position);
		from_json(v["orientation"], orientation);
		properties = v["properties"].get<Ogre::String>();
		std::cout << v.dump(4) << std::endl;
		long x, y;
		ECS::get<Terrain>()
			.mTerrainGroup->convertWorldPositionToTerrainSlot(
				position, &x, &y);
		std::pair<long, long> pos{ x, y };
		flecs::entity slot =
			ECS::get()
				.query_builder<const TerrainSlotParent>()
				.build()
				.find([&](const TerrainSlotParent &slot)
					      -> bool {
					return slot.slot == pos;
				});
		if (!slot.is_valid())
			slot = ECS::get().entity().set<TerrainSlotParent>(
				{ pos });
		OgreAssert(slot.is_valid(), "Failed to create slot");
		flecs::entity item =
			ECS::get().entity().child_of(slot).set<TerrainItem>(
				{ position, orientation, properties });
		if (v.find("objects") != v.end()) {
			for (auto &obj : v["objects"]) {
				Ogre::Vector3 objposition;
				Ogre::Quaternion objorientation;
				Ogre::String objproperties;
				from_json(obj["position"], objposition);
				from_json(obj["orientation"], objorientation);
				objproperties =
					obj["properties"].get<Ogre::String>();
				flecs::entity itemObj =
					ECS::get()
						.entity()
						.child_of(item)
						.set<TerrainItem>(
							{ objposition,
							  objorientation,
							  objproperties });
			}
		}
		std::cout << "createItem: " << x << " " << y << " " << item.id()
			  << std::endl;
		std::cout << "position: " << item.id() << " " << position
			  << std::endl;
    }
}

void StaticGeometryModule::saveFurniture()
{
    /* No saving - furniture is generated by blender */
}

void StaticGeometryModule::loadFurniture()
{
    ZoneScoped;
    ECS::get().delete_with<FurnitureItem>();
    static std::vector<Ogre::String> glb_names;
    const std::vector<Ogre::String> &groups =
        Ogre::ResourceGroupManager::getSingleton().getResourceGroups();
    if (glb_names.size() == 0) {
        int i;
        for (i = 0; i < groups.size(); i++) {
            std::vector<Ogre::String> names =
                *Ogre::ResourceGroupManager::getSingleton()
                     .findResourceNames(
                         groups[i],
                         "furniture-*.glb.json");
            glb_names.insert(glb_names.end(), names.begin(),
                     names.end());
        }
    }
    for (auto &g : glb_names) {
        Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
                         .findGroupContainingResource(g);
        Ogre::DataStreamPtr stream =
            Ogre::ResourceGroupManager::getSingleton().openResource(
                g, group);
        Ogre::String json = stream->getAsString();
        nlohmann::json jdata = nlohmann::json::parse(json);
        std::vector<Ogre::String> tags;
        for (auto &tag : jdata["tags"]) {
            Ogre::String stag = tag.get<Ogre::String>();
            tags.push_back(stag);
        }
        Ogre::String meshName = jdata["mesh"].get<Ogre::String>();
        Ogre::MeshPtr mesh =
            Ogre::MeshManager::getSingleton().getByName(meshName);
        if (mesh) {
            Ogre::LodConfig meshconf(mesh);
            Geometry::setupLods(meshconf);
        }
        ECS::get().entity().set<FurnitureItem>({ json, tags });
        std::cout << "path: " << g << std::endl;
    }
}
void StaticGeometryModule::getItemPositionPerSlot(
    long x, long y, std::list<Ogre::Vector3> *positions)
{
    ZoneScoped;
    std::pair<long, long> pos{ x, y };
    if (!positions)
        return;
    flecs::entity slot =
        ECS::get().query_builder<const TerrainSlotParent>().build().find(
            [&](const TerrainSlotParent &slot) -> bool {
                return slot.slot == pos;
			});
    if (!slot.is_valid())
        return;
    ECS::get()
        .query_builder<const TerrainItem>()
        .with(flecs::ChildOf, slot)
        .build()
        .each([&](flecs::entity e, const TerrainItem &item) {
            positions->push_back(item.position);
        });
}
void StaticGeometryModule::getItemPositions(std::list<Ogre::Vector3> *positions)
{
    ZoneScoped;
    ECS::get().query_builder<const TerrainItem>().build().each(
        [&](flecs::entity e, const TerrainItem &item) {
            positions->push_back(item.position);
        });
}
void StaticGeometryModule::getItemPositionAndRotation(
    flecs::entity e, Ogre::Vector3 &position, Ogre::Quaternion &orientation)
{
    ZoneScoped;
    position = e.get<TerrainItem>().position;
    orientation = e.get<TerrainItem>().orientation;
}
void StaticGeometryModule::getItemsProperties(
    std::list<std::pair<flecs::entity, Ogre::String> > *items)
{
    ZoneScoped;
    ECS::get().query_builder<const TerrainItem>().build().each(
        [&](flecs::entity e, const TerrainItem &item) {
            items->push_back({ e, item.properties });
        });
}
void StaticGeometryModule::createItemGeometry(flecs::entity e)
{
    ZoneScoped;
    Geometry::createItemGeometry(e);
}

void StaticGeometryModule::destroyItemGeometry(flecs::entity e)
{
    ZoneScoped;
    Geometry::destroyItemGeometry(e);
}

nlohmann::json &StaticGeometryModule::getTemplates()
{
    return templates;
}

void StaticGeometryModule::updateItemGeometry(flecs::entity e)
{
    ZoneScoped;
    // We add this as task to reduce UI load
    Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([e]() {
        ZoneScopedN("updateItemGeometry");
        if (e.has<GeometryUpdateItem>())
            return;
        e.add<GeometryUpdateItem>();
        Geometry::updateItemGeometry(e);
        e.remove<GeometryUpdateItem>();
    });
}

void StaticGeometryModule::addTriangleBufferWork(
    const Ogre::String &meshName, Ogre::StaticGeometry *geo,
    const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
    const Procedural::TriangleBuffer &tb)
{
    ZoneScoped;
    struct WorkData {
        Ogre::String meshName;
        Ogre::StaticGeometry *geo;
        Ogre::Vector3 position;
        Ogre::Quaternion rotation;
        Procedural::TriangleBuffer tb;
    };
    WorkData data = { meshName, geo, position, rotation, tb };
    // We add this as task to reduce UI load
    Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([data]() {
        ZoneScopedN("addTriangleBufferWork");
        Ogre::MeshPtr mesh = data.tb.transformToMesh(data.meshName);
        Ogre::Entity *ent =
            ECS::get<EngineData>().mScnMgr->createEntity(mesh);
        data.geo->addEntity(ent, data.position, data.rotation);
        ECS::get<EngineData>().mScnMgr->destroyEntity(ent);
    });
}
struct TiledMeshes {
    struct Tile {
        Ogre::String materialName;
        std::shared_ptr<Ogre::VertexData> vertexData;
        std::shared_ptr<Ogre::IndexData> indexData;
        std::set<uint32_t> positions;
    };
    std::map<Ogre::String, Tile> tiles;
    uint32_t packKey(const Ogre::Vector3i &position)
	{
        uint32_t key = 0;
        key |= (uint32_t)(position[2] + 512) << 20;
        key |= (uint32_t)(position[1] + 512) << 10;
        key |= (uint32_t)(position[0] + 512) << 0;
        return key;
	}
    void unpackKey(uint32_t key, Ogre::Vector3i &position)
	{
        uint32_t mask = 0x3ff;
        position[0] = (int)(key & mask) - 512;
        position[1] = (int)((key >> 10) & mask) - 512;
        position[2] = (int)((key >> 20) & mask) - 512;
	}
    void setTile(const Ogre::String &name, const Ogre::Vector3i &position)
	{
        if (tiles.find(name) == tiles.end())
            return;
        tiles[name].positions.insert(packKey(position));
	}
    void clearTile(const Ogre::String &name, const Ogre::Vector3i &position)
	{
        if (tiles.find(name) == tiles.end())
            return;
        tiles[name].positions.erase(packKey(position));
    }
    void addTile(const Ogre::String &name, Ogre::MeshPtr mesh)
    {
        if (mesh->getSubMeshes().size() != 1)
            return;
        Ogre::SubMesh *submesh = mesh->getSubMesh(0);
        Ogre::VertexData *vertexData;
        if (submesh->useSharedVertices)
            vertexData = mesh->sharedVertexData->clone();
        else
            vertexData = submesh->vertexData->clone();
        tiles[name] = { submesh->getMaterialName(),
                std::shared_ptr<Ogre::VertexData>(vertexData),
                std::shared_ptr<Ogre::IndexData>(
                    submesh->indexData->clone()),
                {} };
    }
    struct buildSettings {
        Ogre::String meshName;
        Ogre::String materialName;
        Ogre::MeshPtr mesh;
        Ogre::SubMesh *sm;
        int vertexCount, vertexOffset;
        int indexCount, indexOffset;
        Ogre::VertexDeclaration *vdecl;
        Ogre::HardwareVertexBufferSharedPtr vbuf;
        Ogre::AxisAlignedBox bounds;
        bool setBounds;
    };
    void configureSettings(struct buildSettings &settings)
    {
        settings.mesh = Ogre::MeshManager::getSingleton().createManual(
            settings.meshName,
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
        settings.mesh->createVertexData();
        settings.sm = settings.mesh->createSubMesh();
        settings.vertexCount = 0;
        settings.indexCount = 0;
        settings.vdecl = nullptr;
        for (const auto &tile : tiles) {
            settings.vertexCount +=
                tile.second.vertexData->vertexCount *
                tile.second.positions.size();
            settings.indexCount +=
                tile.second.indexData->indexCount *
                tile.second.positions.size();
            if (!settings.vdecl) {
                settings.vdecl =
                    tile.second.vertexData
                        ->vertexDeclaration->clone();
                settings.materialName =
                    tile.second.materialName;
			}
		}
        settings.mesh->sharedVertexData->vertexStart = 0;
        settings.mesh->sharedVertexData->vertexCount =
            settings.vertexCount;
        settings.mesh->sharedVertexData->vertexDeclaration =
            settings.vdecl;
        settings.vbuf =
            Ogre::HardwareBufferManager::getSingleton()
                .createVertexBuffer(
                    settings.vdecl->getVertexSize(0),
                    settings.vertexCount,
                    Ogre::HBU_GPU_ONLY);
        settings.mesh->sharedVertexData->vertexBufferBinding->setBinding(
            0, settings.vbuf);
        settings.sm->indexData->indexStart = 0;
        settings.sm->indexData->indexCount = settings.indexCount;
        settings.sm->indexData->indexBuffer =
            Ogre::HardwareBufferManager::getSingleton()
                .createIndexBuffer(
                    Ogre::HardwareIndexBuffer::IT_32BIT,
                    settings.sm->indexData->indexCount * 8,
                    Ogre::HBU_GPU_ONLY);
        settings.sm->setMaterialName(settings.materialName);
        settings.setBounds = true;
    }
    void processIndex(struct buildSettings &settings,
              const struct Tile &tile, unsigned int *dstIndices)
    {
        int j;
        std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
        int srcIndexCount = srcIndexData->indexCount;
        std::shared_ptr<Ogre::VertexData> srcVertexData =
            tile.vertexData;
        int srcVertexCount = srcVertexData->vertexCount;
        Ogre::HardwareIndexBufferSharedPtr srcIbuf =
            srcIndexData->indexBuffer;
        Ogre::HardwareBufferLockGuard srcIndexLock(
            srcIbuf, Ogre::HardwareBuffer::HBL_READ_ONLY);
        if (srcIndexData->indexBuffer->getType() ==
            Ogre::HardwareIndexBuffer::IT_32BIT) {
            unsigned int *indices =
                static_cast<unsigned int *>(srcIndexLock.pData);
            for (j = 0; j < srcIndexCount; j++)
                dstIndices[settings.indexOffset + j] =
                    indices[j] + settings.vertexOffset;
        } else if (srcIndexData->indexBuffer->getType() ==
               Ogre::HardwareIndexBuffer::IT_16BIT) {
            unsigned short *indices = static_cast<unsigned short *>(
                srcIndexLock.pData);
            for (j = 0; j < srcIndexCount; j++)
                dstIndices[settings.indexOffset + j] =
                    indices[j] + settings.vertexOffset;
        }
    }
    void processSingleVertex(
        struct buildSettings &settings,
        const Ogre::VertexDeclaration::VertexElementList &srcElements,
        uint32_t offset, unsigned char *srcData, unsigned char *dstData)
    {
        for (const auto &srcElement : srcElements) {
            unsigned char *srcPtr, *dstPtr;
            const Ogre::VertexElement *destElement =
                settings.vdecl->findElementBySemantic(
                    srcElement.getSemantic(),
                    srcElement.getIndex());
            if (!destElement)
                goto out;
            if (srcElement.getType() != destElement->getType() ||
                srcElement.getSize() != destElement->getSize())
                goto out;
            srcPtr = srcData + srcElement.getOffset();
            dstPtr = dstData + destElement->getOffset();
            if (destElement->getSemantic() == Ogre::VES_POSITION) {
                float *srcPositionData =
                    reinterpret_cast<float *>(srcPtr);
                float *dstPositionData =
                    reinterpret_cast<float *>(dstPtr);
                Ogre::Vector3 position(srcPositionData[0],
                               srcPositionData[1],
                               srcPositionData[2]);
                Ogre::Vector3i offsetv;
                unpackKey(offset, offsetv);
                position.x += (float)offsetv[0];
                position.y += (float)offsetv[1];
                position.z += (float)offsetv[2];
                dstPositionData[0] = position.x;
                dstPositionData[1] = position.y;
                dstPositionData[2] = position.z;
                if (settings.setBounds) {
                    settings.bounds.setMinimum(position);
                    settings.bounds.setMaximum(position);
                    settings.setBounds = false;
				} else
                    settings.bounds.merge(position);
            } else if (destElement->getSemantic() ==
                   Ogre::VES_NORMAL) {
                float *srcNormalData =
                    reinterpret_cast<float *>(srcPtr);
                float *dstNormalData =
                    reinterpret_cast<float *>(dstPtr);
                Ogre::Vector3 normal(srcNormalData[0],
                             srcNormalData[1],
                             srcNormalData[2]);
                dstNormalData[0] = normal.x;
                dstNormalData[1] = normal.y;
                dstNormalData[2] = normal.z;
            } else
                memcpy(dstPtr, srcPtr, srcElement.getSize());
out:;
        }
    }
    void processTile(struct buildSettings &settings,
             const struct Tile &tile, uint32_t position,
             unsigned char *dstpData)
    {
        std::shared_ptr<Ogre::VertexData> srcVertexData =
            tile.vertexData;
        const Ogre::VertexDeclaration *srcDecl =
            srcVertexData->vertexDeclaration;
        const Ogre::VertexBufferBinding *srcBind =
            srcVertexData->vertexBufferBinding;
        int srcVertexCount = srcVertexData->vertexCount;
        Ogre::HardwareVertexBufferSharedPtr srcVbuf =
            srcBind->getBuffer(0);
        std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
        int srcIndexCount = srcIndexData->indexCount;

        Ogre::HardwareBufferLockGuard srcVertexLock(
            srcVbuf, 0, srcVertexCount * srcDecl->getVertexSize(0),
            Ogre::HardwareBuffer::HBL_READ_ONLY);
        const Ogre::VertexDeclaration::VertexElementList &srcElements =
            srcDecl->getElements();
        int j;
        unsigned char *srcpData =
            static_cast<unsigned char *>(srcVertexLock.pData);

        for (j = 0; j < srcVertexCount; j++) {
            unsigned char *srcData =
                srcpData + j * srcVbuf->getVertexSize();
            unsigned char *dstData =
                dstpData + (settings.vertexOffset +
                        j) * settings.vbuf->getVertexSize();
            processSingleVertex(settings, srcElements, position,
                        srcData, dstData);
		}
    }
    Ogre::MeshPtr build(const Ogre::String &meshName)
    {
        buildSettings settings;
        settings.meshName = meshName;
        configureSettings(settings);
		{
            Ogre::HardwareBufferLockGuard vertexLock(
                settings.vbuf, 0,
                settings.vertexCount *
                    settings.vdecl->getVertexSize(0),
                Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
            Ogre::HardwareBufferLockGuard indexLock(
                settings.sm->indexData->indexBuffer,
                Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
            settings.vertexOffset = 0;
            settings.indexOffset = 0;
            unsigned char *dstpData =
                static_cast<unsigned char *>(vertexLock.pData);
            unsigned int *dstIndices =
                static_cast<unsigned int *>(indexLock.pData);

            for (const auto &tile : tiles) {
                std::shared_ptr<Ogre::IndexData> srcIndexData =
                    tile.second.indexData;
                int srcIndexCount = srcIndexData->indexCount;
                std::shared_ptr<Ogre::VertexData> srcVertexData =
                    tile.second.vertexData;
                int srcVertexCount = srcVertexData->vertexCount;
                for (const auto &position :
                     tile.second.positions) {
                    processTile(settings, tile.second,
                            position, dstpData);
                    processIndex(settings, tile.second,
                             dstIndices);
                    settings.vertexOffset += srcVertexCount;
                    settings.indexOffset += srcIndexCount;
                    Ogre::Vector3i vposition;
                    unpackKey(position, vposition);
                    std::cout << "position: " << position
                          << " " << vposition
                          << std::endl;
				}
            }
            settings.mesh->_setBounds(settings.bounds);
        }
        Ogre::LodConfig config(settings.mesh);
        // config.advanced.useCompression = false;
        // config.advanced.useVertexNormals = true;
        config.advanced.preventPunchingHoles = true;
        config.advanced.preventBreakingLines = true;
        config.createGeneratedLodLevel(2, 0.15f);
        config.createGeneratedLodLevel(20, 0.49f);
		config.createGeneratedLodLevel(15, 0.49f);
		config.createGeneratedLodLevel(150, 0.75f);
        config.advanced.useBackgroundQueue = false;
        Ogre::MeshLodGenerator::getSingleton().generateLodLevels(
            config);
        return settings.mesh;
    }
    void removeTile(const Ogre::String &name)
    {
        tiles.erase(name);
    }
    TiledMeshes()
    {
    }
};
}
