#include "shader.h"

static bool _shader_compile(u32 handle);

/* Attempt to compile a shader, given its handle. */
static bool
_shader_compile(u32 handle)
{
 	s32 success;
	char log[SHADER_LOG_BUFFER_SIZE];

 	glCompileShader(handle);
	glGetShaderiv(handle, GL_COMPILE_STATUS, &success);

	if (!success)
	{
		glGetShaderInfoLog(handle, SHADER_LOG_BUFFER_SIZE, NULL, log);
		printf(STRING_SHADER_COMPILE_ERROR, handle, log);
		return false;
	}
	else
		printf(STRING_SHADER_COMPILE_SUCCESS, handle);
	
	return true;
}

/* Given two GLSL strings representing the vertex/fragment shaders,
 * initializes and compiles shader program. */
bool
shader_init(Shader* self, const char* vertex, const char* fragment)
{
	GLuint vertexHandle;
	GLuint fragmentHandle;

	memset(self, '\0', sizeof(struct Shader));

	vertexHandle = glCreateShader(GL_VERTEX_SHADER);
	fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER);

	glShaderSource(vertexHandle, 1, &vertex, NULL);
	glShaderSource(fragmentHandle, 1, &fragment, NULL);

	if (!_shader_compile(vertexHandle) || !_shader_compile(fragmentHandle))
		return false;

	self->handle = glCreateProgram();

	glAttachShader(self->handle, vertexHandle);
	glAttachShader(self->handle, fragmentHandle);

	glLinkProgram(self->handle);

	glDeleteShader(vertexHandle);
	glDeleteShader(fragmentHandle);

	self->isInit = true;

	return true;
}

/* Uses the specified shader program. */
void
shader_use(Shader* self)
{
	glUseProgram(self->handle);
}

/* Frees a shader program. */
void
shader_free(Shader* self)
{
	if (self->isInit)
	{
		glDeleteProgram(self->handle);
		self->isInit = false;
	}
}

/* Sets a s32 uniform. */
void
shader_uniform_s32_set(Shader* self, const char* name, s32 value) 
{
	glUniform1i(glGetUniformLocation(self->handle, name), value); 
}

/* Sets a f32 uniform. */
void
shader_uniform_f32_set(Shader* self, const char* name, f32 value) 
{ 
	glUniform1i(glGetUniformLocation(self->handle, name), value); 
}

/* Sets a vec3 uniform. */
void
shader_uniform_vec3_set(Shader* self, const char* name, vec3 value)
{
	glUniform3fv(glGetUniformLocation(self->handle, name), 1, value);
}

/* Sets a vec4 uniform. */
void
shader_uniform_vec4_set(Shader* self, const char* name, vec4 value)
{
	glUniform4fv(glGetUniformLocation(self->handle, name), 1, value);
}

/* Sets a mat4 uniform. */
void
shader_uniform_mat4_set(Shader* self, const char* name, mat4 value)
{
	glUniformMatrix4fv(glGetUniformLocation(self->handle, name), 1, GL_FALSE, (f32*)value);
}

/* Sets a texture uniform. */
void
shader_uniform_texture_set(Shader* self, const char* name, Texture* texture, s32 index)
{
	glActiveTexture(GL_TEXTURE0 + index);
	texture_bind(texture);
	glUniform1i(glGetUniformLocation(self->handle, name), index);
}
