/*
								+-----------------------------------+
								|                                   |
								|     ***  VBO / PBO class  ***     |
								|                                   |
								|   Copyright  © -tHE SWINe- 2009   |
								|                                   |
								|          BufferObjects.h          |
								|                                   |
								+-----------------------------------+
*/

#pragma once
#ifndef __BUFFER_OBJECTS_INCLUDED
#define __BUFFER_OBJECTS_INCLUDED

/**
 *	@file gl4/BufferObjects.h
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief wrapper for GL_ARB_vertex_buffer_object / GL_ARB_pixel_buffer_object extensions
 *
 *	@todo Improve object design.
 *	@todo Implement GL_ARB_map_buffer_range, GL_ARB_copy_buffer, maybe GL_ARB_texture_buffer_object.
 *
 *	@date 2012-01-21
 *
 *	Modified CGLArraySetup and CGLElementArraySetup to support texcoord-less streams.
 *	This is done simply by passing 0 as texcoord dimension.
 *
 *	@date 2012-02-13
 *
 *	Added the __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER and
 *	the __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING macros (debugging / bugfix).
 *
 *	Added new versions of CGLArraySetup::Draw() and CGLElementArraySetup::Draw(),
 *	allowing explicit specification of the parameters to the OpenGL drawing function
 *	(glDrawArrays() and glDrawElements(), respectively).
 *
 *	@date 2012-03-26
 *
 *	Fixed a minor bug in calculation of vertex size in CGLArraySetup::CGLArraySetup()
 *	and in CGLElementArraySetup::CGLElementArraySetup(). The old code counted on texcoord
 *	data type being the same as the position data type.
 *
 *	@date 2012-06-19
 *
 *	Added \#pragma once.
 *
 */

#include "OpenGL40.h"

/**
 *	@def __GL4BUFFER_OBJECT_HOLD_SIZE
 *	@brief if defined, CGLBufferObject will contain buffer size as a member variable,
 *		CGLBufferObject::n_Size() is compiled
 */
//#define __GL4BUFFER_OBJECT_HOLD_SIZE

/**
 *	@def __GL4BUFFER_OBJECT_HOLD_USAGE
 *	@brief if defined, CGLBufferObject will contain buffer usage as a member variable,
 *		CGLBufferObject::n_Usage() is compiled
 */
//#define __GL4BUFFER_OBJECT_HOLD_USAGE

/**
 *	@def __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
 *	@brief if defined, CGLBufferObject will contain mapping status and access
 *		flags as member variables, CGLBufferObject::b_Mapped()
 *		and CGLBufferObject::n_AccessFlag() are compiled
 */
//#define __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

/**
 *	@def __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
 *	@brief if defined, the default VAO 0 is bound at the end of
 *		CGLArraySetup::Draw() and CGLElementArraySetup::Draw()
 *	@note This was added to help debugging VAO issues.
 */
//#define __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING

/**
 *	@def __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER
 *	@brief if defined, index buffer is bound in CGLElementArraySetup::Draw()
 *		before calling glDrawElements()
 *	@note This fixes a bug occuring in some of the mobile chipsets where
 *		GL_ELEMENT_ARRAY_BINDING is not (properly) stored in VAO state.
 *		One example of such chipset may be even NVIDIA Quadro NVS 4200M.
 */
#define __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER

/**
 *	@brief class, wrapping GL_ARB_vertex_buffer_object / GL_ARB_pixel_buffer_object extensions
 *
 *	Object of this class holds a single vertex buffer object, it is created in OpenGL when
 *		one of Bind(), BindAs() or BufferData() is called first time. OpenGL object is destroyed
 *		once destructor is called.
 */
class CGLBufferObject {
protected:
	GLuint m_n_buffer_id; /**< @brief buffer object OpenGL id */

#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	GLuint m_n_size; /**< @brief buffer size, in bytes */
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE

#ifdef __GL4BUFFER_OBJECT_HOLD_USAGE
	int m_n_usage; /**< @brief buffer usage */
#endif // __GL4BUFFER_OBJECT_HOLD_USAGE

#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	int m_n_access; /**< @brief mapped buffer access mode */
	bool m_b_mapped; /**< @brief buffer mapping status */
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

public:
	/**
	 *	@brief default constructor; creates name for the buffer object
	 */
	CGLBufferObject();

	/**
	 *	@brief destructor; deletes the buffer object
	 */
	~CGLBufferObject();

	/**
	 *	@brief swaps two buffer objects
	 *	@param[in] r_other is the other object to swap with
	 */
	inline void Swap(CGLBufferObject &r_other)
	{
		std::swap(m_n_buffer_id, r_other.m_n_buffer_id);
#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
		std::swap(m_n_size, r_other.m_n_size);
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE
#ifdef __GL4BUFFER_OBJECT_HOLD_USAGE
		std::swap(m_n_usage, r_other.m_n_usage);
#endif // __GL4BUFFER_OBJECT_HOLD_USAGE
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
		std::swap(m_n_access, r_other.m_n_access);
		std::swap(m_b_mapped, r_other.m_b_mapped);
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	}

	/**
	 *	@brief gets buffer status
	 *	@return Returns true if the buffer was created successulfy, otherwise returns false.
	 */
	inline bool b_Status() const
	{
		return m_n_buffer_id != 0;
	}

	/**
	 *	@brief binds the buffer
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *
	 *	@note With GL_PIXEL_UNPACK_BUFFER, it is possible to copy data using glReadPixels()
	 *		or glGetTexImage() with <data> parameter set to the value returned
	 *		by call to p_OffsetPointer(0).
	 */
	void Bind(GLenum n_target) const;

	/**
	 *	@brief allocates the buffer
	 *
	 *	Allocates buffer data of size n_size bytes, can be initialized to p_data in case
	 *		non-NULL pointer is supplied. In case buffer was allocated already, it's data
	 *		are freed as soon as there are no OpenGL commands, sourcing data from it
	 *		(it shouldn't block program execution, depends on graphics card vendor and
	 *		driver version) and new buffer is allocated.
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_size is buffer size in bytes
	 *	@param[in] p_data is pointer to n_size bytes of buffer data, or null pointer
	 *	@param[in] n_usage is optimalization hint only, it is one of:
	 *		GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
	 *		GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
	 *		GL_DYNAMIC_DRAW, GL_DYNAMIC_READ or GL_DYNAMIC_COPY
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 *	@note This may generate GL_OUT_OF_MEMORY error.
	 */
	bool BufferData(GLenum n_target, GLuint n_size, const void *p_data = 0, GLenum n_usage = GL_STATIC_DRAW);

	/**
	 *	@brief gets server-side pointer to the buffer
	 *
	 *	Returns pointer to buffer data for use with glVertexAttribPointer(), glDrawElements(), etc.
	 *		This pointer is only associated with this buffer, in case this buffer is currently bound.
	 *		Do not use this pointer to access data, it doesn't point to client address space.
	 *
	 *	@param[in] n_offset is offset to referenced place in buffer, in bytes
	 *
	 *	@return Returns pointer to buffer data.
	 *
	 *	@note Do not compare returned pointer to null, it is perfectly valid server-side pointer value.
	 */
	static inline void *p_OffsetPointer(GLuint n_offset)
	{
		return (void*)((uint8_t*)NULL + n_offset);
	}

	/**
	 *	@brief (re)specifies part (or all) of buffer data
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_offset is offset in buffer to start of data being specified
	 *	@param[in] n_size is data size in bytes
	 *	@param[in] p_data is pointer to n_size bytes of buffer data
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 *	@note n_offset must be greater or equal to 0, n_offset + n_size must be less or equal
	 *		to buffer size, otherwise OpenGL error is generated (call glGetError()).
	 */
	void BufferSubData(GLenum n_target, GLuint n_offset, GLuint n_size, const void *p_data);

	/**
	 *	@brief reads part (or all) of buffer data
	 *
	 *	Copies n_size bytes of buffer data, starting at byte n_offset to p_data.
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_offset is offset in buffer to start of data being specified
	 *	@param[in] n_size is data size in bytes (n_offset + n_size must be less or equal
	 *		to buffer size, otherwise OpenGL error is generated)
	 *	@param[in] p_data is pointer, n_size bytes of buffer data will be copied to
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 */
	void GetBufferSubData(GLenum n_target, GLuint n_offset, GLuint n_size, void *p_data) const;

	/**
	 *	@brief maps buffer contents to client space
	 *
	 *	Maps buffer into client address space (one buffer only can be mapped at a time).
	 *		Mapped buffer should be un-mapped by calling UnMap(). In case buffer was mapped,
	 *		pointer returned by this function can be retrieved again, by calling
	 *		p_Get_BufferPointer().
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_access is buffer access, it can be one of GL_READ_ONLY, GL_WRITE_ONLY
	 *		or GL_READ_WRITE
	 *
	 *	@return Returns client address space pointer in case of success or NULL on failure.
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 */
	void *p_Map(GLenum n_target, GLenum n_access = GL_READ_WRITE);

	/**
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_offset is offset of range to map (in bytes)
	 *	@param[in] n_length is length of range to map (in bytes)
	 *	@param[in] n_access is buffer access, it can be 0 or combination of GL_MAP_READ_BIT,
	 *		GL_MAP_WRITE_BIT, GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT,
	 *		GL_MAP_FLUSH_EXPLICIT_BIT or GL_MAP_UNSYNCHRONIZED_BIT.
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 *	@note This requires GL_ARB_map_buffer_range (part of OpenGL 3.0 core).
	 */
	void *p_MapRange(GLenum n_target, GLintptr n_offset, GLsizeiptr n_length, GLbitfield n_access);

	/**
	 *	@brief explicitly flushes mapped buffer sub-range
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *	@param[in] n_offset is offset of range to map (in bytes)
	 *	@param[in] n_length is length of range to map (in bytes)
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 *	@note This requires GL_ARB_map_buffer_range (part of OpenGL 3.0 core).
	 */
	void FlushMappedRange(GLenum n_target, GLintptr n_offset, GLsizeiptr n_length);

	/**
	 *	@brief gets client-side pointer to mapped buffer
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *
	 *	@return In case buffer object has been successfuly mapped, return client-space pointer,
	 *		otherwise return NULL.
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 *	@ntoe This involves calling glGetBufferPointerv() and may cause GPU-CPU synchronization.
	 */
	void *p_Get_BufferPointer(GLenum n_target) const;

	/**
	 *	@brief unmaps buffer
	 *
	 *	Ends mapping of buffer data to client address space, previously estabilished
	 *		calling p_Map().
	 *
	 *	@param[in] n_target is buffer object target, valid values include GL_ARRAY_BUFFER,
	 *		GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_DRAW_INDIRECT_BUFFER,
	 *		GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
	 *		GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER and GL_UNIFORM_BUFFER.
	 *
	 *	@return Returns true on success or false on failure (buffer contents were
	 *		lost - that may be caused by screen-mode switch while the buffer was
	 *		bound, or in case the buffer was not mapped).
	 *
	 *	@note The buffer must be bound to the specified target before calling this.
	 */
	bool Unmap(GLenum n_target);

#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	/**
	 *	@brief gets size of buffer
	 *
	 *	@return Returns size of buffer, in bytes.
	 *
	 *	@note This is only available if __GL4BUFFER_OBJECT_HOLD_SIZE is defined.
	 */
	inline GLuint n_Size() const
	{
		return m_n_size;
	}
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE

#ifdef __GL4BUFFER_OBJECT_HOLD_USAGE
	/**
	 *	@brief gets buffer ussage
	 *
	 *	@return Returns buffer ussage (one of:
	 *		GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
	 *		GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
	 *		GL_DYNAMIC_DRAW, GL_DYNAMIC_READ or GL_DYNAMIC_COPY).
	 *
	 *	@note This is only available if __GL4BUFFER_OBJECT_HOLD_USAGE is defined.
	 */
	inline GLenum n_Usage() const
	{
		return m_n_usage;
	}
#endif // __GL4BUFFER_OBJECT_HOLD_USAGE

#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	/**
	 *	@brief determines wheter buffer is mapped
	 *
	 *	@return Returns true in case buffer contents were mapped into client-space memory.
	 *
	 *	@note It's possible to obtain pointer to mapped buffer data by call to p_Get_BufferPointer().
	 *	@note This is only available if __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO is defined.
	 */
	inline bool b_Mapped() const
	{
		return m_b_mapped;
	}

	/**
	 *	@brief determines mapped buffer access flags
	 *
	 *	@return Returns access flag to buffer in case it has been mapped (one of GL_READ_ONLY,
	 *		GL_WRITE_ONLY, GL_READ_WRITE), or in case buffer is not mapped, returns 0.
	 *
	 *	@note This is only available if __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO is defined.
	 */
	inline GLenum n_AccessFlag() const
	{
		return (m_b_mapped)? m_n_access : 0;
	}
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

protected:
	inline bool b_IsBound(GLenum n_target) const;

private:
	inline CGLBufferObject(const CGLBufferObject &UNUSED(r_buff_obj)) {} /**< @brief copy-constructor (cannot be used) */
	inline CGLBufferObject &operator =(const CGLBufferObject &UNUSED(r_buff_obj)) { return *this; } /**< @brief copy-operator (cannot be used) */
};

/**
 *	@brief OpenGL buffer object wrapper with the default target specifier
 */
template <const GLenum n_default_target>
class CGLBufferObject_Target : public CGLBufferObject {
public:
	enum {
		default_Target = n_default_target /**< @brief default buffer binding target */
	};

public:
	/**
	 *	@copydoc CGLBufferObject::Bind()
	 */
	inline void Bind() const
	{
		CGLBufferObject::Bind(default_Target);
	}

	/**
	 *	@copydoc CGLBufferObject::BufferData()
	 */
	bool BufferData(GLuint n_size, const void *p_data = 0, GLenum n_usage = GL_STATIC_DRAW)
	{
		return CGLBufferObject::BufferData(default_Target, n_size, p_data, n_usage);
	}

	/**
	 *	@copydoc CGLBufferObject::BufferSubData()
	 */
	void BufferSubData(GLuint n_offset, GLuint n_size, const void *p_data)
	{
		CGLBufferObject::BufferSubData(default_Target, n_offset, n_size, p_data);
	}

	/**
	 *	@copydoc CGLBufferObject::GetBufferSubData()
	 */
	void GetBufferSubData(GLuint n_offset, GLuint n_size, void *p_data) const
	{
		CGLBufferObject::GetBufferSubData(default_Target, n_offset, n_size, p_data);
	}

	/**
	 *	@copydoc CGLBufferObject::p_Map()
	 */
	void *p_Map(GLenum n_access = GL_READ_WRITE)
	{
		return CGLBufferObject::p_Map(default_Target, n_access);
	}

	/**
	 *	@copydoc CGLBufferObject::p_MapRange()
	 */
	void *p_MapRange(GLenum n_target, GLintptr n_offset, GLsizeiptr n_length, GLbitfield n_access)
	{
		return CGLBufferObject::p_MapRange(default_Target, n_offset, n_length, n_access);
	}

	/**
	 *	@copydoc CGLBufferObject::FlushMappedRange()
	 */
	void FlushMappedRange(GLenum n_target, GLintptr n_offset, GLsizeiptr n_length)
	{
		CGLBufferObject::FlushMappedRange(default_Target, n_offset, n_length);
	}

	/**
	 *	@copydoc CGLBufferObject::p_Get_BufferPointer()
	 */
	void *p_Get_BufferPointer() const
	{
		return CGLBufferObject::p_Get_BufferPointer(default_Target);
	}

	/**
	 *	@copydoc CGLBufferObject::Unmap()
	 */
	bool Unmap()
	{
		return CGLBufferObject::Unmap(default_Target);
	}
};

/**
 *	@brief array buffer object
 */
typedef CGLBufferObject_Target<GL_ARRAY_BUFFER> CGLArrayBufferObject;

/**
 *	@brief element array buffer object (index buffer object)
 */
typedef CGLBufferObject_Target<GL_ELEMENT_ARRAY_BUFFER> CGLElementArrayBufferObject;

/**
 *	@brief draw indirect buffer object
 */
typedef CGLBufferObject_Target<GL_DRAW_INDIRECT_BUFFER> CGLDrawIndirectBufferObject;

/**
 *	@brief texture buffer object
 */
typedef CGLBufferObject_Target<GL_TEXTURE_BUFFER> CGLTextureBufferObject;

/**
 *	@brief transform feedback buffer object
 */
typedef CGLBufferObject_Target<GL_TRANSFORM_FEEDBACK_BUFFER> CGLTransformFeedbackBufferObject;

/**
 *	@brief uniform buffer object
 */
typedef CGLBufferObject_Target<GL_UNIFORM_BUFFER> CGLUniformBufferObject;

/**
 *	@brief pixel (pack) buffer object
 *	@note When a buffer object is bound to the GL_PIXEL_PACK_BUFFER target, commands
 *		such as glReadPixels pack (write) their data into a buffer object.
 *		When a buffer object is bound to the GL_PIXEL_UNPACK_BUFFER target,
 *		commands such as glDrawPixels and glTexImage2D unpack (read) their
 *		data from a buffer object.
 */
typedef CGLBufferObject_Target<GL_PIXEL_PACK_BUFFER> CGLPixelBufferObject;

/**
 *	@brief a simple class, encapsulating vertex array object
 */
class CGLVertexArrayObject {
protected:
	GLuint m_n_name;

public:
	/**
	 *	@brief default constructor; creates the vertex array object
	 */
	CGLVertexArrayObject();

	/**
	 *	@brief destructor; deletes the vertex array object, if created successfully
	 */
	~CGLVertexArrayObject();

	/**
	 *	@brief gets vertex array status
	 *	@return Returns true if the vertex array object was created successfully,
	 *		otherwise returns false.
	 */
	bool b_Status() const;

	/**
	 *	@brief binds the vertex array object
	 */
	inline void Bind() const
	{
		glBindVertexArray(m_n_name);
	}

	/**
	 *	@brief binds the default vertex array object,
	 *		effectively releasing currently bound array object
	 */
	static inline void Release()
	{
		glBindVertexArray(0);
	}

	/**
	 *	@brief swaps two vertex array objects
	 *	@param[in] r_other is the other object to swap with
	 */
	inline void Swap(CGLVertexArrayObject &r_other)
	{
		std::swap(m_n_name, r_other.m_n_name);
	}

private:
	inline CGLVertexArrayObject(const CGLVertexArrayObject &UNUSED(r_array)) {} /**< @brief copy-constructor (cannot be used) */
	inline CGLVertexArrayObject &operator =(const CGLVertexArrayObject &UNUSED(r_array)) { return *this; } /**< @brief copy-operator (cannot be used) */
};

namespace std {

inline void swap(CGLBufferObject &r_one, CGLBufferObject &r_other)
{
	r_one.Swap(r_other);
}

template <GLenum n_target>
inline void swap(CGLBufferObject_Target<n_target> &r_one, CGLBufferObject_Target<n_target> &r_other)
{
	r_one.Swap(r_other);
}

inline void swap(CGLVertexArrayObject &r_one, CGLVertexArrayObject &r_other)
{
	r_one.Swap(r_other);
}

} // štd

/**
 *	@brief a simple class, encapsulating allocation of vertex
 *		array containing some simple statically allocated geometry
 *	@note This requires the GL_ARB_vertex_array_object extension (part of OpenGL 3.0 core).
 */
class CGLArraySetup {
protected:
	CGLArrayBufferObject vertex_buffer; /**< @brief vertex buffer */

	CGLVertexArrayObject vertex_array_object; /**< @brief vertex array object */

	GLenum m_n_draw_mode; /**< @brief draw mode (name of primitives to render) */
	size_t m_n_vertex_num; /**< @brief number of indices to render */

public:
	/**
	 *	@brief constructor; fills the arrays with data
	 *
	 *	Use this constructor to specify indexed geometry with positions
	 *	in vertex attribute 0 and texcoords in vertex attribute 1.
	 *	To specify different layouts or different types of geometry entirely,
	 *	use CBasicStaticRenderable().
	 *
	 *	@param[in] p_vertex_list is buffer containing the vertex data
	 *	@param[in] n_vertices_size_bytes is size of all the vertex data, in bytes
	 *	@param[in] n_vertex_stride_bytes is offset between two vertices, in bytes
	 *	@param[in] n_position_data_type is OpenGL data type used to represent positions
	 *	@param[in] n_position_component_num is number of position components
	 *	@param[in] n_position_offset_bytes is offset to the first position, in bytes
	 *	@param[in] n_texcoord_data_type is OpenGL data type used to represent texcoords
	 *	@param[in] n_texcoord_component_num is number of texcoord components
	 *	@param[in] n_texcoord_offset_bytes is offset to the first texcoord, in bytes
	 *	@param[in] p_index_list is buffer, containing index data
	 *	@param[in] n_index_num is number of indices to render
	 *	@param[in] n_index_data_type is data type of vertex indices
	 *	@param[in] n_draw_mode is draw mode (name of primitives to render)
	 *	@param[in] b_normalize_position_data is position data normalization flag
	 *		(if set and position is stored using integer data type, it is normalized
	 *		to [0, 1] or [-1, 1] range depending on signedness; otherwise it is interpreted
	 *		as the integer value; default false)
	 *	@param[in] b_normalize_texcoord_data is texture coordinate data normalization flag
	 *		(if set and position is stored using integer data type, it is normalized
	 *		to [0, 1] or [-1, 1] range depending on signedness; otherwise it is interpreted
	 *		as the integer value; default false)
	 */
	CGLArraySetup(const void *p_vertex_list, size_t n_vertices_size_bytes,
		int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
		int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
		int n_texcoord_offset_bytes, GLenum n_draw_mode, bool b_normalize_position_data = false,
		bool b_normalize_texcoord_data = false);

	/**
	 *	@brief gets status of this renderable
	 *	@return Returns true if the array buffer and VAO allocated successfully,
	 *		otherwise returns false.
	 */
	bool b_Status() const;

	/**
	 *	@brief binds the VAO and calls glDrawArrays() to display the geometry
	 */
	inline void Draw() const
	{
		vertex_array_object.Bind();
		_ASSERTE(m_n_vertex_num <= UINT_MAX);
		glDrawArrays(m_n_draw_mode, 0, GLuint(m_n_vertex_num));
#ifdef __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
		vertex_array_object.Release();
#endif // __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
	}

	/**
	 *	@brief binds the VAO and calls glDrawArrays() to display the geometry,
	 *		adds override option for some of the parameters
	 *
	 *	@param[in] n_draw_mode Specifies what kind of primitives to render. Symbolic constants GL_POINTS,
	 *		GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY,
	 *		GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY,
	 *		GL_TRIANGLES_ADJACENCY and GL_PATCHES are accepted.
	 *	@param[in] n_first_vertex Specifies the starting index in the enabled arrays.
	 *	@param[in] n_vertex_num Specifies the number of indices to be rendered.
	 */
	inline void Draw(GLenum n_draw_mode, size_t n_first_vertex, size_t n_vertex_num) const
	{
		vertex_array_object.Bind();
		_ASSERTE(n_first_vertex <= UINT_MAX);
		_ASSERTE(n_vertex_num <= UINT_MAX);
		glDrawArrays(n_draw_mode, GLuint(n_first_vertex), GLuint(n_vertex_num));
#ifdef __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
		vertex_array_object.Release();
#endif // __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
	}

	/**
	 *	@brief swaps two buffer objects
	 *	@param[in] r_other is the other array setup to swap with
	 */
	inline void Swap(CGLArraySetup &r_other)
	{
		std::swap(vertex_buffer, r_other.vertex_buffer);
		std::swap(vertex_array_object, r_other.vertex_array_object);
		std::swap(m_n_draw_mode, r_other.m_n_draw_mode);
		std::swap(m_n_vertex_num, r_other.m_n_vertex_num);
	}

protected:
	/**
	 *	@brief default constructor; has no effect
	 *	@note This constructor must be overloaded so the buffers are filled with data.
	 */
	inline CGLArraySetup()
	{}
};

/**
 *	@copydoc CGLArraySetup
 */
class CGLArraySetup_DefConst : public CGLArraySetup {
public:
	/**
	 *	@brief default constructor; makes an empty array setup that cannot be rendered
	 */
	CGLArraySetup_DefConst()
	{
		m_n_draw_mode = 0;
		m_n_vertex_num = 0;
	}

	/**
	 *	@copydoc CGLArraySetup::CGLArraySetup(const void*,size_t,int,GLenum,int,int,GLenum,int,int,GLenum,bool,bool)
	 */
	CGLArraySetup_DefConst(const void *p_vertex_list, size_t n_vertices_size_bytes,
		int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
		int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
		int n_texcoord_offset_bytes, GLenum n_draw_mode, bool b_normalize_position_data = false,
		bool b_normalize_texcoord_data = false)
		:CGLArraySetup(p_vertex_list, n_vertices_size_bytes, n_vertex_stride_bytes,
		n_position_data_type, n_position_component_num, n_position_offset_bytes,
		n_texcoord_data_type, n_texcoord_component_num, n_texcoord_offset_bytes,
		n_draw_mode, b_normalize_position_data, b_normalize_texcoord_data)
	{}
};

/**
 *	@brief a simple class, encapsulating allocation of vertex
 *		array containing some simple statically allocated geometry
 *	@note This requires the GL_ARB_vertex_array_object extension (part of OpenGL 3.0 core).
 */
class CGLElementArraySetup {
protected:
	CGLArrayBufferObject vertex_buffer; /**< @brief vertex buffer */
	CGLElementArrayBufferObject index_buffer; /**< @brief index buffer */

	CGLVertexArrayObject vertex_array_object; /**< @brief vertex array object */

	GLenum m_n_draw_mode; /**< @brief draw mode (name of primitives to render) */
	GLenum m_n_index_data_type; /**< @brief data type of vertex indices */
	size_t m_n_index_num; /**< @brief number of indices to render */

public:
	/**
	 *	@brief constructor; fills the arrays with data
	 *
	 *	Use this constructor to specify indexed geometry with positions
	 *	in vertex attribute 0 and texcoords in vertex attribute 1.
	 *	To specify different layouts or different types of geometry entirely,
	 *	use CBasicStaticRenderable().
	 *
	 *	@param[in] p_vertex_list is buffer containing the vertex data
	 *	@param[in] n_vertices_size_bytes is size of all the vertex data, in bytes
	 *	@param[in] n_vertex_stride_bytes is offset between two vertices, in bytes
	 *	@param[in] n_position_data_type is OpenGL data type used to represent positions
	 *	@param[in] n_position_component_num is number of position components
	 *	@param[in] n_position_offset_bytes is offset to the first position, in bytes
	 *	@param[in] n_texcoord_data_type is OpenGL data type used to represent texcoords
	 *	@param[in] n_texcoord_component_num is number of texcoord components
	 *	@param[in] n_texcoord_offset_bytes is offset to the first texcoord, in bytes
	 *	@param[in] p_index_list is buffer, containing index data
	 *	@param[in] n_index_num is number of indices to render
	 *	@param[in] n_index_data_type is data type of vertex indices
	 *	@param[in] n_draw_mode is draw mode (name of primitives to render)
	 *	@param[in] b_normalize_position_data is position data normalization flag
	 *		(if set and position is stored using integer data type, it is normalized
	 *		to [0, 1] or [-1, 1] range depending on signedness; otherwise it is interpreted
	 *		as the integer value; default false)
	 *	@param[in] b_normalize_texcoord_data is texture coordinate data normalization flag
	 *		(if set and position is stored using integer data type, it is normalized
	 *		to [0, 1] or [-1, 1] range depending on signedness; otherwise it is interpreted
	 *		as the integer value; default false)
	 */
	CGLElementArraySetup(const void *p_vertex_list, size_t n_vertices_size_bytes,
		int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
		int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
		int n_texcoord_offset_bytes, const void *p_index_list, size_t n_index_num,
		GLenum n_index_data_type, GLenum n_draw_mode, bool b_normalize_position_data = false,
		bool b_normalize_texcoord_data = false);

	/**
	 *	@brief gets status of this renderable
	 *	@return Returns true if all the buffers and VAO allocated successfully,
	 *		otherwise returns false.
	 */
	bool b_Status() const;

	/**
	 *	@brief binds the VAO and calls glDrawElements() to display the geometry
	 */
	inline void Draw() const
	{
		vertex_array_object.Bind();
#ifdef __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER
		index_buffer.Bind();
#endif // __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER
		_ASSERTE(m_n_index_num <= UINT_MAX);
		glDrawElements(m_n_draw_mode, GLuint(m_n_index_num), m_n_index_data_type, 0);
#ifdef __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
		vertex_array_object.Release();
#endif // __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
	}

	/**
	 *	@brief binds the VAO and calls glDrawElements() to display the geometry,
	 *		adds override option for some of the parameters
	 *
	 *	@param[in] n_draw_mode Specifies what kind of primitives to render. Symbolic constants GL_POINTS,
	 *		GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY,
	 *		GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY,
	 *		GL_TRIANGLES_ADJACENCY and GL_PATCHES are accepted.
	 *	@param[in] n_index_num Specifies the number of elements to be rendered.
	 *	@param[in] n_first_index_byte_offset Specifies a pointer to the location where the indices are stored.
	 */
	inline void Draw(GLenum n_draw_mode, size_t n_index_num, size_t n_first_index_byte_offset = 0) const
	{
		vertex_array_object.Bind();
#ifdef __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER
		index_buffer.Bind();
#endif // __GL4VERTEX_ARRAY_OBJECT_REBIND_INDEX_BUFFER
		_ASSERTE(n_index_num <= UINT_MAX);
		_ASSERTE(n_index_num <= m_n_index_num);
		glDrawElements(n_draw_mode, GLuint(n_index_num),
			m_n_index_data_type, (const void*)n_first_index_byte_offset);
#ifdef __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
		vertex_array_object.Release();
#endif // __GL4VERTEX_ARRAY_OBJECT_RELEASE_AFTER_DRAWING
	}

	/**
	 *	@brief swaps two buffer objects
	 *	@param[in] r_other is the other element array setup to swap with
	 */
	inline void Swap(CGLElementArraySetup &r_other)
	{
		std::swap(vertex_buffer, r_other.vertex_buffer);
		std::swap(index_buffer, r_other.index_buffer);
		std::swap(vertex_array_object, r_other.vertex_array_object);
		std::swap(m_n_draw_mode, r_other.m_n_draw_mode);
		std::swap(m_n_index_data_type, r_other.m_n_index_data_type);
		std::swap(m_n_index_num, r_other.m_n_index_num);
	}

protected:
	/**
	 *	@brief default constructor; has no effect
	 *	@note This constructor must be overloaded so the buffers are filled with data.
	 */
	inline CGLElementArraySetup()
	{}
};

/**
 *	@copydoc CGLArraySetup
 */
class CGLElementArraySetup_DefConst : public CGLElementArraySetup {
public:
	/**
	 *	@brief default constructor; makes an empty array setup that cannot be rendered
	 */
	CGLElementArraySetup_DefConst()
	{
		m_n_draw_mode = 0;
		m_n_index_data_type = 0;
		m_n_index_num = 0;
	}

	/**
	 *	@copydoc CGLElementArraySetup::CGLElementArraySetup(const void*,size_t,int,GLenum,int,int,GLenum,int,int,const void*,size_t,GLenum,GLenum,bool,bool)
	 */
	CGLElementArraySetup_DefConst(const void *p_vertex_list, size_t n_vertices_size_bytes,
		int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
		int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
		int n_texcoord_offset_bytes, const void *p_index_list, size_t n_index_num,
		GLenum n_index_data_type, GLenum n_draw_mode, bool b_normalize_position_data = false,
		bool b_normalize_texcoord_data = false)
		:CGLElementArraySetup(p_vertex_list, n_vertices_size_bytes, n_vertex_stride_bytes,
		n_position_data_type, n_position_component_num, n_position_offset_bytes,
		n_texcoord_data_type, n_texcoord_component_num, n_texcoord_offset_bytes,
		p_index_list, n_index_num, n_index_data_type, n_draw_mode,
		b_normalize_position_data, b_normalize_texcoord_data)
	{}
};

namespace std {

inline void swap(CGLArraySetup &r_one, CGLArraySetup &r_other)
{
	r_one.Swap(r_other);
}

inline void swap(CGLElementArraySetup &r_one, CGLElementArraySetup &r_other)
{
	r_one.Swap(r_other);
}

inline void swap(CGLArraySetup_DefConst &r_one,
	CGLArraySetup_DefConst &r_other)
{
	r_one.Swap(r_other);
}

inline void swap(CGLElementArraySetup_DefConst &r_one,
	CGLElementArraySetup_DefConst &r_other)
{
	r_one.Swap(r_other);
}

} // štd

#endif // !__BUFFER_OBJECTS_INCLUDED
