#include "Shader.h"


Shader::Shader(const std::string& filepath) : m_filepath(filepath), m_rendererID(0)
{
	ShaderProgramSource source = ParseShader(filepath);
	m_rendererID = CreateShader(source.vertex_source, source.fragment_source);
}


Shader::~Shader()
{
	GLCALL(glDeleteProgram(m_rendererID));
}

void Shader::Bind() const
{
	GLCALL(glUseProgram(m_rendererID)); // activate target shader
}


void Shader::UnBind() const
{
	GLCALL(glUseProgram(0));
}


void Shader::SetUniform1i(const std::string & name, int value)
{
	GLCALL(glUniform1i(GetUniformLocation(name), value));
}


void Shader::SetUniform1f(const std::string & name, float value)
{
	GLCALL(glUniform1f(GetUniformLocation(name), value));
}

void Shader::SetUniform3f(const std::string & name, float v0, float v1, float v2)
{
	GLCALL(glUniform3f(GetUniformLocation(name), v0, v1, v2));
}


void Shader::SetUniform4f(const std::string& name, float v0, float v1, float v2, float v3)
{
	GLCALL(glUniform4f(GetUniformLocation(name), v0, v1, v2, v3));
}


void Shader::SetUniformMatrix4f(const std::string & name, const glm::mat4 & matrix)
{
	GLCALL(glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &matrix[0][0]));
}


int Shader::GetUniformLocation(const std::string& name)
{
	if (m_uniform_location_chache.find(name) != m_uniform_location_chache.end()) // If it is already chached
	{
		return m_uniform_location_chache[name];
	}
	else
	{
		GLCALL(int uniform_location = glGetUniformLocation(m_rendererID, name.c_str()));

		if (uniform_location == -1)
		{
			// 오류 메세지 출력
			Debugger::GetInstance()->WriteError("Shader // Uniform <" + name + "> does not exist !");
		}

		m_uniform_location_chache[name] = uniform_location;
		return uniform_location;
	}
}


ShaderProgramSource Shader::ParseShader(const std::string& file_path)
{
	std::ifstream stream(file_path);

	enum class ShaderTypes
	{
		NONE = 0,
		VERTEX = 1,
		FRAGMENT = 2,
	} shader_type = ShaderTypes::NONE;


	std::stringstream shader_stream[3];

	std::string read_one_line;

	while (getline(stream, read_one_line))
	{
		if (read_one_line.find("#shader") != std::string::npos) // if we can find specific string postion which is "#shader"
		{
			if (read_one_line.find("vertex") != std::string::npos) // if we can find "vertex" in that line
			{
				shader_type = ShaderTypes::VERTEX;
			}
			else if (read_one_line.find("fragment") != std::string::npos) // if we can find "fragment" in that line
			{
				shader_type = ShaderTypes::FRAGMENT;
			}
			else // syntex error
			{
				shader_type = ShaderTypes::NONE;
			}
		}
		else
		{
			shader_stream[(int)shader_type] << read_one_line << '\n';
		}
	}


	return { shader_stream[static_cast<int>(ShaderTypes::VERTEX)].str(), shader_stream[static_cast<int>(ShaderTypes::FRAGMENT)].str() };

}


unsigned int Shader::CompileShader(const unsigned int shader_type, const std::string& source)
{
	unsigned int shader_id = glCreateShader(shader_type); // Generate new shader file
	const char* source_data = source.c_str(); // Convert to c string
	GLCALL(glShaderSource(shader_id, 1, &source_data, nullptr)); // (which shader, how many sources, string data, string length)
	GLCALL(glCompileShader(shader_id));

	// TODO : Error handling..
	int shader_status;
	GLCALL(glGetShaderiv(shader_id, GL_COMPILE_STATUS, &shader_status)); // Get shader compile status
	if (shader_status == GL_FALSE) // If the shader compilation fails
	{
		// Get the length of error message
		int log_length;
		GLCALL(glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length));

		// Get the error message
		char* log_message = new char[log_length];
		//std::vector<char> log_message(log_length);
		GLCALL(glGetShaderInfoLog(shader_id, log_length, &log_length, log_message));

		// Print the error message
		if (shader_type == GL_VERTEX_SHADER)
		{
			Debugger::GetInstance()->WriteMessage("Shader // Vertex shader compile error !");
		}
		else if (shader_type == GL_FRAGMENT_SHADER)
		{
			Debugger::GetInstance()->WriteMessage("Shader // Fragment shader compile error !");
		}

		// 오류 메세지 출력
		Debugger::GetInstance()->WriteError(log_message);

		// Do not leak the VRAM
		GLCALL(glDeleteShader(shader_id));

		return -1;
	}

	if (shader_type == GL_VERTEX_SHADER)
	{
		Debugger::GetInstance()->WriteMessage("Shader // Vertex shader compiled successfully...");
	}
	else if (shader_type == GL_FRAGMENT_SHADER)
	{
		Debugger::GetInstance()->WriteMessage("Shader // Fragment shader compiled successfully...");
	}

	return shader_id;
}


unsigned int Shader::CreateShader(const std::string& vertex_shader_string, const std::string& fragment_shader_string)
{
	unsigned int program_id = glCreateProgram();
	unsigned int vertex_shader_id = CompileShader(GL_VERTEX_SHADER, vertex_shader_string);
	unsigned int fragment_shader_id = CompileShader(GL_FRAGMENT_SHADER, fragment_shader_string);

	// Link shaders
	GLCALL(glAttachShader(program_id, vertex_shader_id));
	GLCALL(glAttachShader(program_id, fragment_shader_id));
	GLCALL(glLinkProgram(program_id));
	GLCALL(glValidateProgram(program_id));

	// Delete obsolete shader files because we already have shader programs
	//glDetachShader(program_id, vertex_shader_id); // Do not detatch it for debugging
	//glDetachShader(program_id, fragment_shader_id); // Do not detatch it for debugging
	GLCALL(glDeleteShader(vertex_shader_id));
	GLCALL(glDeleteShader(fragment_shader_id));

	return program_id;
}

