#include "Chunk.h"
#include <vector>
#include <iostream>

const int VERTEX_SIZE = 7; // x, y, z, nx, ny, nz, voxelKind

const float topFace[24] = {
    // x, y, z, nx, ny, nz
    -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
     0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
     0.5f, 0.5f,  0.5f, 0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f,  0.5f, 0.0f, 1.0f, 0.0f
};
const float bottomFace[24] = {
    -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
     0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 0.0f, -1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, -1.0f, 0.0f
};
const float rightFace[24] = {
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 0.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
     0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 0.0f
};
const float leftFace[24] = {
    -0.5f,  0.5f,  0.5f, -1.0f, 0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f,  0.5f, -1.0f, 0.0f, 0.0f
};
const float frontFace[24] = {
    -0.5f,  0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
     0.5f,  0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
     0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
    -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
const float backFace[24] = {
    -0.5f,  0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
     0.5f,  0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
     0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f
};

void addFace(glm::ivec3 pos, const float *faceVertices, float voxelKind, std::vector<float> &vbo, std::vector<unsigned int> &ebo) {
    unsigned int vIndex = vbo.size() / VERTEX_SIZE;
    for (int i = 0; i < 4; i++) {
        const float* v = &faceVertices[i * 6]; // Face data is still 6 floats per vertex
        float world_x = v[0] + pos.x;
        float world_y = v[1] + pos.y;
        float world_z = v[2] + pos.z;

        // Debug: Print first vertex of first face
        // if (vbo.empty() && i == 0) {
            // std::cout << "[MESH] First vertex - Local pos: (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
            // std::cout << "[MESH] First vertex - World: (" << world_x << ", " << world_y << ", " << world_z << ")" << std::endl;
        // }

        vbo.push_back(world_x);
        vbo.push_back(world_y);
        vbo.push_back(world_z);
        vbo.push_back(v[3]);
        vbo.push_back(v[4]);
        vbo.push_back(v[5]);
        vbo.push_back(voxelKind); // Add voxel kind as 7th component
    }
    ebo.push_back(vIndex + 0);
    ebo.push_back(vIndex + 1);
    ebo.push_back(vIndex + 2);
    ebo.push_back(vIndex + 0);
    ebo.push_back(vIndex + 2);
    ebo.push_back(vIndex + 3);
}

Chunk::Chunk() {
    this->data.fill(VoxelKind::Air);
}

Mesh* Chunk::getMesh() {
    // Generate mesh on first access if not already generated, or if dirty
    if ((!mesh || mesh_dirty) && !this->isEmpty()) {
        generateMesh();
        mesh_dirty = false;
    }
    return mesh.get();
}

void Chunk::generateMesh() {
    std::vector<float> vboData;
    std::vector<unsigned int> eboData;

    // std::cout << "Generating Chunk mesh..." << std::endl;

    const std::pair<glm::ivec3, const float*> neighbors[6] = {
        std::pair(glm::ivec3(0, 1, 0), topFace),        // top
        std::pair(glm::ivec3(0, -1, 0), bottomFace),    // bottom
        std::pair(glm::ivec3(1, 0, 0), rightFace),      // right
        std::pair(glm::ivec3(-1, 0, 0), leftFace),      // left
        std::pair(glm::ivec3(0, 0, 1), frontFace),      // front
        std::pair(glm::ivec3(0, 0, -1), backFace)       // back
    };

    for (int i = 0; i < TOTAL; ++i) {
        VoxelKind kind = this->data[i];
        if (kind == VoxelKind::Air) continue;

        int x = i % SIZE;
        int y = (i / SIZE) % SIZE;
        int z = i / (SIZE * SIZE);

        glm::ivec3 pos(x, y, z);

        for (const auto& neighborPair : neighbors) {
            glm::ivec3 dir = neighborPair.first;
            const float* face = neighborPair.second;
            int nx = x + dir.x;
            int ny = y + dir.y;
            int nz = z + dir.z;

            bool neighbour_is_solid = false;
            // in-bounds neighbor -> check data
            if (nx >= 0 && nx < SIZE && ny >= 0 && ny < SIZE && nz >= 0 && nz < SIZE) {
                // std::cout << "Accessing " << nx << ", " << ny << ", " << nz << std::endl;
                neighbour_is_solid = (data[index(nx, ny, nz)] != VoxelKind::Air);
            } else {
                // neighbour out of this chunk: treat as air (exposed face)
                // or call into World to query adjacent chunk here
                neighbour_is_solid = false;
            }

            if (!neighbour_is_solid) {
                addFace(pos, face, static_cast<float>(kind), vboData, eboData);
            }
        }
    }

    // Create and initialize mesh if it doesn't exist
    if (!mesh) {
        mesh = std::make_unique<Mesh>();
        mesh->init();
    }

    // Now, upload this data to our mesh object
    mesh->uploadData(vboData, eboData);
}
