#pragma once

#ifndef __gx_vertexbuffer_h__
	#define __gx_vertexbuffer_h__

#include "buffer.h"
#include <vector>

namespace Gx
{
	struct VertexAttributeFormat
	{
		enum Enum
		{
			Float,
			Vector2,
			Vector3,
			Vector4,
			Color, //rgba
			Byte4,
			Short4,

			MaxFormat // only internal use
		};
	};

	struct VertexAttributeSemantic
	{
		enum Enum
		{
			Position,
			Color,
			Normal,
			Tangent,
			TexCoord,
			TexCoord0 = TexCoord,
			TexCoord1,
			TexCoord2,
			TexCoord3,
			TexCoordMax = TexCoord3,

			MaxSemantic // only internal use
		};
	};

	struct GX_API  VertexAttribute
	{
		VertexAttribute(GLuint location, VertexAttributeSemantic::Enum semantic, VertexAttributeFormat::Enum format, GLuint offset) {
			this->location = location;
			this->semantic = semantic;
			this->format = format;
			this->offset = offset;
			this->locked = false;
		}

		bool locked;
		GLuint offset;
		GLuint location;
		VertexAttributeFormat::Enum format;
		VertexAttributeSemantic::Enum semantic;
	};

	typedef std::vector<VertexAttribute> VertexAttributeCollection;

	struct GX_API VertexDeclaration
	{
		VertexDeclaration(const VertexAttributeCollection& attributes, GLuint stride) {
			this->attributes = attributes;
			this->stride = stride;
		}

		VertexAttribute* getBySemantic(VertexAttributeSemantic::Enum semantic) {
			for(VertexAttributeCollection::iterator i = attributes.begin() ; i != attributes.end() ; ++i)
			{
				if (i->semantic == semantic)
					return &(*i);
			}

			return nullptr;
		}

		VertexAttribute* getByLocation(GLuint location) {
			for(VertexAttributeCollection::iterator i = attributes.begin() ; i != attributes.end() ; ++i)
			{
				if (i->location == location)
					return &(*i);
			}

			return nullptr;
		}

		GLuint stride;
		VertexAttributeCollection attributes;
	};

	class GX_API VertexBuffer : public Buffer
	{
		friend class GraphicsDevice;

	public:
		template<typename T>
		void setData(const T* data) {
			//TODO: sizeof(T) or T::SizeInBytes ?
			this->copyData(m_vertexCount * sizeof(T), reinterpret_cast<const GLvoid*>(data));
		}

		GLuint getVertexCount() const {
			return m_vertexCount;
		}

		GLuint getStride() const {
			return m_vertexDeclaration.stride;
		}

		void bind() {
			glBindBuffer(m_target, m_id);

			GLuint attributeCount = m_vertexDeclaration.attributes.size();
			for (GLuint i = 0 ; i < attributeCount ; ++i )
			{
				VertexAttribute* attribute = &m_vertexDeclaration.attributes[i];

				GLint formatSize = this->getFormatSize(attribute->format);
				GLenum formatType = this->getFormatType(attribute->format);
				const GLvoid* offset = reinterpret_cast<GLvoid*>(attribute->offset);

				glVertexAttribPointer(attribute->location, formatSize, formatType, GL_FALSE, m_vertexDeclaration.stride, offset);
				glEnableVertexAttribArray(attribute->location);
			}

			glBindBuffer(m_target, 0);
		}

		void unbind() {
			glBindBuffer(m_target, m_id);

			GLuint attributeCount = m_vertexDeclaration.attributes.size();
			for (GLuint i = 0 ; i < attributeCount ; ++i )
			{
				VertexAttribute* attribute = &m_vertexDeclaration.attributes[i];
				glDisableVertexAttribArray(attribute->location);
			}

			glBindBuffer(m_target, 0);
		}

	protected:
		VertexBuffer(const VertexDeclaration& vertexDeclaration, GLuint vertexCount, GLenum usage)
			: Buffer(GL_ARRAY_BUFFER, usage), m_vertexDeclaration(vertexDeclaration), m_vertexCount(vertexCount)
		{
			this->reserve(vertexCount * vertexDeclaration.stride);

			GX_ASSERT(vertexDeclaration.attributes.size() > 0, "Cannot create vertex buffer without vertex attributes.");
		}

		GLint getFormatSize(VertexAttributeFormat::Enum format) {
			GLint size = 0;
			switch(format)
			{
			case VertexAttributeFormat::Float:		size = 1; break;
			case VertexAttributeFormat::Vector2:	size = 2; break;
			case VertexAttributeFormat::Vector3:	size = 3; break;
			case VertexAttributeFormat::Vector4:	size = 4; break;
			case VertexAttributeFormat::Color:		size = 4; break;
			case VertexAttributeFormat::Byte4:		size = 4; break;
			case VertexAttributeFormat::Short4:		size = 4; break;
			}
			GX_ASSERT(size != 0, "Cannot compute attribute format size.");

			return size;
		}

		GLenum getFormatType(VertexAttributeFormat::Enum format) {
			GLenum type = 0;
			switch(format)
			{
			case VertexAttributeFormat::Float:		type = GL_FLOAT; break;
			case VertexAttributeFormat::Vector2:	type = GL_FLOAT; break;
			case VertexAttributeFormat::Vector3:	type = GL_FLOAT; break;
			case VertexAttributeFormat::Vector4:	type = GL_FLOAT; break;
			case VertexAttributeFormat::Color:		type = GL_UNSIGNED_BYTE; break;
			case VertexAttributeFormat::Byte4:		type = GL_BYTE; break;
			case VertexAttributeFormat::Short4:		type = GL_SHORT; break;
			}
			GX_ASSERT(type != 0, "Cannot compute attribute format size.");

			return type;
		}

	private:
		GLuint m_vertexCount;
		VertexDeclaration m_vertexDeclaration;
	};
}

#endif /* __gx_vertexbuffer_h__ */