#include "Models.hpp"

using namespace render::gl;

Shape::Shape(const std::vector<glm::vec3>& pos) {
    size = pos.size();
    glGenBuffers(1, &bufferId);
    glBindBuffer(GL_ARRAY_BUFFER, bufferId);
    glBufferData(GL_ARRAY_BUFFER, pos.size() * sizeof(glm::vec3), pos.data(), GL_STATIC_DRAW);
}
Shape::~Shape() {
    glDeleteBuffers(1, &bufferId);
}
size_t Shape::draw(GLenum format) {
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, bufferId);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);
    glDrawArrays(format, 0, size);
    glDisableVertexAttribArray(0);
    return size;
}

ColoredShape::ColoredShape(const std::vector<glm::vec3>& pos, const std::vector<glm::vec4>& col) {
    assert(pos.size() == col.size());
    size = pos.size();
    glGenBuffers(1, &vertexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glBufferData(GL_ARRAY_BUFFER, pos.size() * sizeof(glm::vec3), pos.data(), GL_STATIC_DRAW);
    glGenBuffers(1, &colorBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferId);
    glBufferData(GL_ARRAY_BUFFER, col.size() * sizeof(glm::vec4), col.data(), GL_STATIC_DRAW);
}
ColoredShape::~ColoredShape() {
    glDeleteBuffers(1, &colorBufferId);
    glDeleteBuffers(1, &vertexBufferId);
}
void ColoredShape::enableAttribs() {
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);

    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferId);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void *)0);
}
void ColoredShape::disableAttribs() {
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}
size_t ColoredShape::draw() {
    enableAttribs();
    glDrawArrays(GL_LINES, 0, size);
    disableAttribs();
    return size;
}
size_t ColoredShape::drawInstanced(size_t count) {
    enableAttribs();
    glDrawArraysInstanced(GL_LINES, 0, size, count);
    disableAttribs();
    return size * count;
}

void enableAttribs() {
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_HALF_FLOAT, GL_FALSE, sizeof(render::PackedVertexData),
        reinterpret_cast<void *>(offsetof(render::PackedVertexData, PosMat[0])));
    glEnableVertexAttribArray(1);
    glVertexAttribIPointer(1, 1, GL_UNSIGNED_SHORT, sizeof(render::PackedVertexData),
        reinterpret_cast<void *>(offsetof(render::PackedVertexData, PosMat[3])));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 3, GL_HALF_FLOAT, GL_FALSE, sizeof(render::PackedVertexData),
        reinterpret_cast<void *>(offsetof(render::PackedVertexData, Nrm)));
}
void disableAttribs() {
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}

Model::Model(const Data& data) {
    indexSize = data.indices.size();
    glGenBuffers(1, &vertexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glBufferData(GL_ARRAY_BUFFER, data.vertices.size() * sizeof(render::PackedVertexData), data.vertices.data(), GL_STATIC_DRAW);
    glGenBuffers(1, &indexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, indexBufferId);
    glBufferData(GL_ARRAY_BUFFER, data.indices.size() * sizeof(glm::u16), data.indices.data(), GL_STATIC_DRAW);
}
Model::~Model() {
    glDeleteBuffers(1, &vertexBufferId);
    glDeleteBuffers(1, &indexBufferId);
}
size_t Model::draw() {
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    enableAttribs();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
    glDrawElements(GL_TRIANGLES, indexSize, GL_UNSIGNED_SHORT, (void *)0);
    disableAttribs();
    return indexSize;
}
size_t Model::drawInstanced(size_t count) {
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    enableAttribs();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
    glDrawElementsInstanced(GL_TRIANGLES, indexSize, GL_UNSIGNED_SHORT, (void *)0, count);
    disableAttribs();
    return indexSize * count;
}

LodModel::LodModel(const LodData& data) {
    indexSize = data.first.indices.size();
    offsets = data.second;
    glGenBuffers(1, &vertexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glBufferData(GL_ARRAY_BUFFER, data.first.vertices.size() * sizeof(render::PackedVertexData), data.first.vertices.data(), GL_STATIC_DRAW);
    glGenBuffers(1, &indexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, indexBufferId);
    glBufferData(GL_ARRAY_BUFFER, data.first.indices.size() * sizeof(glm::u16), data.first.indices.data(), GL_STATIC_DRAW);
}
LodModel::~LodModel() {
    glDeleteBuffers(1, &vertexBufferId);
    glDeleteBuffers(1, &indexBufferId);
}
size_t LodModel::draw() {
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    enableAttribs();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
    const auto start = getOffset(level);
    const auto end = getOffset(level+1);
    const auto count = end - start;
    glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (void *)(start*sizeof(glm::u16)));
    disableAttribs();
    return count;
}
size_t LodModel::drawInstanced(size_t i) {
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    enableAttribs();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
    const auto start = getOffset(level);
    const auto end = getOffset(level+1);
    const auto count = end - start;
    glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (void *)(start*sizeof(glm::u16)), i);
    disableAttribs();
    return i * count;
}

std::unique_ptr<render::Model> createModel(const Model::Data& data) {
    return std::make_unique<Model>(data);
}
void Model::MakeDefault() {
    createFunc = createModel;
}

std::unique_ptr<render::LodModel> createLodModel(const LodModel::LodData& data) {
    return std::make_unique<LodModel>(data);
}
void LodModel::MakeDefault() {
    createFunc = createLodModel;
}