

#define GL_GLEXT_PROTOTYPES

#include <GL/glext.h>

#include <GL/gl.h> // GL 1.1 functions 

#include <GL/glx.h>

#include "block.h"

#include "shader.h"

#include "program.h"

#include "floatArray.h"

#include "unsignedIntegerArray.h"



class mesh{

	struct program * program;

	struct unsignedIntegerArray * indices;


	struct floatArray * textureCoordinates;

	struct floatArray * vertexCoordinates;

	struct floatArray * normalCoordinates;


	struct array * blocks	= new array();


	GLuint vertexArrayObject;


	GLuint uniformBuffer;

	GLuint indexBuffer;

	GLuint vertexbuffer;

	GLuint textureCoordinateBuffer;

	GLuint meshIndexBuffer;

	GLuint uvBuffer;

	struct unsignedIntegerArray * uniformBuffers	= new unsignedIntegerArray();



	struct block * getUniformBlock( char * blockName ) {

		int blockCount 	= this->blocks->length();

		for ( int i = 0; i < blockCount; ++i )
		{
			struct block * currentBlock			= this->blocks->get( i );

			char * currentBlockName				= currentBlock->name;

			if( currentBlockName == blockName ) {

				return currentBlock;

			}

		}

		return NULL;

	}



	void bindBlock( struct block * blockInstance ) {

		//printf("%s\n", blockInstance);

		blockInstance->createBuffer();

		this->blocks->add( blockInstance );

	}

	void setProgram( struct program * currentProgram ) {

		this->program	= currentProgram;

	}

	GLuint getGLTypeSize( GLuint type ) {

		switch( type ) {

			case GL_FLOAT:

				return sizeof( GLfloat );

			break;

			case GL_INT:

				return sizeof( GLint );

			break;


			case GL_UNSIGNED_INT:

				return sizeof( GLuint );

			break;
		}

		return 0;

	}

	GLuint getComponentType( GLuint type ) {


		switch( type ) {

			case GL_FLOAT:

				return GL_FLOAT;

			break;

			case GL_FLOAT_VEC2:

				return GL_FLOAT;

			break;

			case GL_FLOAT_VEC3:

				return GL_FLOAT;

			break;

			case GL_FLOAT_VEC4:

				return GL_FLOAT;

			break;


			case GL_INT:

				return GL_INT;

			break;
			
			case GL_INT_VEC2:

				return GL_INT;

			break;

			case GL_INT_VEC3:

				return GL_INT;

			break;

			case GL_INT_VEC4:

				return GL_INT;

			break;


			case GL_UNSIGNED_INT:

				return GL_UNSIGNED_INT;

			break;
			
			case GL_UNSIGNED_INT_VEC2:

				return GL_UNSIGNED_INT;

			break;

			case GL_UNSIGNED_INT_VEC3:

				return GL_UNSIGNED_INT;

			break;

			case GL_UNSIGNED_INT_VEC4:

				return GL_UNSIGNED_INT;

			break;

		}


		return 0;

	}

	GLuint getItemSize( GLuint type ) {

		switch( type ) {

			case GL_FLOAT:

				return 1;

			break;

			case GL_FLOAT_VEC2:

				return 2;

			break;

			case GL_FLOAT_VEC3:

				return 3;

			break;

			case GL_FLOAT_VEC4:

				return 4;

			break;


			case GL_INT:

				return 1;

			break;
			
			case GL_INT_VEC2:

				return 2;

			break;

			case GL_INT_VEC3:

				return 3;

			break;

			case GL_INT_VEC4:

				return 4;

			break;


			case GL_UNSIGNED_INT:

				return 1;

			break;
			
			case GL_UNSIGNED_INT_VEC2:

				return 2;

			break;

			case GL_UNSIGNED_INT_VEC3:

				return 3;

			break;

			case GL_UNSIGNED_INT_VEC4:

				return 4;

			break;

		}

		return 0;

	}

	GLuint createBuffer( char * attributeName, void * data, GLenum target, GLenum usage ) {

		GLuint itemSize;

		GLuint componentType;

		GLuint componentSize;

		GLuint attributeLocation;

		//GLenum target;

		GLuint isIndexBuffer	= 0;

		GLuint buffer;

		glGenBuffers( 1, &buffer );


		if( attributeName == "index" ) {

			isIndexBuffer 		= 1;

			componentType  		= GL_INT;

			componentSize		= this->getGLTypeSize( componentType );

			target				= GL_ELEMENT_ARRAY_BUFFER;

			glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, buffer );

		} else {

			attribute * attribute			= this->program->getAttributeByName( attributeName );

			if( attribute == NULL ) {

				return 0;

			}

			itemSize						= this->getItemSize( attribute->type );

			componentType 					= this->getComponentType( attribute->type );
			
			componentSize					= this->getGLTypeSize( componentType );

			attributeLocation				= attribute->location;

			target							= GL_ARRAY_BUFFER;

			glEnableVertexAttribArray( attribute->location );

			glBindBuffer( GL_ARRAY_BUFFER, buffer );

		}

		//printf("Name:	%s	itemSize:	%i		componentSize:	%i	", attributeName, itemSize, componentSize);


		int itemCount;

		GLuint bufferSize;

		
		if( componentType == GL_UNSIGNED_INT || componentType == GL_INT ) {

			unsignedIntegerArray * test 	= ( unsignedIntegerArray * ) data;

			itemCount						= test->length();

			bufferSize						= itemCount * componentSize;

			glBufferData( target, bufferSize, test->items, usage );

			if( isIndexBuffer == NULL ) {

				glVertexAttribIPointer( attributeLocation, itemSize, componentType, 0, ( void * ) 0 );

			}

		} else if( componentType == GL_FLOAT ) {

			floatArray * test 	= ( floatArray * ) data;

			itemCount						= test->total;

			bufferSize						= itemCount * componentSize;

			glBufferData( GL_ARRAY_BUFFER, bufferSize, test->items, usage );

			if( isIndexBuffer == NULL ) {

				glVertexAttribPointer( attributeLocation, itemSize, componentType, GL_FALSE, 0, ( void * ) 0 );

			}
		}

		//printf("itemCount:	%i\n\n", bufferSize);
		
		GLint compareSize = 0;

		glGetBufferParameteriv( target, GL_BUFFER_SIZE, &compareSize );

		if( bufferSize != compareSize )
		{

		    glDeleteBuffers(1, &buffer);

		    printf("ERROR:	size error");

		    return 0;

		}

		return buffer;

	}


	void createBuffers( ) {

		struct unsignedIntegerArray * 	indices		= new unsignedIntegerArray();

		struct floatArray * textureCoordinates 		= new floatArray();

		struct floatArray * vertexCoordinates		= new floatArray();

		struct floatArray * normalCoordinates		= new floatArray();

		struct unsignedIntegerArray * 	meshIndices	= new unsignedIntegerArray();

		int subdivisionsDepth 	= 1;

		int subdivisionsWidth	= 1;

		float width				= 2;

		float depth				= 2;


		int meshCount			= 100 * 50;

		int meshStartIndex		= 0;

		for (int meshIndex = 0; meshIndex < meshCount; ++meshIndex)
		{


			meshStartIndex = meshIndices->length();

			//printf("Number of vectex cnumVertsAcrossoordinates:		%i\n\n", vertexCoordinates->length());
			
			int numVertsAcross = subdivisionsWidth + 1;

			for ( int z = 0; z < subdivisionsDepth; z++ ) {

				for ( int x = 0; x < subdivisionsWidth; x++ ) {
					
					// triangle 1 of quad
					indices->add( ( z + 0 ) * numVertsAcross + x + meshStartIndex );

					indices->add( ( z + 1 ) * numVertsAcross + x + meshStartIndex );

					indices->add( ( z + 0 ) * numVertsAcross + x + 1 + meshStartIndex );

					// triangle 2 of quad
					indices->add( ( z + 1 ) * numVertsAcross + x + meshStartIndex );

					indices->add( ( z + 1 ) * numVertsAcross + x + 1 + meshStartIndex );

					indices->add( ( z + 0 ) * numVertsAcross + x + 1 + meshStartIndex );
		
				}

			}

			
			for ( int z = 0; z <= subdivisionsDepth; z++ ) {

				for ( int x = 0; x <= subdivisionsWidth; x++ ) {

				
					float u = ( float ) x;// / subdivisionsWidth;

					float v = ( float ) z;// / subdivisionsDepth;


					textureCoordinates->add( u );

					textureCoordinates->add( ( 1 - v ) );


					vertexCoordinates->add( width * u - width * 0.5 );

					vertexCoordinates->add( depth * v - depth * 0.5 );

					vertexCoordinates->add( 0 );


					normalCoordinates->add( 0 );

					normalCoordinates->add( 0 );

					normalCoordinates->add( 1 );


					meshIndices->add( meshIndex );


				}

			}


		}

		glGenVertexArrays( 1, &this->vertexArrayObject );

		glBindVertexArray( this->vertexArrayObject ); 


			this->vertexbuffer 					= this->createBuffer( "position", vertexCoordinates, GL_ARRAY_BUFFER, GL_STATIC_DRAW );

			this->textureCoordinateBuffer 		= this->createBuffer( "textureCoordinates", textureCoordinates, GL_ARRAY_BUFFER, GL_STATIC_DRAW );

			this->vertexbuffer 					= this->createBuffer( "meshIndex", meshIndices, GL_ARRAY_BUFFER, GL_STATIC_DRAW );

			this->indexBuffer 					= this->createBuffer( "index", indices, GL_ARRAY_BUFFER, GL_STATIC_DRAW );

			//this->subMeshPositions 				= this->createBuffer( "subMeshPositions", indices, GL_SHADER_STORAGE_BUFFER, GL_STATIC_DRAW );

			int blockCount 					= this->blocks->length();

			for (int i = 0; i < blockCount; ++i)
			{
				
				block * currentBlock			= this->blocks->get( i );

				int size						= currentBlock->bufferSize;

				printf("Bind block %s	bufferSize:	%i  bindingPoint: %i bufferType: %s  \n\n", currentBlock->name, currentBlock->bufferSize, currentBlock->bindingPoint, currentBlock->getBufferTypeText( ) );


		
				glBindBuffer( currentBlock->bufferType, currentBlock->buffer );

				glBindBufferBase( currentBlock->bufferType, currentBlock->bindingPoint, currentBlock->buffer );

				glBufferData( currentBlock->bufferType, currentBlock->bufferSize, 0, GL_DYNAMIC_DRAW );

			}


		glBindVertexArray( 0 );




/*

		unsigned int uniformBufferSize	= 152; // allocate 152 bytes of memory

		glGenBuffers( 1, &this->uniformBuffer );

		glBindBuffer( GL_UNIFORM_BUFFER, this->uniformBuffer );

		glBufferData( GL_UNIFORM_BUFFER, uniformBufferSize, NULL, GL_DYNAMIC_DRAW ); 


		



		glBindBuffer( GL_UNIFORM_BUFFER, this->uniformBuffer );

		//glBufferData( this->uniformBuffer, size, data );

		glBufferSubData( this->uniformBuffer, offset, size, data );
*/

		//glBindBufferRange( GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4) );


/*
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0); 


glm::mat4 view = camera.GetViewMatrix();	       
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);  */





		//glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0, indexBufferSize, indices->items );


/*
		int vertexCount = vertexCoordinates->length();

		for (int i = 0; i < vertexCount; ++i)
		{
			
			float currentVertex = vertexCoordinates->get( i );

			printf(" %i:	%f \n", i, currentVertex);

		}

		int indexCount = indices->length();

		printf("\n\n");

		for (int i = 0; i < indexCount; ++i)
		{
			
			int currentIndex = indices->get( i );

			printf(" %i:	%i \n", i, currentIndex);

		}

		printf("number of indices:		%i\n", indices->length());
*/

		this->indices				= indices;

		this->textureCoordinates 	= textureCoordinates;

		this->vertexCoordinates 	= vertexCoordinates;

		this->normalCoordinates 	= normalCoordinates;

	}

	createOrderedTriangleStripQuad() {


/*
		vector3 * a = new vector3(0.0, 0.0, 0.0);

		vector3 * b = new vector3(0.0, 0.5, 0.0);

		vector3 * c = new vector3(0.5, 0.0, 0.0);

		vector3 * d = new vector3(0.5, 0.5, 0.0);

		GLfloat g_vertex_buffer_data[ 12 * 2 ];

		for (int i = 0; i < 2; ++i)
		{
				
			int vectorIndex	= i * 3;

			float index =  0.5 *i;

			g_vertex_buffer_data[ vectorIndex + 0 ] = 0.0 - index;

			g_vertex_buffer_data[ vectorIndex + 1 ] = 0.;

			g_vertex_buffer_data[ vectorIndex + 2 ] = 0.0;


			g_vertex_buffer_data[ vectorIndex + 3 ] = 0.0 - index;

			g_vertex_buffer_data[ vectorIndex + 4 ] = 0.5;

			g_vertex_buffer_data[ vectorIndex + 5 ] = 0.0;


			g_vertex_buffer_data[ vectorIndex + 6 ] = 0.5 - index;

			g_vertex_buffer_data[ vectorIndex + 7 ] = 0.0;

			g_vertex_buffer_data[ vectorIndex + 8 ] = 0.0;


			g_vertex_buffer_data[ vectorIndex + 9 ] = 0.5 - index;

			g_vertex_buffer_data[ vectorIndex + 10 ] = 0.5;

			g_vertex_buffer_data[ vectorIndex + 11 ] = 0.0;


		}


		GLfloat g_vertex_buffer_data[] = {
			0.0, 0.0, 0.0f,
			0.0, 0.5, 0.0f,
			0.5f,  0.0f, 0.0f,
			.5, 0.5, 0.0

		};
*/

	}



}