//
// Created by sebastian on 04.08.26.
//

#include "SphereFactory.h"

#include <cmath>
#include <vector>

#include "utils/openglWrapper/openglObjects/Vao.h"

std::shared_ptr<engine::Model> engine::SphereFactory::createUVSphere(float radius, int sectorCount, int stackCount) {
    constexpr float PI = 3.14159265358979323846f;

    // Vertices: (stackCount + 1) Ringe entlang der Breitengrade, je (sectorCount + 1) Punkte
    // entlang der Längengrade (der letzte Punkt dupliziert den ersten, damit das Index-Gitter
    // pro Segment gleichförmig bleibt).
    std::vector<float> vertices;
    vertices.reserve(static_cast<std::size_t>(stackCount + 1) * (sectorCount + 1) * 3);

    for (int i = 0; i <= stackCount; ++i) {
        const float stackAngle = PI / 2.0f - static_cast<float>(i) * PI / static_cast<float>(stackCount); // +pi/2 .. -pi/2
        const float xy = radius * std::cos(stackAngle);
        const float z  = radius * std::sin(stackAngle);

        for (int j = 0; j <= sectorCount; ++j) {
            const float sectorAngle = static_cast<float>(j) * 2.0f * PI / static_cast<float>(sectorCount); // 0 .. 2pi
            vertices.push_back(xy * std::cos(sectorAngle)); // x
            vertices.push_back(xy * std::sin(sectorAngle)); // y
            vertices.push_back(z);                          // z
        }
    }

    // Indices: zwei Dreiecke pro Gitterzelle; die Kappen am Pol lassen das jeweils
    // entartete Dreieck aus.
    std::vector<unsigned int> indices;
    indices.reserve(static_cast<std::size_t>(stackCount) * sectorCount * 6);

    for (int i = 0; i < stackCount; ++i) {
        unsigned int k1 = static_cast<unsigned int>(i * (sectorCount + 1)); // Anfang des aktuellen Rings
        unsigned int k2 = k1 + static_cast<unsigned int>(sectorCount + 1);  // Anfang des nächsten Rings

        for (int j = 0; j < sectorCount; ++j, ++k1, ++k2) {
            if (i != 0) {
                indices.push_back(k1);
                indices.push_back(k2);
                indices.push_back(k1 + 1);
            }
            if (i != stackCount - 1) {
                indices.push_back(k1 + 1);
                indices.push_back(k2);
                indices.push_back(k2 + 1);
            }
        }
    }

    auto vao = Vao::create();

    std::vector<std::unique_ptr<Attribute>> attribs;
    attribs.push_back(std::make_unique<Vec3Attribute>(0));
    vao->initDataFeed(vertices.data(), vertices.size() * sizeof(float), GL_STATIC_DRAW, std::move(attribs));

    vao->createIndexBuffer(indices.data(), indices.size());
    vao->unbind();

    AABB modelAABB = AABB(
        glm::vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max()),
        glm::vec3(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest())
    );
    for (int i = 0; i < static_cast<int>(vertices.size()); i+=3) {
        modelAABB.expandToInclude(glm::vec3(vertices[i], vertices[i+1], vertices[i+2]));
    }

    std::vector<MeshSection> meshSections;
    return std::make_shared<Model>(std::move(vao), static_cast<int>(indices.size()),  meshSections, modelAABB);
}
