#include "ogl3_vao.h"
#include <memory.h>
#include <GL/gl.h>


namespace sleek
{
    static GLuint ogl3_call_mode[] =
    {
        GL_STATIC_DRAW,
        GL_DYNAMIC_DRAW,
        GL_STREAM_DRAW
    };

    namespace driver
    {
        template<>
        ogl3_vao_identifer<false>::ogl3_vao_identifer(MeshBuffer *o, VAO_ALIGNMENT t, VAO_ALIGNMENT v) noexcept
            : identifier(o), vert(t), element(v)
        {
            vbo_allocated = -1;
            ebo_allocated = -1;

            glGenVertexArrays(1, &gl);
            glGenBuffers(1, &vbo);
            glGenBuffers(1, &ebo);

            glBindBuffer(GL_ARRAY_BUFFER, ebo);
            glBufferData(GL_ARRAY_BUFFER, 0, 0, ogl3_call_mode[t]);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, 0, ogl3_call_mode[v]);
        }

        template<>
        ogl3_vao_identifer<true>::ogl3_vao_identifer(MeshBuffer *o, VAO_ALIGNMENT t, VAO_ALIGNMENT v) noexcept
            : identifier(o), vert(t), element(v)
        {
            vbo_allocated = -1;
            ebo_allocated = -1;

            glGenVertexArrays(1, &gl);
            glGenBuffers(1, &vbo);
            glGenBuffers(1, &ebo);
            glNamedBufferDataEXT(vbo, 0, 0, ogl3_call_mode[t]);
            glNamedBufferDataEXT(ebo, 0, 0, ogl3_call_mode[v]);
        }

        template<bool dsa>
        ogl3_vao_identifer<dsa>::~ogl3_vao_identifer() noexcept
        {
            glDeleteBuffers(1, &vbo);
            glDeleteBuffers(1, &ebo);
            glDeleteBuffers(1, &gl);
        }

        template<bool dsa>
        void* ogl3_vao_identifer<dsa>::getHardwareLink() const noexcept
        {
            GLuint *tmp = (GLuint*)&gl;
            return tmp;
        }

        template<bool dsa>
        void ogl3_vao_identifer<dsa>::bind() noexcept
        {
            glBindVertexArray(gl);
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

            constexpr GLboolean normalize = GL_FALSE;

            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0, 3, GL_FLOAT, normalize, sizeof(math::vertex), (GLubyte*)0 + offsetof(math::vertex, Pos));

            glEnableVertexAttribArray(1);
            glVertexAttribPointer(1, 3, GL_FLOAT, normalize, sizeof(math::vertex), (GLubyte*)0 + offsetof(math::vertex, Normal));

            glEnableVertexAttribArray(2);
            glVertexAttribPointer(2, 2, GL_FLOAT, normalize, sizeof(math::vertex), (GLubyte*)0 + offsetof(math::vertex, Coord));

            glEnableVertexAttribArray(3);
            glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, normalize, sizeof(math::vertex), (GLubyte*)0 + offsetof(math::vertex, Color));
        }

        template<bool dsa>
        void ogl3_vao_identifer<dsa>::unbind() noexcept
        {
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        }

        template<>
        void ogl3_vao_identifer<false>::update() noexcept
        {
            MeshBuffer *tmp = (MeshBuffer*)owner;

            glBindVertexArray(gl);

            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBufferData(GL_ARRAY_BUFFER, sizeof(*tmp->indices.data())*tmp->indices.size(), tmp->vertices.data(), ogl3_call_mode[vert]);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*tmp->vertices.data())*tmp->vertices.size(), tmp->indices.data(), ogl3_call_mode[element]);

//            glBufferSubData(
//                GL_ARRAY_BUFFER, 0,
//                sizeof(*tmp->vertices.data()) * tmp->vertices.size(),
//                tmp->vertices.data()
//            );
//
//            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
//            glBufferSubData(
//                GL_ELEMENT_ARRAY_BUFFER, 0,
//                sizeof(*tmp->indices.data()) * tmp->indices.size(),
//                tmp->indices.data()
//            );

            glBindVertexArray(0);
        }

        template<>
        void ogl3_vao_identifer<true>::update() noexcept
        {
            MeshBuffer *tmp = (MeshBuffer*)owner;

            const unsigned int vertex_size = sizeof(*tmp->vertices.data())*tmp->vertices.size();
            const unsigned int index_size = sizeof(*tmp->indices.data())*tmp->indices.size();

            if(vbo_allocated != vertex_size)
                glNamedBufferDataEXT(vbo, vertex_size, tmp->vertices.data(), ogl3_call_mode[vert]);
            else
                glNamedBufferSubDataEXT(vbo, 0, vertex_size, tmp->vertices.data());

            if(ebo_allocated != index_size)
                glNamedBufferDataEXT(ebo, index_size, tmp->indices.data(), ogl3_call_mode[element]);
            else
                glNamedBufferSubDataEXT(ebo, 0, index_size, tmp->indices.data());

            vbo_allocated = vertex_size;
            ebo_allocated = index_size;
        }
    }
}