module ecs_utils.gfx.buffer;

import ecs.std;

import glad.gl.gl;
import glad.gl.gles2;

extern(C):

struct Buffer
{

    void create() nothrow
    {
        data = Mallocator.make!Data;
        data.gl_handle = 0;
        glGenBuffers(1,&data.gl_handle);
        data.elem_size = 0;
    }

    //---------------------------------------------------------------------------------------------------------------------------------------------------

    void destroy() nothrow
    {
        if(data.gl_handle)glDeleteBuffers(1,&data.gl_handle);
    }

    //---------------------------------------------------------------------------------------------------------------------------------------------------

    void bind(BindTarget target) nothrow
    {
        //if(vbo != this)glBindBuffer(GL_ARRAY_BUFFER,data.gl_handle);
        //vbo = this;
        glBindBuffer(target,data.gl_handle);
    }

    void bindRange(BindTarget target, uint index, uint offset, uint size) nothrow
    {
        glBindBufferRange(target, index, data.gl_handle, offset, size);
    }

    //---------------------------------------------------------------------------------------------------------------------------------------------------

    void bufferData(BindTarget target, uint size, uint count, uint usage, void* data) nothrow
    {
        bind(target);
        this.data.elem_size = size;
        glBufferData(target,size*count,data,usage);
    }

    /*void bufferStorage(uint size, uint count, void* data, uint flags = StorageFlagBits.write)
    {
        bind(BindTarget.array);
        this.data.elem_size = size;
        glBufferStorage(GL_ARRAY_BUFFER,size*count,data, flags);
    }*/

    void bufferSubData(uint size, uint offset, void* data) nothrow
    {
        bind(BindTarget.array);
        glBufferSubData(GL_ARRAY_BUFFER,offset,size,data);
    }
    
    void map(BindTarget target) nothrow
    {
        bind(target);
        data.map_ptr = glMapBuffer(target,GL_WRITE_ONLY);
    }

    void map(uint offset, uint size, BindTarget target, uint flags = MapFlagBits.write | MapFlagBits.flush_explict | MapFlagBits.invalidate_buffer) nothrow
    {
        bind(target);
        data.map_ptr = glMapBufferRange(target,offset,size,flags);
    }

    void flush(uint offset, uint size, BindTarget target) nothrow
    {
        glFlushMappedBufferRange(target, offset, size);
    }

    void unmap(BindTarget target) nothrow
    {
        bind(target);
        glUnmapBuffer(target);
        data.map_ptr = null;
    }

    void* mappedPointer() nothrow
    {
        return data.map_ptr;
    }

    //---------------------------------------------------------------------------------------------------------------------------------------------------

    static void unbind(BindTarget target) nothrow
    {
        //vbo = 0;
        glBindBuffer(target,0);
    }

    enum BindTarget
    {
        array = GL_ARRAY_BUFFER,
        element_array = GL_ELEMENT_ARRAY_BUFFER,
        uniform = GL_UNIFORM_BUFFER,
        //shader_storage = GL_SHADER_STORAGE_BUFFER,
        //indirect = GL_DRAW_INDIRECT_BUFFER
    }

    enum MapFlagBits
    {
        write = GL_MAP_WRITE_BIT,
        invalidate_buffer = GL_MAP_INVALIDATE_BUFFER_BIT,
        flush_explict = GL_MAP_FLUSH_EXPLICIT_BIT,
        //coherent = GL_MAP_COHERENT_BIT,
        //persistent = GL_MAP_PERSISTENT_BIT
    }

    enum StorageFlagBits
    {
        write = GL_MAP_WRITE_BIT,
        //coherent = GL_MAP_COHERENT_BIT,
        //persistent = GL_MAP_PERSISTENT_BIT
    }

    struct Data
    {
        uint elem_size;
        uint gl_handle;
        void* map_ptr;
    }

    Data* data;
}