#include "graphics/shader.h"

#include "utility/logger.h"
#include <cassert>

Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
	std::string vertexSource;
	std::string fragmentSource;
	std::ifstream vertexStream;
	std::ifstream fragmentStream;

	vertexStream.exceptions(std::ifstream::badbit | std::ifstream::failbit);
	fragmentStream.exceptions(std::ifstream::badbit | std::ifstream::failbit);
	try
	{
		vertexStream.open(vertexPath);
		fragmentStream.open(fragmentPath);

		std::stringstream vStringStream, fStringStream;

		vStringStream << vertexStream.rdbuf();
		fStringStream << fragmentStream.rdbuf();

		vertexSource = vStringStream.str();
		fragmentSource = fStringStream.str();

		vertexStream.close();
		fragmentStream.close();
	}
	catch(std::exception e)
	{
		LOG(ERROR, "failed to open shader files '{}', '{}' error: {}", vertexPath, fragmentPath, e.what());
		assert(1 == 0); // force crash
	}
	const char* vSource = vertexSource.c_str();
	const char* fSource = fragmentSource.c_str();

	unsigned int vertexid, fragmentid;
	char infoLog[512];
	int success;

	//compile vertex shader
	vertexid = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexid, 1, &vSource, NULL);
	glCompileShader(vertexid);

	glGetShaderiv(vertexid, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexid, 512, NULL, infoLog);
		LOG(ERROR, "VERTEX SHADER '{}' COMPILE ERROR\n{}", vertexPath, infoLog);
	}

	//compile fragment shader
	fragmentid = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentid, 1, &fSource, NULL);
	glCompileShader(fragmentid);

	glGetShaderiv(fragmentid, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentid, 512, NULL, infoLog);
		LOG(ERROR, "FRAGMENT SHADER '{}' COMPILE ERROR\n{}", fragmentPath, infoLog);
	}

	//create and link program with compiled shaders
	ID = glCreateProgram();
	glAttachShader(ID, vertexid);
	glAttachShader(ID, fragmentid);
	glLinkProgram(ID);

	glGetProgramiv(ID, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(ID, 512, NULL, infoLog);
		LOG(ERROR, "PROGRAM LINKER ERROR\n{}", infoLog);
	}

	glDeleteShader(vertexid);
	glDeleteShader(fragmentid);
}

void Shader::setFloat(const std::string& name, float value)
{
	glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setFloatArray(const std::string& name, size_t count, const float* value)
{
	glUniform1fv(glGetUniformLocation(ID, name.c_str()), count, value);
}

void Shader::setIntArray(const std::string& name, size_t count, const int* value)
{
	glUniform1iv(glGetUniformLocation(ID, name.c_str()), count, value);
}

void Shader::setInt(const std::string& name, int value)
{
	glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setBool(const std::string& name, bool value)
{
	glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setVec2(const std::string& name, const float* value)
{
	glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, value);
}

void Shader::setMatrix4f(const std::string& name, const float* value)
{
	GLuint loc = glGetUniformLocation(ID, name.c_str());
	glUniformMatrix4fv(loc, 1, GL_FALSE, value);
}

Shader::~Shader()
{
	glDeleteProgram(ID);
}
