#include "Shader.hpp"
#include "Exception.hpp"

using namespace std;
using namespace std;

Shader::Shader():
m_ProgramObject(0)
{
}

Shader::Shader(const Shader& ref):
m_ProgramObject(ref.m_ProgramObject),
m_VertexShader(ref.m_VertexShader),
m_FragmentShader(ref.m_FragmentShader)
{
};

Shader::~Shader()
{
	//TODO delete the shader somewhen, but not here, because this makes trouble when the shader is copied!
	//glDeleteProgram(m_ProgramObject);
}

bool Shader::Load(string VertexFile, string FragmentFile)
{
	m_VertexShader.Load(VertexFile);
	m_FragmentShader.Load(FragmentFile);

	return ProcessShader();
}

bool Shader::Create(string VertexSource, string FragmentSource)
{
	m_VertexShader.Create(VertexSource);
	m_FragmentShader.Create(FragmentSource);

	return ProcessShader();
}

void Shader::Set() const
{
	glUseProgram(m_ProgramObject);

	//NOTE Maybe we dont need this
	GLenum Error=glGetError();
	if(GL_NO_ERROR!=Error)
	{
		//MRL_THROW(Mrl::Exception() << GlErrorInfo(Error) << DescriptionInfo("Shader could'nt be set!"));
	}
}

void Shader::Unset() const
{
	glUseProgram(0);
}



void Shader::SetUniform(std::string Name, float Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform1f(loc, Value);
}

void Shader::SetUniform(std::string Name, int Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform1i(loc, Value);
}

void Shader::SetUniform(std::string Name, GLuint Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform1i(loc, Value);
}

void Shader::SetUniform(std::string Name, glm::vec2 Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform2fv(loc, 1, &(Value.x));
}

void Shader::SetUniform(std::string Name, glm::uvec2 Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform2ui(loc, Value.x, Value.y);
}

void Shader::SetUniform(std::string Name, glm::vec3 Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform3fv(loc, 1, &(Value.x));
}

void Shader::SetUniform(std::string Name, Vector3f Value)
{
	GLint loc=glGetUniformLocation(m_ProgramObject, Name.c_str());
	if(-1==loc)
		THROW(Exception() << ex_message("Uniform variable not found: "+Name));

	glUniform3fv(loc, 1, &(Value.x));
}






bool Shader::ProcessShader()
{
	m_ProgramObject=glCreateProgram();
	glAttachShader(m_ProgramObject, m_VertexShader.GetVertexObject());
	glAttachShader(m_ProgramObject, m_FragmentShader.GetFragmentObject());

	glLinkProgram(m_ProgramObject);

	//Fehler suchen:
	int Success=0;
	glGetProgramiv(m_ProgramObject, GL_LINK_STATUS, &Success);

	if(!Success)
	{
		char Text[2048];
		int Length;
		glGetProgramInfoLog(m_ProgramObject, 2046, &Length, Text);
		THROW(Exception() << ex_message("Shader couldn't be linked - ") << ex_detail(Text));
	}

	glValidateProgram(m_ProgramObject);
	glGetProgramiv(m_ProgramObject, GL_VALIDATE_STATUS, &Success);
	if(!Success)
	{
		char Text[512];
		int Length;
		glGetProgramInfoLog(m_ProgramObject, 510, &Length, Text);
		THROW(Exception() << ex_message("Shader couldn't be linked - ") << ex_detail(Text));
	}

    return true;
}



//----------------- Vertex Shader ------------------------

VertexShader::VertexShader():
m_VertexObject(0)
{
}

VertexShader::VertexShader(const VertexShader& ref):
m_VertexObject(ref.m_VertexObject)
{
}

VertexShader::~VertexShader()
{
	glDeleteShader(m_VertexObject);
}

GLuint VertexShader::Load(string Filename)
{
	ifstream File;

	File.open(Filename.c_str(), ios::in | ios::binary);

	if(!File.is_open())
	{
		THROW(Exception() << ex_message("VertexShader File couldn't be loaded! - ") << ex_file(Filename));
		return 0;
	}

	File.seekg(0, ios::end);//zum ende der datei springen
	int FileLength=static_cast<int>(File.tellg());
	File.seekg(0, ios::beg);

	char* Buffer=new char[FileLength];

	File.read(Buffer, FileLength);

	File.close();
	string Source(Buffer, FileLength);

	delete[] Buffer;

	return Create(Source);
}

GLuint VertexShader::Create(string Source)
{
	m_VertexObject=glCreateShader(GL_VERTEX_SHADER);
	const GLchar* Text=(GLchar*)Source.c_str();//TODO write better code for this 2 lines
	glShaderSource(m_VertexObject, 1, &Text, 0);
	glCompileShader(m_VertexObject);


	//Nach Fehlern gucken:
	int Success;
	glGetShaderiv(m_VertexObject, GL_COMPILE_STATUS, &Success);

	if(!Success)
	{
		char Text[2048];
		int Length;
		glGetShaderInfoLog(m_VertexObject, 2046, &Length, Text);
		
		THROW(Exception() << ex_message("Vertexshader Compiling error!") << ex_detail(Text) << ex_source(Source));
		return 0;
	}

	return m_VertexObject;
}

GLuint VertexShader::GetVertexObject() const
{
	return m_VertexObject;
}

//--------------------------------- Fragment Shader----------------------------------------
FragmentShader::FragmentShader():
m_FragmentObject(0)
{
}

FragmentShader::FragmentShader(const FragmentShader& ref):
m_FragmentObject(ref.m_FragmentObject)
{
}

FragmentShader::~FragmentShader()
{
	glDeleteShader(m_FragmentObject);
}

GLuint FragmentShader::Load(string Filename)
{
	//TODO Unify Fileloading with vertexshader
	ifstream File;

	File.open(Filename.c_str(), ios::in | ios::binary);

	if(!File.is_open())
	{
		THROW(Exception() << ex_message("FragmentShader File couldn't be loaded! - ") << ex_file(Filename));
		return 0;
	}

	File.seekg(0, ios::end);//zum ende der datei springen
	int FileLength=static_cast<int>(File.tellg());
	File.seekg(0, ios::beg);

	char* Buffer=new char[FileLength];

	File.read(Buffer, FileLength);

	File.close();
	string Source(Buffer, FileLength);

	delete[] Buffer;

	return Create(Source);
}

GLuint FragmentShader::Create(string Source)
{
	m_FragmentObject=glCreateShader(GL_FRAGMENT_SHADER);
	const GLchar* Text=(GLchar*)Source.c_str();
	glShaderSource(m_FragmentObject, 1, &Text, 0);
	glCompileShader(m_FragmentObject);

	//nach fehlern gucken:
	int Success=0;
	glGetShaderiv(m_FragmentObject, GL_COMPILE_STATUS, &Success);

	if(!Success)
	{
		char Text[2048];
		int Length;
		glGetShaderInfoLog(m_FragmentObject, 2046, &Length, Text);
		THROW(Exception() << ex_message("Fragmentshader Compiling error! - ") << ex_detail(Text) << ex_source(Source));
		return 0;
	}

	return m_FragmentObject;
}

GLuint FragmentShader::GetFragmentObject() const
{
	return m_FragmentObject;
}