//
// Created by sebastian on 29.07.26.
//

#include "Vao.h"
#include "Vao.h"

#include <iostream>
#include <ostream>

namespace engine {

Vao::Vao(unsigned int id) : m_id(id) {
    bind();
}

Vao::~Vao() {
    glDeleteVertexArrays(1, &m_id);
    // m_relatedVbos und m_indexBuffer räumen sich selbst auf (unique_ptr, RAII)
}

std::unique_ptr<Vao> Vao::create() {
    unsigned int id;
    glGenVertexArrays(1, &id);
    return std::unique_ptr<Vao>(new Vao(id));
}

void Vao::bind() const {
    glBindVertexArray(m_id);
}

void Vao::unbind() const {
    glBindVertexArray(0);
}

void Vao::enableAttributes() const {
    for (const auto& attribute : m_attributes) {
        attribute->enable(true);
    }
}

void Vao::disableAttributes(const std::vector<unsigned int>& attribNumbers) const {
    for (unsigned int i : attribNumbers) {
        glDisableVertexAttribArray(i);
    }
}

int Vao::getVertexDataTotalBytes(const std::vector<std::unique_ptr<Attribute>>& attributes) {
    int total = 0;
    for (const auto& attribute : attributes) {
        total += attribute->getBytesPerVertex();
    }
    return total;
}

void Vao::linkAttributes(int bytesPerVertex, std::vector<std::unique_ptr<Attribute>>& newAttributes) {
    int offset = 0;
    for (auto& attribute : newAttributes) {
        attribute->link(offset, bytesPerVertex);
        offset += attribute->getBytesPerVertex();
        attribute->enable(true);
        m_attributes.push_back(std::move(attribute));
    }
}

void Vao::indicateInstancedAttributes(const std::vector<std::unique_ptr<Attribute>>& attributes) const {
    for (const auto& attribute : attributes) {
        glVertexAttribDivisor(attribute->getAttributeNumber(), 1);
    }
}

Vbo& Vao::createDataFeed(size_t maxVertexCount, GLenum usage,
                          std::vector<std::unique_ptr<Attribute>> newAttributes) {
    int bytesPerVertex = getVertexDataTotalBytes(newAttributes);
    auto vbo = Vbo::create(GL_ARRAY_BUFFER, usage);
    vbo->allocateData(bytesPerVertex * maxVertexCount);
    linkAttributes(bytesPerVertex, newAttributes);
    vbo->unbind();

    m_relatedVbos.push_back(std::move(vbo));
    return *m_relatedVbos.back();
}

Vbo& Vao::createInstanceDataFeed(size_t maxInstances,
                                  std::vector<std::unique_ptr<Attribute>> newAttributes) {
    int bytesPerInstance = getVertexDataTotalBytes(newAttributes);
    auto vbo = Vbo::create(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
    vbo->allocateData(bytesPerInstance * maxInstances);
    linkAttributes(bytesPerInstance, newAttributes);
    indicateInstancedAttributes(newAttributes);
    vbo->unbind();

    m_relatedVbos.push_back(std::move(vbo));
    return *m_relatedVbos.back();
}

Vbo& Vao::initDataFeed(const void* data, size_t sizeInBytes, GLenum usage,
                        std::vector<std::unique_ptr<Attribute>> newAttributes) {
    int bytesPerVertex = getVertexDataTotalBytes(newAttributes);
    auto vbo = Vbo::create(GL_ARRAY_BUFFER, usage);
    vbo->allocateAndStoreData(data, sizeInBytes);
    linkAttributes(bytesPerVertex, newAttributes);
    vbo->unbind();

    m_relatedVbos.push_back(std::move(vbo));
    return *m_relatedVbos.back();
}

void Vao::linkBoundVbo(std::unique_ptr<Vbo> vbo,
                        std::vector<std::unique_ptr<Attribute>> newAttributes) {
    int bytesPerVertex = getVertexDataTotalBytes(newAttributes);
    linkAttributes(bytesPerVertex, newAttributes);
    m_relatedVbos.push_back(std::move(vbo));
}

void Vao::linkInstancingVbo(std::unique_ptr<Vbo> vbo,
                             std::vector<std::unique_ptr<Attribute>> newAttributes) {
    vbo->bind();
    int bytesPerVertex = getVertexDataTotalBytes(newAttributes);
    linkAttributes(bytesPerVertex, newAttributes);
    indicateInstancedAttributes(newAttributes);
    vbo->unbind();

    m_relatedVbos.push_back(std::move(vbo));
}

Vbo& Vao::createIndexBuffer(const unsigned int* indices, size_t count) {
    m_indexBuffer = Vbo::create(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW);
    m_indexBuffer->allocateAndStoreData(indices, count * sizeof(unsigned int));
    return *m_indexBuffer;
}

Vbo& Vao::createIndexBufferFeed(size_t maxIndexCount) {
    m_indexBuffer = Vbo::create(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW);
    m_indexBuffer->allocateData(maxIndexCount * sizeof(unsigned int));
    return *m_indexBuffer;
}

} // namespace engine