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

#include "Vbo.h"

namespace engine {

Vbo::Vbo(unsigned int id, GLenum type, GLenum usage)
    : m_id(id), m_type(type), m_usage(usage) {
    bind();
}

Vbo::~Vbo() {
    glDeleteBuffers(1, &m_id);
}

std::unique_ptr<Vbo> Vbo::create(GLenum type, GLenum usage) {
    unsigned int id;
    glGenBuffers(1, &id);
    // std::unique_ptr<Vbo>(new Vbo(...)), da Konstruktor privat ist und
    // std::make_unique keinen Zugriff auf private Konstruktoren hat
    return std::unique_ptr<Vbo>(new Vbo(id, type, usage));
}

std::unique_ptr<Vbo> Vbo::createAndAllocate(GLenum type, GLenum usage, size_t sizeInBytes) {
    auto vbo = create(type, usage);
    vbo->allocateData(sizeInBytes);
    return vbo;
}

void Vbo::bind() const {
    glBindBuffer(m_type, m_id);
}

void Vbo::unbind() const {
    glBindBuffer(m_type, 0);
}

void Vbo::allocateData(size_t sizeInBytes) {
    allocateData(sizeInBytes, m_type);
}

void Vbo::orphan() {
    allocateData(m_sizeInBytes, m_type);
}

void Vbo::allocateData(size_t sizeInBytes, GLenum target) {
    glBufferData(target, static_cast<GLsizeiptr>(sizeInBytes), nullptr, m_usage);
    m_sizeInBytes = sizeInBytes;
}

void Vbo::storeData(size_t startInBytes, const void* data, size_t dataSizeInBytes) {
    glBufferSubData(m_type, static_cast<GLintptr>(startInBytes),
                     static_cast<GLsizeiptr>(dataSizeInBytes), data);
}

void Vbo::allocateAndStoreData(const void* data, size_t sizeInBytes) {
    glBufferData(m_type, static_cast<GLsizeiptr>(sizeInBytes), data, m_usage);
    m_sizeInBytes = sizeInBytes;
}

std::unique_ptr<Vbo> Vbo::createTempVbo(size_t dataSize) {
    auto tempVbo = create(GL_COPY_WRITE_BUFFER, GL_STATIC_COPY); // TODO: GL_STREAM_COPY erwägen?
    tempVbo->allocateData(dataSize);
    return tempVbo;
}

void Vbo::copyContentsTo(Vbo& destination, size_t readStart, size_t writeStart, size_t dataSize) const {
    glBindBuffer(GL_COPY_READ_BUFFER, m_id);
    glBindBuffer(GL_COPY_WRITE_BUFFER, destination.m_id);
    glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
                         static_cast<GLintptr>(readStart), static_cast<GLintptr>(writeStart),
                         static_cast<GLsizeiptr>(dataSize));
}

void Vbo::resize(size_t copyDataSize, size_t newSizeInBytes) {
    if (copyDataSize == 0) {
        bind();
        allocateData(newSizeInBytes);
        return;
    }

    auto temporaryVbo = createTempVbo(copyDataSize);
    copyContentsTo(*temporaryVbo, 0, 0, copyDataSize);
    allocateData(newSizeInBytes, GL_COPY_READ_BUFFER);
    temporaryVbo->copyContentsTo(*this, 0, 0, copyDataSize);
    // temporaryVbo wird hier automatisch gelöscht (unique_ptr out-of-scope) -
    // kein manuelles delete() nötig, im Gegensatz zur Java-Vorlage
}

} // namespace engine