//
// Created by Tristan on 11/24/2025.
//

#include "mesh.h"

#include "../logger/log.h"

GroupMesh create_mesh_from_group(const WMOGroupData* group, const WMORootData* root_data) {
    GroupMesh mesh = {0};

    if (!group->movt_data_ptr || !group->movi_data_ptr) {
        log_warn("Group has vertex flag but no MOVT/MOVI data");
        return mesh;
    }

    size_t num_vertices = group->movt_size / sizeof(C3Vector);
    size_t num_indices = group->movi_size / sizeof(uint16_t);

    Vertex* vertices = (Vertex*)malloc(num_vertices * sizeof(Vertex));
    if (!vertices) {
        log_error("Failed to allocate memory for mesh generation");
        return mesh;
    }

    const C3Vector* positions = (const C3Vector*)group->movt_data_ptr;
    const C3Vector* normals = (const C3Vector*)group->monr_data_ptr;
    const C2Vector* texCoords = (const C2Vector*)group->motv_data_ptr;

    // TODO: double check if we need the offset here, I believe its
    GLfloat offset_x = group->header.boundingBox.min[0];
    GLfloat offset_y = group->header.boundingBox.min[1];
    GLfloat offset_z = group->header.boundingBox.min[2];

    for (size_t i = 0; i < num_vertices; i++) {
        //float wx = positions[i].x + offset_x;
        //float wy = positions[i].y + offset_y;
        //float wz = positions[i].z + offset_z;

        float wx = positions[i].x;
        float wy = positions[i].y;
        float wz = positions[i].z;


        vertices[i].position.x = wx;
        vertices[i].position.y = wy;
        vertices[i].position.z = wz;

        if (normals) {
            vertices[i].normal.x = normals[i].x;
            vertices[i].normal.y = normals[i].y;
            vertices[i].normal.z = normals[i].z;
        } else {
            vertices[i].normal = (C3Vector){0.0f, 1.0f, 0.0f};
        }

        if (texCoords) {
            vertices[i].texCoord.x = texCoords[i].x;
            vertices[i].texCoord.y = texCoords[i].y;
        } else {
            vertices[i].texCoord = (C2Vector){0.0f, 0.0f};
        }
    }

    if (num_vertices > 0) {
        float minX = vertices[0].position.x, maxX = vertices[0].position.x;
        float minY = vertices[0].position.y, maxY = vertices[0].position.y;
        float minZ = vertices[0].position.z, maxZ = vertices[0].position.z;

        for (size_t k = 0; k < num_vertices; k++) {
            if (vertices[k].position.x < minX) minX = vertices[k].position.x;
            if (vertices[k].position.x > maxX) maxX = vertices[k].position.x;
            if (vertices[k].position.y < minY) minY = vertices[k].position.y;
            if (vertices[k].position.y > maxY) maxY = vertices[k].position.y;
            if (vertices[k].position.z < minZ) minZ = vertices[k].position.z;
            if (vertices[k].position.z > maxZ) maxZ = vertices[k].position.z;
        }

        log_info("MESH DEBUG: Group has %zu vertices", num_vertices);
        // This is the critical line. It tells us where the mesh actually IS.
        log_info("BOUNDS: X[%.1f to %.1f] Y[%.1f to %.1f] Z[%.1f to %.1f]",
                 minX, maxX, minY, maxY, minZ, maxZ);
    }

    if (group->moba_data_ptr && group->moba_size > 0) {
        const SMOBatch* wmo_batches = (const SMOBatch*)group->moba_data_ptr;
        size_t num_batches = group->moba_size / sizeof(SMOBatch);

        mesh.batches = (RenderBatch*)malloc(num_batches * sizeof(RenderBatch));
        mesh.batchCount = num_batches;

        for (size_t i = 0; i < num_batches; i++) {
            mesh.batches[i].indexOffset = wmo_batches[i].startIndex;
            mesh.batches[i].indexCount = wmo_batches[i].count;

            // lookup texture id
            uint8_t mat_id = wmo_batches[i].material_id;

            if (root_data && root_data->material_textures) {
                mesh.batches[i].textureID = root_data->material_textures[mat_id];
            } else {
                mesh.batches[i].textureID = 0;
            }
        }
    } else {
        // Fallback if none
        mesh.batchCount = 1;
        mesh.batches = (RenderBatch*)malloc(sizeof(RenderBatch));
        mesh.batches[0].indexOffset = 0;
        mesh.batches[0].indexCount = num_indices;
        mesh.batches[0].textureID = 0;
    }

    // create buffers
    glGenVertexArrays(1, &mesh.VAO);
    glGenBuffers(1, &mesh.VBO);
    glGenBuffers(1, &mesh.EBO);

    glBindVertexArray(mesh.VAO);

    // vbo
    glBindBuffer(GL_ARRAY_BUFFER, mesh.VBO);
    glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(Vertex), vertices, GL_STATIC_DRAW);

    // ebo
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, group->movi_size, group->movi_data_ptr, GL_STATIC_DRAW);

    // attributes
    // 0: Position
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
    glEnableVertexAttribArray(0);
    // 1: Normal
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)sizeof(C3Vector));
    glEnableVertexAttribArray(1);
    // 2: TexCoord
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(2 * sizeof(C3Vector)));
    glEnableVertexAttribArray(2);

    glBindVertexArray(0);

    free(vertices);

    return mesh;
}

void draw_group_mesh(GroupMesh mesh) {
    if (mesh.VAO == 0)
        return;

    glBindVertexArray(mesh.VAO);

    for (size_t i = 0; i < mesh.batchCount; i++) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mesh.batches[i].textureID);

        // The last argument is the byte offset into the EBO
        // multiply indexOffset * sizeof(uint16_t) because OpenGL expects bytes
        void* offset = (void*)(uintptr_t)(mesh.batches[i].indexOffset * sizeof(uint16_t));
        glDrawElements(GL_TRIANGLES, (GLsizei)mesh.batches[i].indexCount, GL_UNSIGNED_SHORT, offset);
    }


    glBindVertexArray(0);
}

void free_group_mesh(GroupMesh *mesh) {
    if (mesh->VAO) glDeleteVertexArrays(1, &mesh->VAO);
    if (mesh->VBO) glDeleteBuffers(1, &mesh->VBO);
    if (mesh->EBO) glDeleteBuffers(1, &mesh->EBO);

    if (mesh->batches) {
        free(mesh->batches);
        mesh->batches = NULL;
    }

    mesh->VAO = 0;
    mesh->batchCount = 0;
}