#include "ShaderManager.h"

#include "Utilities/Log.h"
#include "Utilities/CfgParser.h"

// library includes
#include "physfs.h"

// system includes
#include <iostream>
#include <vector>

using namespace std;

namespace BlueCore
{
//------------------------------------------------------------------------------
ShaderManager::ShaderManager(RenderWindow* renderwindow) :
	_usingShaders(true), _RenderWindow(renderwindow)
{
	glewInit();

	if (GLEW_ARB_fragment_shader == false)
		_usingShaders = false;

	_RenderWindow->WindowCloseSignal.connect(this,
			&ShaderManager::WindowCloseSlot);

	clog << ">>> ShaderManager constructed ..."<< endline;
	if (_usingShaders)
		clog << "    using shaders"<< endline;
	else
		clog << "    shaders not supported!"<< endlog;
}

//------------------------------------------------------------------------------
ShaderManager::~ShaderManager()
{
	clog << ">>> ShaderManager destructed ..."<< endlog;
}

//------------------------------------------------------------------------------
void ShaderManager::shutdown()
{
	clog << ">>> ShaderManager shutting down ..."<< endlog;

	ShaderProgramMap::iterator prog_iter;
	for (prog_iter = shader_programs.begin(); prog_iter!= shader_programs.end(); prog_iter++)
	{
		glDeleteObjectARB(prog_iter->second);
	}
	shader_programs.clear();

	VertexShaderMap::iterator vert_iter;
	for (vert_iter = vertex_shader.begin(); vert_iter != vertex_shader.end(); vert_iter++)
	{
		glDeleteObjectARB(vert_iter->second);
	}
	vertex_shader.clear();

	FragmentShaderMap::iterator frag_iter;
	for (frag_iter = fragment_shader.begin(); frag_iter!= fragment_shader.end(); frag_iter++)
	{
		glDeleteObjectARB(frag_iter->second);
	}
	fragment_shader.clear();
}

//------------------------------------------------------------------------------
void ShaderManager::WindowCloseSlot()
{
	shutdown();
}

//------------------------------------------------------------------------------
bool ShaderManager::usingShaders()
{
	return _usingShaders;
}

//------------------------------------------------------------------------------
void printInfoLog(GLhandleARB obj)
{
	int infologLength = 0;
	int charsWritten = 0;
	char *infoLog;

	glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
			&infologLength);

	if (infologLength > 1)
	{
		infoLog = new char[infologLength+1];

		glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);

		clog << "   "<< infoLog << endline;

		delete [] infoLog;
	}
}

//------------------------------------------------------------------------------
VertexShader ShaderManager::loadVertexShader(const string &name)
{
	if ( !_usingShaders)
		return 0;

	// check if this mesh is already loaded
	VertexShaderMap::const_iterator result;

	result = vertex_shader.find(name);

	if (result != vertex_shader.end() )
	{
		return result->second;
	}

	string filename = name + ".vert";

	PHYSFS_file *file = PHYSFS_openRead(filename.c_str() );

	if ( !file)
	{
		clog << "!!! VertexShader '"<< name << "' not found"<< endline;
		return 0;
	}

	char *buffer = new char[ PHYSFS_fileLength ( file ) ];

	PHYSFS_read(file, buffer, 1, PHYSFS_fileLength(file) );
	buffer[ PHYSFS_fileLength ( file ) - 1] = 0;

	PHYSFS_close(file);

	VertexShader shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);

	glShaderSourceARB(shader, 1, ( const GLcharARB** ) &buffer, NULL);

	delete [] buffer;

	glCompileShaderARB(shader);

	vertex_shader[name] = shader;

	clog << ">>> VertexShader '"<< name << "' loaded"<< endline;

	return shader;
}

//------------------------------------------------------------------------------
FragmentShader ShaderManager::loadFragmentShader(const string &name)
{
	if ( !_usingShaders)
		return 0;

	// check if this mesh is already loaded
	FragmentShaderMap::const_iterator result;

	result = fragment_shader.find(name);

	if (result != fragment_shader.end() )
	{
		return result->second;
	}

	string filename = name + ".frag";

	PHYSFS_file *file = PHYSFS_openRead(filename.c_str() );

	if ( !file)
	{
		clog << "!!! FragmentShader '"<< name << "' not found"<< endline;
		return 0;
	}

	char *buffer = new char[ PHYSFS_fileLength ( file ) ];

	PHYSFS_read(file, buffer, 1, PHYSFS_fileLength(file) );
	buffer[ PHYSFS_fileLength ( file ) - 1] = 0;

	PHYSFS_close(file);

	FragmentShader shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

	glShaderSourceARB(shader, 1, ( const GLcharARB** ) &buffer, NULL);

	delete [] buffer;

	glCompileShaderARB(shader);

	printInfoLog(shader);

	fragment_shader[name] = shader;

	clog << ">>> FragmentShader '"<< name << "' loaded"<< endline;

	return shader;
}

//------------------------------------------------------------------------------
ShaderProgram ShaderManager::loadShaderProgram(const std::string &name)
{
	if ( !_usingShaders)
		return 0;

	if (name.empty() )
		return 0;

	// check if this mesh is already loaded
	ShaderProgramMap::const_iterator result;

	result = shader_programs.find(name);

	if (result != shader_programs.end() )
	{
		return result->second;
	}

	string filename = name + ".prog";

	PHYSFS_file *file = PHYSFS_openRead(filename.c_str() );

	if ( !file)
	{
		clog << "!!! ShaderProgramm '"<< name << "' not found"<< endline;
		return 0;
	}

	unsigned int length = PHYSFS_fileLength(file);
	char *buffer = new char[length];

	PHYSFS_read(file, buffer, 1, length);
	PHYSFS_close(file);

	CfgParser cfg;
	cfg.parse(buffer, length);

	delete [] buffer;

	ShaderProgram program = glCreateProgramObjectARB();

	std::vector<std::string> vertex_shaders;
	if (cfg.getStrings("vertex_shaders", vertex_shaders) )
	{
		for (unsigned int i = 0; i < vertex_shaders.size(); i++)
		{
			VertexShader shader = loadVertexShader(vertex_shaders[i]);

			if (shader != 0)
				glAttachObjectARB(program, shader);
		}
	}

	std::vector<std::string> fragment_shaders;
	if (cfg.getStrings("fragment_shaders", fragment_shaders) )
	{
		for (unsigned int i = 0; i < fragment_shaders.size(); i++)
		{
			FragmentShader shader = loadFragmentShader(fragment_shaders[i]);

			if (shader != 0)
				glAttachObjectARB(program, shader);
		}
	}

	glLinkProgramARB(program);

	printInfoLog(program);

	shader_programs[name] = program;

	clog << ">>> ShaderProgramm '"<< name << "' loaded"<< endline;

	return program;

}

//------------------------------------------------------------------------------
void ShaderManager::useShaderProgram(ShaderProgram shader_program)
{
	if ( !_usingShaders)
		return;

	glUseProgramObjectARB(shader_program);
}

//------------------------------------------------------------------------------
void ShaderManager::useTexture(ShaderProgram shader_program,
		unsigned int texture, const std::string &name)
{
	if ( !_usingShaders)
		return;

	GLint location = glGetUniformLocationARB(shader_program, name.c_str() );

	//if( location > 0 )
	glUniform1iARB(location, texture);
}
/*
 //------------------------------------------------------------------------------
 void ShaderManager::useTangentBuffer (
 ShaderProgram shader_program,
 TangentBuffer *tangent_buffer,
 const std::string &name )
 {
 if ( !_usingShaders || !tangent_buffer )
 return;

 GLuint location = glGetAttribLocationARB ( shader_program, name.c_str() );

 glEnableVertexAttribArrayARB ( location );

 tangent_buffer->bind();

 glVertexAttribPointerARB ( location, 3, GL_FLOAT, 0, 0, 0 );
 }

 //------------------------------------------------------------------------------
 void ShaderManager::disableTangentBuffer (
 ShaderProgram shader_program,
 const std::string &name )
 {
 if ( !_usingShaders )
 return;

 GLuint location = glGetAttribLocationARB ( shader_program, name.c_str() );

 glDisableVertexAttribArrayARB ( location );
 }
 */
}
