
#include <Spectre/Math/Color.h>
#include <Spectre/Graphics/OpenGL.h>
#include <Spectre/Graphics/ShaderProgram.h>
#include <Spectre/System/File.h>

ShaderProgram::ShaderProgram() :
m_id	(0)
{
}

ShaderProgram::~ShaderProgram()
{
	destroy();
}

void ShaderProgram::create()
{
	destroy();

	m_id = glCreateProgram();
}

void ShaderProgram::destroy()
{
	if (m_id > 0) {
		glDeleteProgram(m_id);
		m_id = 0;
	}
}

void ShaderProgram::addShader(const Shader& shader)
{
	glAttachShader(m_id, shader.getHandle());
}

bool ShaderProgram::loadFromFile(const std::string& filename)
{
	std::string extension = File::getExtension(filename);

	// Meta file. load real shaders.
	if (extension == "shader.glsl") {

		// FIXME: This is ugly :)
		std::string base_name = filename.substr(0, filename.length() - extension.length());

		// vert and frag are not optional. they must exist.
		return	loadFromFile(base_name + "vert.glsl", Shader::Type::Vertex) && 
			loadFromFile(base_name + "frag.glsl", Shader::Type::Fragment);

	} else if (extension == "vert.glsl") {
		return loadFromFile(filename, Shader::Type::Vertex);
	} else if (extension == "frag.glsl") {
		return loadFromFile(filename, Shader::Type::Fragment);
	}

	m_error = "Invalid file extension: " + extension;

	return false;
}

bool ShaderProgram::loadFromFile(const std::string& filename, Shader::Type type)
{
	Shader shader(type);

	if (!shader.loadFromFile(filename)) {
		m_error = shader.getName() + ": " + shader.getError();
		return false;
	}

	addShader(shader);

	return true;
}

bool ShaderProgram::loadFromMemory(const std::string& source, Shader::Type type)
{
	Shader shader(type);

	if (!shader.loadFromMemory(source)) {
		m_error = shader.getError();
		return false;
	}

	addShader(shader);

	return true;
}

bool ShaderProgram::link()
{
	glLinkProgram(m_id);

	if (!isLinked()) {
		m_error = fetchErrorLog();
		return false;
	}
	return true;
}

bool ShaderProgram::isLinked() const
{
	// Make sure we have a id first.
	if (m_id) {

		// Query OpenGL for link status.
		GLint status;
		glGetProgramiv(m_id, GL_LINK_STATUS, &status);
		return status == GL_TRUE;
	}
	return false;
}

void ShaderProgram::enable() const
{
	glUseProgram(m_id);
}
	
void ShaderProgram::disable() const
{
	glUseProgram(0);
}

std::string ShaderProgram::getLastError() const
{
	return m_error;
}

bool ShaderProgram::setMVPMatrix(const Matrix4f& matrix)
{
	return setUniform("u_MVP", matrix);
}

bool ShaderProgram::setUniform(const std::string& name, const Matrix4f& matrix) const
{
	int loc;
	if (getUniformLoc(name, loc)) {
		glUniformMatrix4fv(loc, 1, GL_FALSE, matrix.e);
		return true;
	}
	return false;
}

bool ShaderProgram::setAttribute(const std::string& name, const Matrix4f& matrix) const
{
	int loc;
	if (getAttribLoc(name, loc)) {
		glVertexAttrib4fv(loc, matrix.e);
		return true;
	}
	return false;
}

bool ShaderProgram::setUniform(const std::string& name, const Color& color) const
{
	int loc;
	if (getUniformLoc(name, loc)) {
		Vector4f c = color.toRGBAf();

		glUniform4f(loc, c.x, c.y, c.z, c.w);
		return true;
	}
	return false;
}

bool ShaderProgram::getAttribLoc(const std::string& name, int& loc) const
{
	loc = glGetAttribLocation(m_id, name.c_str());
	if (loc < 0) {
		m_error = "Attribute variable '" + name + "' was not found in shader.";
		return false;
	}
	return true;
}

bool ShaderProgram::getUniformLoc(const std::string& name, int& loc) const
{
	loc = glGetUniformLocation(m_id, name.c_str());
	if (loc < 0) {
		m_error = "Uniform variable '" + name + "' was not found in shader.";
		return false;
	}
	return true;
}

std::string ShaderProgram::fetchErrorLog()
{
	GLchar buf[4096] = { '\0' };

	glGetProgramInfoLog(m_id, sizeof(buf), NULL, buf);

	return std::string(buf);
}
