
#define GL_GLEXT_PROTOTYPES

#include <GL/glext.h>

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

#include <GL/glx.h>

#include <stdio.h>

#include "fileSystem.h"

#include "array.h"

#include "char.h"

#include "sampler2D.h"

#include "member.h"

class block{

	array * members		= new array();

	GLint buffer;

	GLuint index;

	GLenum bufferType;

	GLint bufferSize;

	GLint bindingPoint;

	GLchar * name;

	float * data;

	GLint autoUpload	= 0;



	add( struct member * memberInstance ) {

		this->members->add( memberInstance );

	}

	enableAutoUpload() {

		this->autoUpload	= 1;

	}


	char * getBufferTypeText(  ) {

		switch( this->bufferType ) {

			case GL_SHADER_STORAGE_BUFFER:

				return "GL_SHADER_STORAGE_BUFFER";

			break;

			case GL_UNIFORM_BUFFER:

				return "GL_UNIFORM_BUFFER";

			break;
		}

		return "buffer message not found";

	}

	createBuffer() {

		unsigned int blockBufferSize	= this->bufferSize; // allocate 152 bytes of memory

		//printf("	create array for block %s os size %i  \n", this->name, this->bufferSize );

		this->data	= ( float * ) malloc( blockBufferSize );

		glGenBuffers( 1, &this->buffer );

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

		glBindBufferBase( this->bufferType, this->index, this->buffer );

		glBufferData( this->bufferType, blockBufferSize, 0, GL_DYNAMIC_DRAW );

	}

	void upload() {

		//printf("upload uniform buffer:	%f 	%f  %f 	%f\n", this->data[0], this->data[1], this->data[2], this->data[3]);

		//printf("upload uniform buffer:	%i\n", this->index);

		//glBindBuffer( this->bufferType, this->buffer );

		//glBindBufferBase( this->bufferType, this->bindingPoint, this->buffer );

		//glBufferData( this->bufferType, this->bufferSize, this->data, GL_DYNAMIC_DRAW );


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

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

		glBufferSubData( this->bufferType, 0, this->bufferSize, this->data );

	}

	mapBufferError( void * pointer ) {

		if ( pointer == NULL ){

			GLenum errorCode = glGetError();

			switch( errorCode ) {

				case GL_INVALID_ENUM:

					printf("GL_INVALID_ENUM\n");

				break;

				case GL_INVALID_OPERATION:

					printf("GL_INVALID_OPERATION\n");

				break;

				case GL_INVALID_VALUE:

					printf("GL_INVALID_VALUE\n");

				break;

			}


			printf("null pointer on buffer:		%i\n", errorCode);

			return;

		}

	}

	void * getMemberArray( char * name ) {

		glMemoryBarrier( GL_SHADER_STORAGE_BARRIER_BIT );

		vector<vector2> * output	= new vector();


		int uniformCount 	= this->members->length();

		for (int i = 0; i < uniformCount; ++i)
		{
			member * currentMember = this->members->get( i );

			char * memberName	= ( char * ) currentMember->name;

			if( memberName == name ) {

				int size	= currentMember->size * 8;

				int offset	= currentMember->offset / 4;

				output->items 	= glMapBufferRange( GL_SHADER_STORAGE_BUFFER, 0, size, GL_MAP_WRITE_BIT ); 

				output->total	= currentMember->size;
				
				this->mapBufferError( output->items );


			}

		}

		glUnmapBuffer( GL_SHADER_STORAGE_BUFFER );


		return output;

	}

	void setMemberArrayRow( char * name, int arrayIndex, float * data ) {

		int memberCount 	= this->members->length();

		for (int i = 0; i < memberCount; ++i)
		{
			member * currentMember = this->members->get( i );

			char * memberName	= ( char * ) currentMember->name;

			if( memberName == name ) {

				int size		= currentMember->size * 8;

				int arrayStride = currentMember->arrayStride;

				int offset		= arrayStride * arrayIndex;

				//printf("set array stride:		%i 	%i\n", arrayIndex, arrayStride);

				//int offset	= currentMember->offset / 4;


				if( this->autoUpload ) {



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

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

					glBufferSubData( this->bufferType, offset, arrayStride, data );

				}


				memcpy( this->data + offset, data, arrayStride );

				

			}

		}

	}

	void setMemberArray( char * name, float * data ) {

		int memberCount 	= this->members->length();

		for (int i = 0; i < memberCount; ++i)
		{
			member * currentMember = this->members->get( i );

			char * memberName	= ( char * ) currentMember->name;

			if( memberName == name ) {

				int size	= currentMember->size * 8;

				int offset	= currentMember->offset / 4;

				memcpy( this->data + offset, data, size );

			}

		}

	}

	void setMemberItem( char * name, int index, void * value ) {


		int uniformCount 	= this->members->length();

		//printf("uniformCount:		%i\n\n", uniformCount);

		for (int i = 0; i < uniformCount; ++i)
		{
			member * currentMember = this->members->get( i );

			char * memberName	= ( char * ) currentMember->name;

			if( memberName == name ) {

			//printf("\n\n Update this uniform  from uniform block %s\n\n", name);
						int stride;	

						int strideNormalized;

						int size;

						int offset;


				switch( currentMember->type ) {

					case GL_FLOAT_VEC2:

						stride 				= currentMember->arrayStride;

						strideNormalized	=  stride  / sizeof( float ) ;

						//printf("get size of item:		%i\n",strideNormalized );

					

						vector2 * vector2Value	= ( vector2 * ) value;

						size				= 8;

						offset				= currentMember->offset;



						if( this->autoUpload ) {

							GLint arrayIndex				= ( strideNormalized * index * 2 * 8 );

							float data[2]					= { vector2Value->x, vector2Value->y };

							printf("%i:	(size:%i)	%f %f\n", arrayIndex, data[0], data[1]);


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

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

							glBufferSubData( this->bufferType, arrayIndex, size, data );



						} else {

							GLint dataOffset				= offset / sizeof( float );

							GLint arrayIndex				= ( strideNormalized * index );

							// need to fix this
							if( currentMember->topLevelSize == 1 ) {

								//arrayIndex = 0;

							}


							//printf("	%i:	%f \n", dataOffset + 1 + arrayIndex , vector2Value->y );

							this->data[ dataOffset + arrayIndex ] 		= vector2Value->x;

							this->data[ dataOffset + 1 + arrayIndex ] 	= vector2Value->y;

						}

						

					break;

					case GL_FLOAT_VEC3:
		
						stride 				= currentMember->arrayStride;

						strideNormalized	= stride  / sizeof( float ) ;


						vector3 * vector3Value	= ( vector3 * ) value;

						size				= 12;

						offset			= currentMember->offset;



						if( this->autoUpload ) {

							float data[3]	= { vector3Value->x, vector3Value->y, vector3Value->z };

						//	printf("%f %f\n", data[0], data[1]);


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

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

							glBufferSubData( this->bufferType, offset, size, data );



						} else {

							GLint dataOffset				= offset / sizeof( float );

							GLint arrayIndex				= ( strideNormalized * index );

							this->data[ dataOffset + arrayIndex ] 		= vector3Value->x;

							this->data[ dataOffset + 1 + arrayIndex ] 	= vector3Value->y;

							this->data[ dataOffset + 2 + arrayIndex ] 	= vector3Value->z;

						}

					break;

					case GL_SAMPLER_2D:

	
					break;

				}

			}

		}

	}

	void setData( float * data ) {

		this->data = data;

	}

	void setMember( char * name, void * value ) {

		int uniformCount 	= this->members->length();

		//printf("uniformCount:		%i\n\n", uniformCount);

		for (int i = 0; i < uniformCount; ++i)
		{
			member * currentMember = this->members->get( i );

			char * memberName	= ( char * ) currentMember->name;

			if( memberName == name ) {

			//printf("\n\n Update this uniform  from uniform block %s\n\n", name);

				GLuint size = 8;

				GLint offset		= currentMember->offset;

				switch( currentMember->type ) {

					case GL_INT:

						int intValue	= *( int * ) value;

						printf("set int value:	%i offset:	%i\n", intValue, offset);

						

						if( this->autoUpload ) {

							float data[1]	= { value };

						//	printf("%f %f\n", data[0], data[1]);


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

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

							glBufferSubData( this->bufferType, offset, size, data );



						} else {

							GLint dataOffset				= offset / sizeof( float );

							this->data[ dataOffset ] 		= intValue;

							//this->data[ dataOffset + 1 ] 	= vector2Value->y;

						}

					break;

					case GL_FLOAT:

						float floatValue	= *( float * ) value;

						printf("set int value:	%f offset:	%i\n", floatValue, offset);

						

						if( this->autoUpload ) {

							float data[1]	= { value };

						//	printf("%f %f\n", data[0], data[1]);


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

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

							glBufferSubData( this->bufferType, offset, size, data );



						} else {

							GLint dataOffset				= offset / sizeof( float );

							this->data[ dataOffset ] 		= floatValue;

							//this->data[ dataOffset + 1 ] 	= vector2Value->y;

						}

					break;

					case GL_FLOAT_VEC2:

						vector2 * vector2Value	= ( vector2 * ) value;

						if( this->autoUpload ) {

							float data[2]	= { vector2Value->x, vector2Value->y };


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

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

							glBufferSubData( this->bufferType, offset, size, data );



						} else {

							GLint dataOffset				= offset / sizeof( float );

							this->data[ dataOffset ] 		= vector2Value->x;

							this->data[ dataOffset + 1 ] 	= vector2Value->y;

						}

					break;

					case GL_FLOAT_VEC3:

						vector3 * vector3Value	= ( vector3 * ) value;

						

					break;

					case GL_SAMPLER_2D:

	
					break;

				}

			}


		}


	}

}
