// src/Mesh.cpp

#include "Mesh.h"
#include <glad.h>
#include <iostream>
#include <cmath>

// Constructor using spans
Mesh::Mesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices)
    : vertex_count_(vertices.size()), index_count_(indices.size()) {
    SetupMesh(vertices, indices);
}

// Constructor using moved vectors
Mesh::Mesh(std::vector<Vertex>&& vertices, std::vector<unsigned int>&& indices)
    : vertex_count_(vertices.size()), index_count_(indices.size()) {
    SetupMesh(vertices, indices);
}

// Move constructor
Mesh::Mesh(Mesh&& other) noexcept
    : VAO_(other.VAO_), VBO_(other.VBO_), EBO_(other.EBO_),
      vertex_count_(other.vertex_count_), index_count_(other.index_count_) {
    other.VAO_ = other.VBO_ = other.EBO_ = 0;
    other.vertex_count_ = other.index_count_ = 0;
}

// Move assignment
Mesh& Mesh::operator=(Mesh&& other) noexcept {
    if (this != &other) {
        Cleanup();
        VAO_ = other.VAO_;
        VBO_ = other.VBO_;
        EBO_ = other.EBO_;
        vertex_count_ = other.vertex_count_;
        index_count_ = other.index_count_;
        other.VAO_ = other.VBO_ = other.EBO_ = 0;
        other.vertex_count_ = other.index_count_ = 0;
    }
    return *this;
}

Mesh::~Mesh() {
    Cleanup();
}

void Mesh::SetupMesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices) {
    glGenVertexArrays(1, &VAO_);
    glGenBuffers(1, &VBO_);
    if (!indices.empty()) {
        glGenBuffers(1, &EBO_);
    }

    glBindVertexArray(VAO_);

    // Vertex buffer
    glBindBuffer(GL_ARRAY_BUFFER, VBO_);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);

    // Index buffer
    if (!indices.empty()) {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
    }

    // Setup attributes
    SetupVertexAttributes();

    glBindVertexArray(0);
}

void Mesh::Cleanup() {
    if (EBO_) glDeleteBuffers(1, &EBO_);
    if (VBO_) glDeleteBuffers(1, &VBO_);
    if (VAO_) glDeleteVertexArrays(1, &VAO_);
}

void Mesh::Draw() const {
    glBindVertexArray(VAO_);
    if (index_count_ > 0) {
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(index_count_), GL_UNSIGNED_INT, nullptr);
    } else {
        glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertex_count_));
    }
    glBindVertexArray(0);
}

void Mesh::DrawInstanced(int instance_count) const {
    glBindVertexArray(VAO_);
    if (index_count_ > 0) {
        glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(index_count_), GL_UNSIGNED_INT, nullptr, instance_count);
    } else {
        glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast<GLsizei>(vertex_count_), instance_count);
    }
    glBindVertexArray(0);
}

std::unique_ptr<Mesh> Mesh::CreateQuad() {
    std::vector<Vertex> verts = {
        {{-1, -1, 0}, {0,0,1}, {0,0}, {}, {}},
        {{ 1, -1, 0}, {0,0,1}, {1,0}, {}, {}},
        {{ 1,  1, 0}, {0,0,1}, {1,1}, {}, {}},
        {{-1,  1, 0}, {0,0,1}, {0,1}, {}, {}},
    };
    std::vector<unsigned int> idx = {0,1,2, 2,3,0};
    return std::make_unique<Mesh>(std::move(verts), std::move(idx));
}

std::unique_ptr<Mesh> Mesh::CreateCube() {
    std::vector<Vertex> verts = {
        {{-1, -1, -1}, {0,0,-1}, {0,0}, {}, {}},
        {{ 1, -1, -1}, {0,0,-1}, {1,0}, {}, {}},
        {{ 1,  1, -1}, {0,0,-1}, {1,1}, {}, {}},
        {{-1,  1, -1}, {0,0,-1}, {0,1}, {}, {}},
        {{-1, -1,  1}, {0,0, 1}, {0,0}, {}, {}},
        {{ 1, -1,  1}, {0,0, 1}, {1,0}, {}, {}},
        {{ 1,  1,  1}, {0,0, 1}, {1,1}, {}, {}},
        {{-1,  1,  1}, {0,0, 1}, {0,1}, {}, {}},
    };
    std::vector<unsigned int> idx = {
        0,1,2, 2,3,0,    // back
        4,5,6, 6,7,4,    // front
        0,4,7, 7,3,0,    // left
        1,5,6, 6,2,1,    // right
        3,2,6, 6,7,3,    // top
        0,1,5, 5,4,0     // bottom
    };
    return std::make_unique<Mesh>(std::move(verts), std::move(idx));
}

std::unique_ptr<Mesh> Mesh::CreateSphere(int sectors, int stacks) {
    std::vector<Vertex> verts;
    std::vector<unsigned int> idx;
    const float PI = 3.14159265359f;
    for (int i = 0; i <= stacks; ++i) {
        float stackAngle = PI / 2 - i * PI / stacks;
        float xy = cosf(stackAngle);
        float z = sinf(stackAngle);
        for (int j = 0; j <= sectors; ++j) {
            float sectorAngle = j * 2 * PI / sectors;
            Vertex v;
            v.position = { xy * cosf(sectorAngle), xy * sinf(sectorAngle), z };
            v.normal = glm::normalize(v.position);
            v.tex_coords = { (float)j / sectors, (float)i / stacks };
            verts.push_back(v);
        }
    }
    for (int i = 0; i < stacks; ++i) {
        int k1 = i * (sectors + 1);
        int k2 = k1 + sectors + 1;
        for (int j = 0; j < sectors; ++j) {
            if (i != 0) {
                idx.push_back(k1 + j);
                idx.push_back(k2 + j);
                idx.push_back(k1 + j + 1);
            }
            if (i != (stacks - 1)) {
                idx.push_back(k1 + j + 1);
                idx.push_back(k2 + j);
                idx.push_back(k2 + j + 1);
            }
        }
    }
    return std::make_unique<Mesh>(std::move(verts), std::move(idx));
}

std::unique_ptr<Mesh> Mesh::CreatePlane(float width, float height, int subdivisions) {
    std::vector<Vertex> verts;
    std::vector<unsigned int> idx;
    int rows = subdivisions + 1;
    int cols = subdivisions + 1;
    for (int y = 0; y < rows; ++y) {
        for (int x = 0; x < cols; ++x) {
            float fx = (float)x / subdivisions * width - width / 2;
            float fy = (float)y / subdivisions * height - height / 2;
            verts.push_back({ {fx, fy, 0}, {0,0,1}, {(float)x/subdivisions, (float)y/subdivisions}, {}, {} });
        }
    }
    for (int y = 0; y < subdivisions; ++y) {
        for (int x = 0; x < subdivisions; ++x) {
            int i0 = y * cols + x;
            int i1 = i0 + 1;
            int i2 = i0 + cols;
            int i3 = i2 + 1;
            idx.push_back(i0); idx.push_back(i2); idx.push_back(i1);
            idx.push_back(i1); idx.push_back(i2); idx.push_back(i3);
        }
    }
    return std::make_unique<Mesh>(std::move(verts), std::move(idx));
}

void Mesh::UpdateVertices(std::span<const Vertex> new_vertices) {
    glBindBuffer(GL_ARRAY_BUFFER, VBO_);
    glBufferSubData(GL_ARRAY_BUFFER, 0, new_vertices.size() * sizeof(Vertex), new_vertices.data());
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    vertex_count_ = new_vertices.size();
}

void Mesh::UpdateIndices(std::span<const unsigned int> new_indices) {
    if (!EBO_) {
        glGenBuffers(1, &EBO_);
    }
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, new_indices.size() * sizeof(unsigned int), new_indices.data(), GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    index_count_ = new_indices.size();
}

glm::vec3 Mesh::CalculateCenter() const {
    // User should implement actual center calculation
    return glm::vec3(0.0f);
}

std::pair<glm::vec3, glm::vec3> Mesh::CalculateBoundingBox() const {
    // User should implement bounding box calculation
    return { glm::vec3(0.0f), glm::vec3(0.0f) };
}

void Mesh::PrintInfo() const {
    std::cout << "Mesh - Vertices: " << vertex_count_
              << ", Indices: " << index_count_
              << ", VAO: " << VAO_ << "\n";
}
