/*
 ** This file is part of the ViTE project.
 **
 ** This software is governed by the CeCILL-A license under French law
 ** and abiding by the rules of distribution of free software. You can
 ** use, modify and/or redistribute the software under the terms of the
 ** CeCILL-A license as circulated by CEA, CNRS and INRIA at the following
 ** URL: "http://www.cecill.info".
 **
 ** As a counterpart to the access to the source code and rights to copy,
 ** modify and redistribute granted by the license, users are provided
 ** only with a limited warranty and the software's author, the holder of
 ** the economic rights, and the successive licensors have only limited
 ** liability.
 **
 ** In this respect, the user's attention is drawn to the risks associated
 ** with loading, using, modifying and/or developing or reproducing the
 ** software by the user in light of its specific status of free software,
 ** that may mean that it is complicated to manipulate, and that also
 ** therefore means that it is reserved for developers and experienced
 ** professionals having in-depth computer knowledge. Users are therefore
 ** encouraged to load and test the software's suitability as regards
 ** their requirements in conditions enabling the security of their
 ** systems and/or data to be ensured and, more generally, to use and
 ** operate it in the same conditions as regards security.
 **
 ** The fact that you are presently reading this means that you have had
 ** knowledge of the CeCILL-A license and that you accept its terms.
 **
 **
 ** ViTE developpers are (for version 0.* to 1.0):
 **
 **        - COULOMB Kevin
 **        - FAVERGE Mathieu
 **        - JAZEIX Johnny
 **        - LAGRASSE Olivier
 **        - MARCOUEILLE Jule
 **        - NOISETTE Pascal
 **        - REDONDY Arthur
 **        - VUCHENER Clément
 **
 */
/*!
 *\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;
    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) {
        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 ) {
        delete _shader;
    }
    _shader = s;
}
