#include <GL/glew.h>
#include <SDKDDKVer.h>
#include <Windows.h>
#include "CGShaderProgram.h"
#include "resource.h"

//
// FUNCIÓN: CGShaderProgram::CGShaderProgram(int vs, int fs, int gs, int tcs, int tes)
//
// PROPÓSITO: Crea un programa gráfico cargando y compilando los shaders que lo forman
//
CGShaderProgram::CGShaderProgram(int vs, int fs, int gs, int tcs, int tes)
{
	vertexShader = NO_SHADER;
	fragmentShader = NO_SHADER;
	geometryShader = NO_SHADER;
	tessControlShader = NO_SHADER;
	tessEvaluationShader = NO_SHADER;
	compiled = GL_TRUE;
	linked = GL_FALSE;

	GLint status;
	logInfo = NULL;

	// Crea y compila el vertex shader
	if (vs != -1)
	{
		vertexShader = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertexShader, 1, GetShaderCodeFromResource(vs), NULL);
		glCompileShader(vertexShader);
		glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
		if (status == GL_FALSE)
		{
			compiled = GL_FALSE;
			GLint logLength;
			glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
			logInfo = (char*)malloc(sizeof(char) * logLength);
			GLsizei written;
			glGetShaderInfoLog(vertexShader, logLength, &written, logInfo);
		}
	}

	// Crea y compila el fragment shader
	if (fs != -1)
	{
		fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragmentShader, 1, GetShaderCodeFromResource(fs), NULL);
		glCompileShader(fragmentShader);
		glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
		if (status == GL_FALSE)
		{
			compiled = GL_FALSE;
			GLint logLength;
			glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
			logInfo = (char*)malloc(sizeof(char) * logLength);
			GLsizei written;
			glGetShaderInfoLog(fragmentShader, logLength, &written, logInfo);
		}
	}

	// Crea y compila el geometry shader
	if (gs != -1)
	{
		geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
		glShaderSource(geometryShader, 1, GetShaderCodeFromResource(gs), NULL);
		glCompileShader(geometryShader);
		glGetShaderiv(geometryShader, GL_COMPILE_STATUS, &status);
		if (status == GL_FALSE)
		{
			compiled = GL_FALSE;
			GLint logLength;
			glGetShaderiv(geometryShader, GL_INFO_LOG_LENGTH, &logLength);
			logInfo = (char*)malloc(sizeof(char) * logLength);
			GLsizei written;
			glGetShaderInfoLog(geometryShader, logLength, &written, logInfo);
		}
	}

	// Crea y compila el tessellation control shader
	if (tcs != -1)
	{
		tessControlShader = glCreateShader(GL_TESS_CONTROL_SHADER);
		glShaderSource(tessControlShader, 1, GetShaderCodeFromResource(tcs), NULL);
		glCompileShader(tessControlShader);
		glGetShaderiv(tessControlShader, GL_COMPILE_STATUS, &status);
		if (status == GL_FALSE)
		{
			compiled = GL_FALSE;
			GLint logLength;
			glGetShaderiv(tessControlShader, GL_INFO_LOG_LENGTH, &logLength);
			logInfo = (char*)malloc(sizeof(char) * logLength);
			GLsizei written;
			glGetShaderInfoLog(tessControlShader, logLength, &written, logInfo);
		}
	}

	// Crea y compila el tessellation evaluation shader
	if (tes != -1)
	{
		tessEvaluationShader = glCreateShader(GL_TESS_EVALUATION_SHADER);
		glShaderSource(tessEvaluationShader, 1, GetShaderCodeFromResource(tcs), NULL);
		glCompileShader(tessEvaluationShader);
		glGetShaderiv(tessEvaluationShader, GL_COMPILE_STATUS, &status);
		if (status == GL_FALSE)
		{
			compiled = GL_FALSE;
			GLint logLength;
			glGetShaderiv(tessEvaluationShader, GL_INFO_LOG_LENGTH, &logLength);
			logInfo = (char*)malloc(sizeof(char) * logLength);
			GLsizei written;
			glGetShaderInfoLog(tessEvaluationShader, logLength, &written, logInfo);
		}
	}

	//Crea el programa y carga los shaders
	program = glCreateProgram();
	if (vertexShader != NO_SHADER) glAttachShader(program, vertexShader);
	if (fragmentShader != NO_SHADER) glAttachShader(program, fragmentShader);
	if (geometryShader != NO_SHADER) glAttachShader(program, geometryShader);
	if (tessControlShader != NO_SHADER) glAttachShader(program, tessControlShader);
	if (tessEvaluationShader != NO_SHADER) glAttachShader(program, tessEvaluationShader);
}

//
// FUNCIÓN: CGShaderProgram::Link()
//
// PROPÓSITO: Enlaza el programa
//
GLvoid CGShaderProgram::Link() {
	GLint status;

	glLinkProgram(program);
	glGetProgramiv(program, GL_LINK_STATUS, &status);
	if (status == GL_FALSE)
	{
		linked = GL_FALSE;
		GLint logLength;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
		logInfo = (char*)malloc(sizeof(char) * logLength);
		GLsizei written;
		glGetProgramInfoLog(program, logLength, &written, logInfo);
		return;
	}

	linked = GL_TRUE;
}

//
// FUNCIÓN: CGShaderProgram::GetShaderCodeFromResource(int idr)
//
// PROPÓSITO: Obtiene el contenido de un fichero de texto almacenado como un recurso
//
char** CGShaderProgram::GetShaderCodeFromResource(int idr)
{
	HRSRC shaderHandle = FindResource(NULL, MAKEINTRESOURCE(idr), RT_HTML);
	HGLOBAL shaderGlobal = LoadResource(NULL, shaderHandle);
	LPCTSTR shaderPtr = static_cast<LPCTSTR>(LockResource(shaderGlobal));
	DWORD shaderSize = SizeofResource(NULL, shaderHandle);
	char* shaderCodeLine = (char*)malloc((shaderSize + 1) * sizeof(char));
	memcpy(shaderCodeLine, shaderPtr, shaderSize);
	shaderCodeLine[shaderSize] = '\0';
	char** shaderCode = (char**)malloc(sizeof(char*));
	shaderCode[0] = shaderCodeLine;
	FreeResource(shaderGlobal);
	return shaderCode;
}

//
// FUNCIÓN: CGShaderProgram::~CGShaderProgram()
//
// PROPÓSITO: Destruye el programa gráfico
//
CGShaderProgram::~CGShaderProgram()
{
	if (vertexShader != NO_SHADER) glDeleteShader(vertexShader);
	if (fragmentShader != NO_SHADER) glDeleteShader(fragmentShader);
	if (geometryShader != NO_SHADER) glDeleteShader(geometryShader);
	if (tessControlShader != NO_SHADER) glDeleteShader(tessControlShader);
	if (tessEvaluationShader != NO_SHADER) glDeleteShader(tessEvaluationShader);

	glDeleteProgram(program);
}

//
// FUNCIÓN: CGShaderProgram::IsCompiled()
//
// PROPÓSITO: Verifica si el programa se ha compilado de forma correcta
//
GLboolean CGShaderProgram::IsCompiled()
{
	return compiled;
}

//
// FUNCIÓN: CGShaderProgram::IsLinked()
//
// PROPÓSITO: Verifica si el programa se ha linkado de forma correcta
//
GLboolean CGShaderProgram::IsLinked()
{
	return linked;
}

//
// FUNCIÓN: CGShaderProgram::GetLog()
//
// PROPÓSITO: Obtiene el mensaje de error
//
char* CGShaderProgram::GetLog()
{
	return logInfo;
}

//
// FUNCIÓN: CGShaderProgram::Use()
//
// PROPÓSITO: Activa el funcionamiento del programa en la tarjeta gráfica
//
GLvoid CGShaderProgram::Use()
{
	glUseProgram(program);
}

//
// FUNCIÓN: CGShaderProgram::SetTransformFeedbackVaryings(int vars, const char* names[])
//
// PROPÓSITO: Configura el transform feedback (debe hacerse antes de linkar el programa)
//
GLvoid CGShaderProgram::SetTransformFeedbackVaryings(int vars, const char* names[])
{
	glTransformFeedbackVaryings(program, vars, names, GL_SEPARATE_ATTRIBS);
}

//
// FUNCIÓN: CGShaderProgram::SetUniformF(const char* name, GLfloat f)
//
// PROPÓSITO: Asigna el valor de una variable uniforme de tipo float
//
GLvoid CGShaderProgram::SetUniformF(const char* name, GLfloat f)
{
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) glUniform1f(location, f);
}

//
// FUNCIÓN: CGShaderProgram::SetUniformMatrix4(const char* name, glm::mat4 m)
//
// PROPÓSITO: Asigna el valor de una variable uniforme de tipo mat4 (matriz 4x4)
//
GLvoid CGShaderProgram::SetUniformMatrix4(const char* name, glm::mat4 m)
{
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) glUniformMatrix4fv(location, 1, GL_FALSE, &m[0][0]);
}

//
// FUNCIÓN: CGShaderProgram::SetUniformVec4(const char* name, glm::vec4 v)
//
// PROPÓSITO: Asigna el valor de una variable uniforme de tipo vec4 (vector de 4 float)
//
GLvoid CGShaderProgram::SetUniformVec4(const char* name, glm::vec4 v)
{
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) glUniform4fv(location, 1, &v[0]);
}

//
// FUNCIÓN: CGShaderProgram::SetUniformVec3(const char* name, glm::vec3 v)
//
// PROPÓSITO: Asigna el valor de una variable uniforme de tipo vec3 (vector de 3 float)
//
GLvoid CGShaderProgram::SetUniformVec3(const char* name, glm::vec3 v)
{
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) glUniform3fv(location, 1, &v[0]);
}

//
// FUNCIÓN: CGShaderProgram::SetUniformI(const char* name, GLint i)
//
// PROPÓSITO: Asigna el valor de una variable uniforme de tipo entero
//
GLvoid CGShaderProgram::SetUniformI(const char* name, GLint i)
{
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) glUniform1i(location, i);
}
