#include "ProceduralMeshSystem.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/Primitive.hpp"
#include "../components/ProceduralTexture.hpp"
#include "../components/ProceduralMaterial.hpp"
#include "../components/Transform.hpp"
#include "../components/StaticGeometry.hpp"
#include "../components/StaticGeometryMember.hpp"
#include <OgreSceneManager.h>
#include <OgreMeshManager.h>
#include <OgreEntity.h>
#include <OgreSubMesh.h>
#include <OgreLogManager.h>
#include <ProceduralTriangleBuffer.h>
#include <ProceduralBoxGenerator.h>
#include <ProceduralPlaneGenerator.h>

ProceduralMeshSystem::ProceduralMeshSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
    : m_world(world)
    , m_sceneMgr(sceneMgr)
    , m_triangleBufferQuery(world.query<TriangleBufferComponent>())
    , m_primitiveQuery(world.query<PrimitiveComponent>())
{
}

ProceduralMeshSystem::~ProceduralMeshSystem() = default;

void ProceduralMeshSystem::initialize()
{
    if (m_initialized) return;
    m_initialized = true;
    
    Ogre::LogManager::getSingleton().logMessage("ProceduralMeshSystem initialized");
}

void ProceduralMeshSystem::update()
{
    if (!m_initialized) return;

    // First pass: mark TriangleBuffers dirty if any child primitives are dirty
    m_triangleBufferQuery.each([&](flecs::entity entity, TriangleBufferComponent& tb) {
        if (arePrimitivesDirty(entity)) {
            tb.markDirty();
        }
    });

    // Second pass: rebuild dirty triangle buffers
    m_triangleBufferQuery.each([&](flecs::entity entity, TriangleBufferComponent& tb) {
        if (tb.dirty) {
            buildTriangleBuffer(entity, tb);
            if (tb.buffer && hasValidTriangles(tb.buffer.get())) {
                convertToMesh(entity, tb);
            }
        }
    });
}

bool ProceduralMeshSystem::arePrimitivesDirty(flecs::entity parent)
{
    bool dirty = false;
    
    // Check all children for PrimitiveComponent
    parent.children([&](flecs::entity child) {
        if (child.has<PrimitiveComponent>()) {
            const auto& prim = child.get<PrimitiveComponent>();
            if (prim.dirty) {
                dirty = true;
                return;
            }
            // Check if transform has changed
            if (child.has<TransformComponent>()) {
                const auto& transform = child.get<TransformComponent>();
                // Get stored transform version from primitive's user data or check directly
                // We'll use a simple approach: store last known version in the primitive
                auto& primMut = child.get_mut<PrimitiveComponent>();
                if (transform.version != primMut.lastTransformVersion) {
                    dirty = true;
                }
            }
        }
    });
    
    return dirty;
}

void ProceduralMeshSystem::buildTriangleBuffer(flecs::entity entity, TriangleBufferComponent& tb)
{
    // Create new triangle buffer
    tb.buffer = std::make_shared<Procedural::TriangleBuffer>();
    
    // Collect all primitive children
    std::vector<std::pair<flecs::entity, PrimitiveComponent*>> primitives;
    
    entity.children([&](flecs::entity child) {
        if (child.has<PrimitiveComponent>()) {
            auto& prim = child.get_mut<PrimitiveComponent>();
            primitives.emplace_back(child, &prim);
            prim.dirty = false;  // Mark as processed
        }
    });
    
    // Build each primitive into the triangle buffer
    for (auto& [primEntity, prim] : primitives) {
        if (prim->type == PrimitiveComponent::Type::None) continue;
        
        // Get transform from the primitive entity
        Ogre::Vector3 position = Ogre::Vector3::ZERO;
        Ogre::Quaternion orientation = Ogre::Quaternion::IDENTITY;
        
        if (primEntity.has<TransformComponent>()) {
            const auto& transform = primEntity.get<TransformComponent>();
            if (transform.node) {
                position = transform.node->getPosition();
                orientation = transform.node->getOrientation();
            }
        }
        
        try {
            Procedural::TriangleBuffer primBuffer;
            
            switch (prim->type) {
                case PrimitiveComponent::Type::Box: {
                    Procedural::BoxGenerator boxGen;
                    boxGen.setSizeX(prim->boxSizeX);
                    boxGen.setSizeY(prim->boxSizeY);
                    boxGen.setSizeZ(prim->boxSizeZ);
                    boxGen.addToTriangleBuffer(primBuffer);
                    break;
                }
                case PrimitiveComponent::Type::Plane: {
                    Procedural::PlaneGenerator planeGen;
                    planeGen.setSizeX(prim->planeSizeX);
                    planeGen.setSizeY(prim->planeSizeY);
                    planeGen.setNumSegX(prim->planeSegmentsX);
                    planeGen.setNumSegY(prim->planeSegmentsY);
                    planeGen.addToTriangleBuffer(primBuffer);
                    break;
                }
                default:
                    break;
            }
            
            // Transform vertices
            auto& vertices = primBuffer.getVertices();
            for (auto& vertex : vertices) {
                Ogre::Vector3 pos = vertex.mPosition;
                pos = orientation * pos + position;
                vertex.mPosition = pos;
            }
            
            // Merge into main buffer
            tb.buffer->append(primBuffer);
            
            // Update transform version tracking
            if (primEntity.has<TransformComponent>()) {
                const auto& transform = primEntity.get<TransformComponent>();
                prim->lastTransformVersion = transform.version;
            }
            
        } catch (const std::exception& e) {
            Ogre::LogManager::getSingleton().logMessage(
                "ProceduralMesh: Failed to build primitive: " + Ogre::String(e.what()));
        }
    }
    
    tb.dirty = false;
    
    Ogre::LogManager::getSingleton().logMessage(
        "ProceduralMesh: Built triangle buffer with " + 
        std::to_string(tb.buffer->getVertices().size()) + " vertices");
}

bool ProceduralMeshSystem::hasValidTriangles(Procedural::TriangleBuffer* buffer)
{
    if (!buffer) return false;
    
    const auto& indices = buffer->getIndices();
    if (indices.size() < 3) return false;
    
    // Check for at least one non-degenerate triangle
    for (size_t i = 0; i + 2 < indices.size(); i += 3) {
        const auto& v0 = buffer->getVertices()[indices[i]];
        const auto& v1 = buffer->getVertices()[indices[i + 1]];
        const auto& v2 = buffer->getVertices()[indices[i + 2]];
        
        // Check if triangle has non-zero area
        Ogre::Vector3 e1 = v1.mPosition - v0.mPosition;
        Ogre::Vector3 e2 = v2.mPosition - v0.mPosition;
        
        if (e1.crossProduct(e2).squaredLength() > 0.0001f) {
            return true;  // Found valid triangle
        }
    }
    
    return false;
}

void ProceduralMeshSystem::applyUVMapping(Procedural::TriangleBuffer* buffer, 
                                           ProceduralTextureComponent& texture,
                                           const std::string& rectName)
{
    if (!buffer || rectName.empty()) return;
    
    const TextureRectInfo* rect = texture.getNamedRect(rectName);
    if (!rect) return;
    
    // Calculate UV mapping with configurable margin
    const float margin = texture.uvMargin;
    float uRange = (rect->u2 - rect->u1) * (1.0f - 2.0f * margin);
    float vRange = (rect->v2 - rect->v1) * (1.0f - 2.0f * margin);
    float uOffset = rect->u1 + (rect->u2 - rect->u1) * margin;
    float vOffset = rect->v1 + (rect->v2 - rect->v1) * margin;
    
    auto& vertices = buffer->getVertices();
    for (auto& vertex : vertices) {
        // Map UV from [0,1] to [rect.u1, rect.u2] with margin
        vertex.mUV.x = uOffset + vertex.mUV.x * uRange;
        vertex.mUV.y = vOffset + vertex.mUV.y * vRange;
    }
}

void ProceduralMeshSystem::destroyMesh(TriangleBufferComponent& tb)
{
    if (tb.ogreEntity) {
        try {
            m_sceneMgr->destroyEntity(tb.ogreEntity);
        } catch (...) {}
        tb.ogreEntity = nullptr;
    }
    
    if (!tb.meshName.empty()) {
        try {
            Ogre::MeshManager::getSingleton().remove(tb.meshName);
        } catch (...) {}
    }
    
    tb.meshCreated = false;
}

void ProceduralMeshSystem::convertToMesh(flecs::entity entity, TriangleBufferComponent& tb)
{
    if (!tb.buffer) return;
    
    // Apply UV mapping if texture and rect are specified
    if (tb.textureEntity.is_alive() && tb.textureEntity.has<ProceduralTextureComponent>()) {
        auto& texture = tb.textureEntity.get_mut<ProceduralTextureComponent>();
        applyUVMapping(tb.buffer.get(), texture, tb.textureRectName);
    }
    
    // Generate mesh name
    if (tb.meshName.empty()) {
        tb.meshName = "ProceduralMesh_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
    }
    
    // Destroy old mesh if exists
    destroyMesh(tb);
    
    try {
        // Create Ogre mesh from triangle buffer
        Ogre::MeshPtr mesh = tb.buffer->transformToMesh(tb.meshName);
        
        // Apply material if specified
        if (tb.materialEntity.is_alive() && tb.materialEntity.has<ProceduralMaterialComponent>()) {
            const auto& material = tb.materialEntity.get<ProceduralMaterialComponent>();
            if (material.created && !material.materialName.empty()) {
                mesh->getSubMesh(0)->setMaterialName(material.materialName);
            }
        }
        
        // Either attach to SceneNode or StaticGeometry
        if (tb.useStaticGeometry && tb.staticGeometryEntity.is_alive()) {
            // Add to StaticGeometry as a member
            attachToStaticGeometry(entity, tb);
        } else {
            // Attach to entity's SceneNode
            if (entity.has<TransformComponent>()) {
                auto& transform = entity.get_mut<TransformComponent>();
                if (transform.node) {
                    tb.ogreEntity = m_sceneMgr->createEntity(tb.meshName);
                    transform.node->attachObject(tb.ogreEntity);
                }
            }
        }
        
        tb.meshCreated = true;
        
        Ogre::LogManager::getSingleton().logMessage(
            "ProceduralMesh: Created mesh '" + tb.meshName + "'");
            
    } catch (const Ogre::Exception& e) {
        Ogre::LogManager::getSingleton().logMessage(
            "ProceduralMesh ERROR: Failed to create mesh: " + e.getDescription());
    }
}

void ProceduralMeshSystem::rebuildTriangleBuffer(flecs::entity entity)
{
    if (!entity.is_alive() || !entity.has<TriangleBufferComponent>()) {
        return;
    }
    
    entity.get_mut<TriangleBufferComponent>().markDirty();
}

void ProceduralMeshSystem::attachToStaticGeometry(flecs::entity entity, TriangleBufferComponent& tb)
{
    if (!tb.staticGeometryEntity.is_alive() || !tb.staticGeometryEntity.has<StaticGeometryComponent>()) {
        Ogre::LogManager::getSingleton().logMessage(
            "ProceduralMesh: StaticGeometry entity invalid or missing StaticGeometryComponent");
        return;
    }
    
    auto& region = tb.staticGeometryEntity.get_mut<StaticGeometryComponent>();
    
    // Get or create StaticGeometryMemberComponent on the TriangleBuffer entity
    StaticGeometryMemberComponent* member = nullptr;
    if (entity.has<StaticGeometryMemberComponent>()) {
        member = &entity.get_mut<StaticGeometryMemberComponent>();
    } else {
        // Add the component
        entity.add<StaticGeometryMemberComponent>();
        member = &entity.get_mut<StaticGeometryMemberComponent>();
    }
    
    // Configure the member
    member->regionEntity = tb.staticGeometryEntity;
    member->regionId = region.regionId;
    member->meshName = tb.meshName;
    member->castShadows = true;
    member->visible = true;
    member->dirty = true;
    
    // Get material name if available
    if (tb.materialEntity.is_alive() && tb.materialEntity.has<ProceduralMaterialComponent>()) {
        const auto& material = tb.materialEntity.get<ProceduralMaterialComponent>();
        if (material.created && !material.materialName.empty()) {
            member->materialName = material.materialName;
        }
    }
    
    // Mark the region as dirty to trigger rebuild
    region.markDirty();
    
    Ogre::LogManager::getSingleton().logMessage(
        "ProceduralMesh: Attached mesh '" + tb.meshName + "' to StaticGeometry region '" + 
        region.regionName + "'");
}
