
#include "block.h"

#include "member.h"

#include "uniform.h"


#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 "shader.h"

class program{


	GLuint samplerIndex	 		= 0;

	array * uniforms			= new array();

	array * attributes			= new array();

	array * blocks				= new array();

	array * shaders 			= new array();

	GLuint glProgram;



	addShader( struct shader * shaderInstance ) {

		this->shaders->add( shaderInstance );

	}


	GLint glGetProgramResourceiv( GLint programProperty, GLint index, GLint Property ) {

		GLint offsetValues;

		glGetProgramResourceiv( this->glProgram, programProperty, index, 1, &Property , 1, 0, &offsetValues );

		return offsetValues;

	}

	GLchar * glGetProgramResourceName( GLint programProperty, GLint index, GLint nameLength ) {


		GLchar name[ GL_NAME_LENGTH + 1 ];

		glGetProgramResourceName( 
			this->glProgram, 

			programProperty, 
			index, 
			GL_NAME_LENGTH + 1, 
			0, 
			name );


		return name;

	}


	void extractBlocks() {

		GLint	programInterfaces[2]		= { GL_SHADER_STORAGE_BLOCK, GL_UNIFORM_BLOCK }; // GL_UNIFORM_BLOCK

		GLint	programProperties[2]		= { GL_BUFFER_VARIABLE, GL_UNIFORM }; // GL_UNIFORM  , GL_PROGRAM_INPUT 

		GLenum	programBufferTypes[2]		= { GL_SHADER_STORAGE_BUFFER, GL_UNIFORM_BUFFER };



		for (int blockTypeIndex = 0; blockTypeIndex < 2; ++blockTypeIndex)
		{


			GLint program				= this->glProgram;

			GLint numActiveResources;

			GLint	programInterface	= programInterfaces[blockTypeIndex]; // GL_UNIFORM_BLOCK

			GLint	programProperty		= programProperties[blockTypeIndex]; // GL_UNIFORM

			GLenum  programBufferType	= programBufferTypes[blockTypeIndex];

			glGetProgramInterfaceiv( program, programInterface, GL_ACTIVE_RESOURCES, &numActiveResources );

			//printf("extractShaderStorageBuffers %i\n\n", numActiveResources);

			for ( GLuint blockIndex = 0; blockIndex < numActiveResources; blockIndex++ ) {

				struct block * blockInstance	= new block();

				//GLint blockIndex				= this->glGetProgramResourceiv( programInterface, blockIndex, GL_BLOCK_INDEX );

				GLint blockNameLength			= this->glGetProgramResourceiv( programInterface, blockIndex, GL_NAME_LENGTH );

				blockInstance->bufferSize		= this->glGetProgramResourceiv( programInterface, blockIndex, GL_BUFFER_DATA_SIZE );

				GLint numberActiveVariables		= this->glGetProgramResourceiv( programInterface, blockIndex, GL_NUM_ACTIVE_VARIABLES );

				blockInstance->bindingPoint		= this->glGetProgramResourceiv( programInterface, blockIndex, GL_BUFFER_BINDING );

				//const GLchar * name				= this->glGetProgramResourceName( programInterface, blockIndex, blockNameLength );
				GLchar name[ GL_NAME_LENGTH + 1 ];

				glGetProgramResourceName( this->glProgram, programInterface, blockIndex, GL_NAME_LENGTH + 1, NULL, name );

				blockInstance->name = malloc( GL_NAME_LENGTH );

				strcpy( blockInstance->name, name );
			

				blockInstance->bufferType		= programBufferType;
	
				printf("block:%s		\n\n", blockInstance->name );

				printf("	block name		%s\n", blockInstance->name);

				printf("	block buffer size:	%i\n", blockInstance->bufferSize);

				printf("	block binding point:	%i\n\n", blockInstance->bindingPoint);

			


				GLint indices[ numberActiveVariables ];

				GLenum member 				= GL_ACTIVE_VARIABLES;

				glGetProgramResourceiv( program, programInterface, blockIndex, 1, &member, numberActiveVariables, 0, indices );

				printf("	number of variables:	%i\n\n\n", numberActiveVariables);

				// add block


				for ( GLuint index = 0; index < numberActiveVariables; index++ )
				{

					struct member * memberInstance	= new member();


					GLint itemIndex					= indices[ index ];

					memberInstance->index			= itemIndex;

					GLint nameLength				= this->glGetProgramResourceiv( programProperty, itemIndex, GL_NAME_LENGTH );

					memberInstance->offset			= this->glGetProgramResourceiv( programProperty, itemIndex, GL_OFFSET );

					memberInstance->type			= this->glGetProgramResourceiv( programProperty, itemIndex, GL_TYPE );

					memberInstance->arrayStride		= this->glGetProgramResourceiv( programProperty, itemIndex, GL_ARRAY_STRIDE );
					

					memberInstance->size			= this->glGetProgramResourceiv( programProperty, itemIndex, GL_ARRAY_SIZE );

					int topLevelSize				= this->glGetProgramResourceiv( programProperty, itemIndex, GL_TOP_LEVEL_ARRAY_SIZE );
					
					int topLevelStride				= this->glGetProgramResourceiv( programProperty, itemIndex, GL_TOP_LEVEL_ARRAY_STRIDE );


					if( memberInstance->arrayStride == 0 ) {

						memberInstance->arrayStride = topLevelStride;

					}

					memberInstance->topLevelSize = topLevelSize;


	//				memberInstance->name	= this->glGetProgramResourceName( programProperty, itemIndex, nameLength );


					GLchar memberName[ GL_NAME_LENGTH + 1 ];

					glGetProgramResourceName( this->glProgram, programProperty, itemIndex, GL_NAME_LENGTH + 1, NULL, memberName );

					memberInstance->name = malloc( GL_NAME_LENGTH );

					strcpy( memberInstance->name, memberName );



					printf("		offset:	#%i  name: %s vec2: %i offset: %i		itemSize / arrayStride:	%i		Array size: %i toplevel size:	%i top level size: %i \n\n", 

								memberInstance->index, 
								memberInstance->name, 
								memberInstance->type == GL_FLOAT_VEC2, 
								memberInstance->offset, 
								memberInstance->arrayStride, 
								memberInstance->size, topLevelSize, 
								topLevelStride );



					blockInstance->add( memberInstance );

				}

				blockInstance->createBuffer();

				this->blocks->add( blockInstance );

			}

		}


	}

	struct block * createNewBlock( char * blockName ) {

		printf("Copy buffer:	%s\n\n", blockName);

		block * originalBlock  		= this->getBlock( blockName );

		block * blockCopy 			= new block();

		blockCopy->members			= originalBlock->members;

		blockCopy->bindingPoint		= originalBlock->bindingPoint;

		blockCopy->index			= originalBlock->index;

		blockCopy->bufferSize		= originalBlock->bufferSize;

		blockCopy->createBuffer();

		return blockCopy;

	}

	struct block * getBlock( char * blockName ) {

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

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

			char * currentBlockName		= currentBlock->name;

			if( currentBlockName == blockName ) {

				return currentBlock;

			}

		}

		return NULL;

	}

	create() { 

		this->glProgram		= glCreateProgram();


		array * shaders 	= this->shaders;

		int shaderCount		= shaders->length();


		for (int i = 0; i < shaderCount; ++i)
		{
			
			shader * currentShader = shaders->get( i );

			glAttachShader( this->glProgram, currentShader->glShader );

		}

		glLinkProgram( this->glProgram );

		glUseProgram( this->glProgram );


		this->extractBlocks();

		this->extractAttributes();

		this->extractUniforms();

	}

	bindBlock( char * blockName ) {

		block * currentBlock				= this->getBlock( blockName );

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

	}

	use() {

		glUseProgram( this->glProgram );

	}


	extractUniforms() {

		int attributeCount = 0;

		GLsizei bufSize = 64;

		GLsizei length; // name length

		GLint size; 

		GLenum type; // type of the variable (float, vec3 or mat4, etc)

		int uniformCount = 0;

		glGetProgramiv( this->glProgram, GL_ACTIVE_UNIFORMS, &uniformCount );

		for (int i = 0; i < uniformCount; i++)
		{

			struct uniform * uniformInstance 			= new uniform();

			GLenum type;

			GLchar name[bufSize];

			glGetActiveUniform( this->glProgram, ( GLuint ) i, bufSize, &length, &size, &type, uniformInstance->name );

			//printf( "	Uniform #%d Type: %u Name: %s\n", i, type, uniformInstance->name );

			GLint uniformLocation = glGetUniformLocation( this->glProgram, uniformInstance->name );

			uniformInstance->location 			= uniformLocation;

			uniformInstance->type				= type;

			this->uniforms->add( uniformInstance );
			
		}

	}

	extractAttributes() {

		int attributeCount = 0;

		GLsizei bufSize = 64;

		GLsizei length; // name length

		GLint size; 

		GLenum type; // type of the variable (float, vec3 or mat4, etc)

		glGetProgramiv( this->glProgram, GL_ACTIVE_ATTRIBUTES, &attributeCount );

		for (int i = 0; i < attributeCount; i++)
		{
			GLenum type;

			attribute * attributeInstance 		= new attribute();

			glGetActiveAttrib( this->glProgram, ( GLuint ) i, bufSize, &length, &size, &type, attributeInstance->name);

			//printf("	Attribute #%d Type: %u Name: %s size: %i\n", i, type, attributeInstance->name, size);

			GLint attributeLocation = glGetAttribLocation( this->glProgram, attributeInstance->name );

			glEnableVertexAttribArray( attributeLocation );


			attributeInstance->location 			= attributeLocation;

			//attributeInstance.name 				= name;

			attributeInstance->type				= type;

			this->attributes->add( attributeInstance );


		}

	}

	struct attribute * getAttributeByName( char * attributeName ) {

		int attributeCount 	= this->attributes->length();

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

		for ( int i = 0; i < attributeCount; ++i )
		{
			struct attribute * currentAttribute = this->attributes->get( i );

			char * currentAttributeName	= currentAttribute->name;

			//printf("attributeName->name:	%s  %s\n\n", currentAttributeName, attributeName );

			if( currentAttributeName == attributeName ) {

				return currentAttribute;

			}

		}

		return NULL;

	}

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

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

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

		for (int i = 0; i < uniformCount; ++i)
		{
			uniform * currentUniform = this->uniforms->get( i );

			char * uniformName	= (char *)currentUniform->name;

		//	printf("currentUniform->name:	%s\n\n", uniformName);

			if( uniformName == name ) {

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

				switch( currentUniform->type ) {


					case GL_FLOAT_VEC2:

						vector2 * vector2Value	= ( vector2 * ) value;

						//printf("\n\n this is an float vec2 %f  %f   \n\n", vector2Value->x, vector2Value->y );

						glUniform2f( currentUniform->location, vector2Value->x, vector2Value->y );

					break;

					case GL_FLOAT_VEC3:

						vector3 * vector3Value	= ( vector3 * ) value;

						//printf("\n\n this is an float vec3 %f  %f  %f  \n\n", vector3Value->x, vector3Value->y, vector3Value->z);

						glUniform3f( currentUniform->location, vector3Value->x, vector3Value->y, vector3Value->z );

					break;

					case GL_SAMPLER_2D:

						this->updateSampler2D( currentUniform, value );

					break;


					case GL_SAMPLER_2D_ARRAY:

						this->updateSampler2D( currentUniform, value );

					break;




				}

			}


		}


	}

	updateSampler2D( uniform * currentUniform, void * value ) {

		glEnable( GL_BLEND );

		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); 

		sampler2D * sampler = ( sampler2D * ) value; 


		if( !sampler->binded ) {

			sampler->index = this->samplerIndex++;

			sampler->bind();

			//printf("sampler->index:	%i \n", sampler->index);

		}

		

		glActiveTexture( GL_TEXTURE0 + sampler->index );

		glBindTexture( sampler->target, sampler->glTexture );

		//glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,GL_UNSIGNED_BYTE, texture );

		glUniform1i( currentUniform->location, sampler->index );


	}


}
