require 'gl'
require 'util/pointers'
include GL

# A vertex buffer object, or VBO.
class GL::Buffer

    # Reference ID
    attr_reader :id
    # Buffer type
    attr_reader :type
    # Buffer usage
    attr_reader :usage
    # Buffer length
    attr_reader :length

    # Create a new, unallocated buffer.
    def initialize( type = nil )
        ptr = 0.pointer
        glGenBuffers( 1, ptr )
        @id = ptr.read_uint
        @type = type
    end

    # Bind to this buffer.  If another buffer is in use, it will be unbound.
    def bind()
        return false unless @type
        glBindBuffer( @type, @id )
    end

    # Unbind from this buffer.
    def unbind()
        return false unless @type
        glBindBuffer( @type, 0 )
    end

    # Check if this buffer is currently in use.
    def current?()
        Buffer.current( @type ).eql? @id
    end

    # Return the ID of the current buffer of the given type enum.
    def self.current( type )
        buf = 0.pointer
        enum = case type
            when GL_ARRAY_BUFFER then GL_ARRAY_BUFFER_BINDING
            when GL_PIXEL_PACK_BUFFER then GL_PIXEL_PACK_BUFFER_BINDING
            when GL_PIXEL_UNPACK_BUFFER then GL_PIXEL_UNPACK_BUFFER_BINDING
            when GL_SHADER_STORAGE_BUFFER then GL_SHADER_STORAGE_BUFFER_BINDING
            when GL_TEXTURE_BUFFER then GL_TEXTURE_BINDING_BUFFER
            when GL_TRANSFORM_FEEDBACK_BUFFER then GL_TRANSFORM_FEEDBACK_BUFFER_BINDING
            when GL_UNIFORM_BUFFER then GL_UNIFORM_BUFFER_BINDING
            else
                return 0    # unsupported binding ?
        end
        glGetIntegerv( enum, buf )
        buf.read_uint
    end

    # Set up a buffer's data.  If data is *not* a `FFI::Pointer`, it will be converted to one.
    #
    # This method establishes the buffer object wrapper's internal state.
    def buffer_data( type, data, usage )
        data = data.read if data.respond_to? :read
        data = data.pointer unless data.is_a? FFI::Pointer
        @type = type
        @usage = usage
        @length = data.size

        glNamedBufferData( @id, data.size, data, @usage )
    end
    alias :data= :buffer_data

    # Bind this buffer's data as is to declare its range.  As per `Buffer.range()`.
    def bind_range( target, index = 0, offset = 0 )
        Buffer.range( target, index, @id, offset, @length - (index + offset) )
    end

    # Set up a buffer range, as per `glBindBufferRange()`.  The value of `target` must be one of:
    #
    # * `GL_ATOMIC_COUNTER_BUFFER`
    # * `GL_TRANSFORM_FEEDBACK_BUFFER`
    # * `GL_UNIFORM_BUFFER`
    # * `GL_SHADER_STORAGE_BUFFER`
    def self.range( target, index, buffer, offset, size )
        target = const_get( target ) if target.is_a? Symbol
        glBindBufferRange( target, index, (buffer.is_a? GL::Buffer) ? buffer.id : buffer, offset, size )
    end

    # Destroy this buffer.
    def destroy!()
        glDeleteBuffers( 1, @id.pointer )
    end
end

# Class-name alias
GL::VBO = GL::Buffer
