#include "OglShader.h"

#define UNF_MAT4 0
#define UNF_VEC3 1
#define UNF_FLOAT 2
#define UNF_TEXTURE 3
#define UNF_TEXTURE2DARRAY 6
#define UNF_VEC2 4
#define UNF_INT 5
#define UNF_UINT 7
#define UNF_TEXTUREIMAGE 8

OglShaderProg::OglShaderProg()
{
	textures = 0;
}

void OglShader::compile_( const char * file_path, GLenum type )
{
	ShaderID = glCreateShader( type );
	string path = file_path;

	std::string ShaderCode;
	std::ifstream ShaderStream( file_path, std::ios::in );
	if ( ShaderStream.is_open( ) )
	{
		std::string Line = "";
		while ( getline( ShaderStream, Line ) )
		{
			ShaderCode += "\n" + Line;
			if ( Line.substr( 0, 7 ) == "uniform" )
			{
				int startpos = Line.find_last_of( " " );
				int prestartpos = Line.substr( 0, startpos - 1 ).find_last_of( " " );
				string name = Line.substr( startpos + 1, Line.length( ) - startpos - 2 );
				string type = Line.substr( prestartpos + 1, startpos - prestartpos - 1 );

				char typec = 0;
				if ( type == "mat4" )
					typec = UNF_MAT4;
				else if ( type == "vec3" )
					typec = UNF_VEC3;
				else if ( type == "vec2" )
					typec = UNF_VEC2;
				else if ( type == "float" )
					typec = UNF_FLOAT;
				else if ( type == "int")
					typec = UNF_INT;
				else if (type == "uint")
					typec = UNF_UINT;
				else if ( type == "sampler2D" )
					typec = UNF_TEXTURE;
				else if (type == "usampler2D")
					typec = UNF_TEXTURE;
				else if (type == "sampler2DArray")
					typec = UNF_TEXTURE2DARRAY;
				else if ( type == "sampler2DShadow" )
					typec = UNF_TEXTURE;

				UniformNameType uniform( name, typec );
				uniformtypes.push_back( uniform );
			}
		}
		ShaderStream.close( );
	}
	else {
		//cout << "stream " << file_path << " not opened!" << endl;
		LOGERROR("stream " + path + "not opened!");
	}

	printf( "Compiling shader : %s\n", file_path );
	char const * SourcePointer = ShaderCode.c_str( );
	GLint len = ShaderCode.length();
	glShaderSource( ShaderID, 1, &SourcePointer, &len);
	glCompileShader( ShaderID );

	GLint Result = GL_FALSE;
	int InfoLogLength;

	glGetShaderiv( ShaderID, GL_COMPILE_STATUS, &Result );
	glGetShaderiv( ShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength );
	std::vector<char> ShaderErrorMessage( InfoLogLength );
	if (Result == GL_FALSE) {
		glGetShaderInfoLog(ShaderID, InfoLogLength, NULL, &ShaderErrorMessage[0]);
		string strMess = &ShaderErrorMessage[0];
		LOGERROR(path + ": " + strMess);
	}
}
OglShader::OglShader()
{
	del = true;
}
OglShader::~OglShader( )
{
	if (del)
		glDeleteShader( ShaderID );
}
void OglShader::notDelete()
{
	del = false;
}

void OglVertexShader::compile( const char * file_path )
{
	compile_( file_path, GL_VERTEX_SHADER );
}
void OglFragmentShader::compile( const char * file_path )
{
	compile_( file_path, GL_FRAGMENT_SHADER );
}
void OglGeometryShader::compile(const char * file_path)
{
	compile_(file_path, GL_GEOMETRY_SHADER);
}
void OglComputeShader::compile(const char * file_path)
{
	compile_(file_path, GL_COMPUTE_SHADER);
}
void OglShaderProg::processUniform( UniformNameType unf )
{
	map<int, int> m;


	GLuint id = glGetUniformLocation( ProgramID, unf.name.c_str( ) );
	UniformTypeID uniform( unf.type, id );
	uniforms[unf.name] = uniform;
}
void OglShaderProg::link( OglShader& vsh, OglShader& fsh )
{
	//uniforms._Mysize = 0;

	GLint VertexShaderID = vsh.ShaderID;
	GLint FragmentShaderID = fsh.ShaderID;
	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Link the program
	//fprintf( stdout, "Linking program\n" );
	ProgramID = glCreateProgram( );
	glAttachShader( ProgramID, VertexShaderID );
	glAttachShader( ProgramID, FragmentShaderID );
	glLinkProgram( ProgramID );

	// Check the program
	glGetProgramiv( ProgramID, GL_LINK_STATUS, &Result );
	glGetProgramiv( ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength );
	std::vector<char> ProgramErrorMessage( std::max( InfoLogLength, int( 1 ) ) );
	if (Result == GL_FALSE) {
		glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		string errStr = &ProgramErrorMessage[0];
		LOGERROR(errStr);
	}

	// Know about uniforms
	for ( uint i = 0; i < vsh.uniformtypes.size( ); i++ )
		processUniform( vsh.uniformtypes[i] );
	for ( int i = 0; i < fsh.uniformtypes.size( ); i++ )
		processUniform( fsh.uniformtypes[i] );
}
void OglShaderProg::linkG(OglShader& vsh, OglShader& gsh, OglShader& fsh)
{
	//uniforms._Mysize = 0;

	GLint VertexShaderID = vsh.ShaderID;
	GLint FragmentShaderID = fsh.ShaderID;
	GLint GeometryShaderID = gsh.ShaderID;
	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Link the program
	//LOG("Linking program");
	ProgramID = glCreateProgram();
	glAttachShader(ProgramID, VertexShaderID);
	glAttachShader(ProgramID, FragmentShaderID);
	glAttachShader(ProgramID, GeometryShaderID);
	GLCHECKERROR("LINK");

	glLinkProgram(ProgramID);

	// Check the program
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> ProgramErrorMessage(std::max(InfoLogLength, int(1)));
	if (Result == GL_FALSE) {
		glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		string errStr = &ProgramErrorMessage[0];
		LOGERROR(errStr);
	}

	// Know about uniforms
	for (uint i = 0; i < vsh.uniformtypes.size(); i++)
		processUniform(vsh.uniformtypes[i]);
	for (int i = 0; i < fsh.uniformtypes.size(); i++)
		processUniform(fsh.uniformtypes[i]);
}
void OglShaderProg::linkC(OglShader& csh)
{
	//uniforms._Mysize = 0;

	GLint ComputeShaderID = csh.ShaderID;
	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Link the program
	//fprintf(stdout, "Linking program\n");
	ProgramID = glCreateProgram();
	glAttachShader(ProgramID, ComputeShaderID);
	glLinkProgram(ProgramID);

	// Check the program
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> ProgramErrorMessage(std::max(InfoLogLength, int(1)));
	if (Result == GL_FALSE) {
		glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		string errStr = &ProgramErrorMessage[0];
		LOGERROR(errStr);
	}

	// Know about uniforms
	for (uint i = 0; i < csh.uniformtypes.size(); i++)
		processUniform(csh.uniformtypes[i]);
}
void OglShaderProg::enable( )
{
	glUseProgram( ProgramID );
	textures = 0;
}
void OglShaderProg::setColor( float r, float g, float b )
{
}

void OglShaderProg::regTexture( OglTex *tex )
{
	textureMap[tex] = textures;
	glActiveTexture( GL_TEXTURE0+textures );
	GLenum err;
	err = glGetError();
	glBindTexture( GL_TEXTURE_2D, tex->getId() );
	err = glGetError();
	textures++;
	if (err)
		int u = 0;
}
void OglShaderProg::regTextureImage(OglTexImage *tex)
{
	textureMap[tex] = textures;
	glActiveTexture(GL_TEXTURE0 + textures);
	GLenum err;
	err = glGetError();
	glBindImageTexture(GL_TEXTURE_2D, tex->getId(),0,false,0, GL_WRITE_ONLY, GL_RGBA16F);
	err = glGetError();
	textures++;
}
void OglShaderProg::regTexture2DArray(OglTex *tex)
{
	textureMap[tex] = textures;
	glActiveTexture(GL_TEXTURE0 + textures);
	glBindTexture(GL_TEXTURE_2D_ARRAY, tex->getId());
	textures++;
}

void OglShaderProg::importValue( string name, float data )
{
	UniformTypeID uniform = uniforms[name];
	if (uniform.type == UNF_FLOAT)
		glUniform1f(uniform.id, data);
	else
		//cout << name << ": type float mismatch" << endl;
		LOGERROR(name + ": type float mismatch");
}

void OglShaderProg::importValue( string name, int data )
{
	UniformTypeID uniform = uniforms[name];
	if ( uniform.type == UNF_INT )
		glUniform1i( uniform.id, data );
	else
		//cout << name << ": type int mismatch" << endl;
		LOGERROR(name + ": type int mismatch");
}

void OglShaderProg::importValue(string name, unsigned int data)
{
	UniformTypeID uniform = uniforms[name];
	if (uniform.type == UNF_UINT)
		glUniform1ui(uniform.id, data);
	else
		//cout << name << ": type int mismatch" << endl;
		LOGERROR(name + ": type int mismatch");
}

void OglShaderProg::importValue( string name, Matrix data )
{
	UniformTypeID uniform = uniforms[name];
	if ( uniform.type == UNF_MAT4 )
		glUniformMatrix4fv( uniform.id, 1, GL_FALSE, (float*)&data );
	else
		//cout << name << ": type matrix mismatch" << endl;
		LOGERROR(name + ": type matrix mismatch");
}
void OglShaderProg::importValue( string name, Vector3 data )
{
	UniformTypeID uniform = uniforms[name];
	if ( uniform.type == UNF_VEC3 )
		glUniform3f( uniform.id, data.x, data.y, data.z );
	else
		//cout << name << ": type vec3 mismatch" << endl;
		LOGERROR(name + ": type vec3 mismatch");
}
void OglShaderProg::importValue( string name, Vector2 data )
{
	UniformTypeID uniform = uniforms[name];
	if ( uniform.type == UNF_VEC2 )
		glUniform2f( uniform.id, data.x, data.y);
	else
		//cout << name << ": type vec2 mismatch" << endl;
		LOGERROR(name + ": type vec2 mismatch");
}
void OglShaderProg::importValue( string name, OglTex *data )
{
	regTexture( data );

	UniformTypeID uniform = uniforms[name];
	if ( uniform.type == UNF_TEXTURE )
		glUniform1i( uniform.id, textures-1 );
	else
		//cout << name << ": type texture mismatch" << endl;
		LOGERROR(name + ": type texture mismatch");
}
void OglShaderProg::importValueTex2DArray(string name, OglTex *data)
{
	regTexture2DArray(data);

	UniformTypeID uniform = uniforms[name];
	if (uniform.type == UNF_TEXTURE2DARRAY)
		glUniform1i(uniform.id, textures - 1);
	else
		//cout << name << ": type texture2D mismatch" << endl;
		LOGERROR(name + ": type texture2D mismatch");
}