/**
 *
 * @file src/render/vbo/vbo.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Arthur Chevalier
 * @author Johnny Jazeix
 * @author Olivier Lagrasse
 * @author Mathieu Faverge
 * @author Thibault Soucarre
 * @author Luca Bourroux
 *
 * @date 2024-07-17
 */
/*!
 *\file vbo.cpp
 */
#include <GL/glew.h>
#include <stdlib.h>
#include <iostream>
#include <glm/glm.hpp>
#include "vbo.hpp"
#include "Shader.hpp"

#define ERROR(m) printf("%s", m);

using namespace std;

/***********************************
 *
 *
 *
 * Constructor and destructor.
 *
 *
 *
 **********************************/

/* Default constructor*/
Vbo::Vbo() :
    _vboID(0), _vaoID(0), _nbVertex(0), _shader(nullptr) { }

/* Shader parameter is the shader we use for the entity type the vbo stand for
 */
Vbo::Vbo(Shader *s) :
    _vboID(0), _vaoID(0), _nbVertex(0), _shader(s) { }

/* Destructor*/
Vbo::~Vbo() {
    glDeleteBuffers(1, &_vboID);
    _vboID = 0;
    glDeleteVertexArrays(1, &_vaoID);
    _vaoID = 0;
    // see >SHADER_POOL
    // delete _shader;
}

/***********************************
 *
 *
 *
 * Buffer filling.
 *
 *
 *
 **********************************/

/* add vertex for a vbo storing coordinates and colors*/
int Vbo::add(Element_pos x, Element_pos y, Element_col r, Element_col g, Element_col b) {
    _vertex.push_back(x);
    _vertex.push_back(y);
    _colors.push_back(r);
    _colors.push_back(g);
    _colors.push_back(b);
    _nbVertex++;

    return 1;
}

/* add vertex for a vbo using coordinates and texture*/
int Vbo::add(Element_pos x, Element_pos y, Element_pos tx, Element_pos ty) {
    _vertex.push_back(x);
    _vertex.push_back(y);
    _texture_coord.push_back(tx);
    _texture_coord.push_back(ty);
    _nbVertex++;

    return 1;
}

/* add vertex for state vbo using a char for shade (glsl 330 or later)*/
int Vbo::add(Element_pos x, Element_pos y, char b) {
    _vertex.push_back(x);
    _vertex.push_back(y);
    _shaded.push_back(b);
    _nbVertex++;

    return 1;
}

/* add vertex for state vbo with glsl < 330*/
int Vbo::add(Element_pos x, Element_pos y, float b) {
    _vertex.push_back(x);
    _vertex.push_back(y);
    _shaded2.push_back(b);
    _nbVertex++;

    return 1;
}

/*add vertex for link or event vbo*/
int Vbo::add(Element_pos x, Element_pos y) {
    _vertex.push_back(x);
    _vertex.push_back(y);
    _nbVertex++;

    return 1;
}

void Vbo::config(int glsl) {
    // std::cout << "Configuration du VBO. Nombre de points : "<< _vertex.size()/2 << std::endl;
    /* calculate sizes of all dynamic tables*/
    int vertex_size = _vertex.size() * sizeof(Element_pos);
    int colors_size = _colors.size() * sizeof(Element_col);
    int texture_size = _texture_coord.size() * sizeof(Element_pos);
    int shaded_size;
    Element_pos *vertex = _vertex.data();
    Element_col *colors = _colors.data();
    Element_pos *texture = _texture_coord.data();

    char *shaded;
    float *shaded2;
    if (glsl < 330) { // use float for color gradient
        shaded_size = _shaded2.size() * sizeof(float);
        shaded2 = _shaded2.data();
    }
    else { // use char for color gradient
        shaded_size = _shaded.size() * sizeof(char);
        shaded = _shaded.data();
    }
    // Delete old vbo if necessary
    if (glIsBuffer(_vboID) == GL_TRUE)
        glDeleteBuffers(1, &_vboID);
    // Generate vbo
    glGenBuffers(1, &_vboID);
    // Bind our VBO
    glBindBuffer(GL_ARRAY_BUFFER, _vboID);
    // Allocate memory inside graphic card
    glBufferData(GL_ARRAY_BUFFER, vertex_size + colors_size + shaded_size + texture_size, nullptr, GL_STATIC_DRAW);
    // Send datas
    glBufferSubData(GL_ARRAY_BUFFER, 0, vertex_size, vertex);
    glBufferSubData(GL_ARRAY_BUFFER, vertex_size, colors_size, colors);
    glBufferSubData(GL_ARRAY_BUFFER, vertex_size + colors_size, texture_size, texture);
    if (glsl < 330) // use float for color gradient
        glBufferSubData(GL_ARRAY_BUFFER, vertex_size + colors_size, shaded_size, shaded2);
    else // use char for color gradient
        glBufferSubData(GL_ARRAY_BUFFER, vertex_size + colors_size, shaded_size, shaded);
    // Unbind our VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // Clear RAM and realoccate vectors
    std::vector<Element_pos>().swap(_vertex);
    std::vector<Element_col>().swap(_colors);
    std::vector<char>().swap(_shaded);
    std::vector<float>().swap(_shaded2);
    std::vector<Element_pos>().swap(_texture_coord);

    // Delete old vao if necessary
    if (glIsVertexArray(_vaoID))
        glDeleteVertexArrays(1, &_vaoID);
    // Generate VAO
    glGenVertexArrays(1, &_vaoID);
    // Bind our VAO
    glBindVertexArray(_vaoID);
    // The following instructions are stocked inside the VAO
    {
        // Bind VBO
        glBindBuffer(GL_ARRAY_BUFFER, _vboID);
        // Send vertex
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
        glEnableVertexAttribArray(0);
        // Send colors
        if (colors_size > 0) {
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(vertex_size));
            glEnableVertexAttribArray(1);
        }
        /* Send textures coordinates*/
        if (texture_size > 0) {
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(vertex_size));
            glEnableVertexAttribArray(2);
        }
        /* Send color gradient data (for states) */
        if (shaded_size > 0) {
            if (glsl < 330) {
                glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(vertex_size + colors_size));
                glEnableVertexAttribArray(1);
            }
            else {
                glVertexAttribPointer(1, 1, GL_BYTE, GL_FALSE, 0, BUFFER_OFFSET(vertex_size + colors_size));
                glEnableVertexAttribArray(1);
            }
        }
        // Unbind VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
    // Unbind VAO
    glBindVertexArray(0);
}

/***********************************
 *
 *
 *
 * Buffer display.
 *
 *
 *
 **********************************/

/* Should be called before using a vbo in paintGL*/
void Vbo::lock() {
    glBindVertexArray(_vaoID);
}

/* Should be called after using a vbo in paintGL*/
void Vbo::unlock() {
    glBindVertexArray(0);
}

/* Used to know how many vertex we send to shader*/
int Vbo::getNbVertex() {
    return _nbVertex;
}

/* Should be used to set nbVertex to 0 when we change datas in a vbo (ruler)*/
void Vbo::setNbVertex(int n) {
    _nbVertex = n;
}

Shader *Vbo::get_shader() {
    return _shader;
}

void Vbo::delete_shader() {
    if (_shader != nullptr) {
        // see >SHADER_POOL
        // delete _shader;
    }
    _shader = nullptr;
}

/* change current shader
 if precedent is not used anymore, delete_shader should be called before this function*/
void Vbo::set_shader(Shader *s) {
    if (_shader != nullptr) {
        // see >SHADER_POOL
        // delete _shader;
    }
    _shader = s;
}
