#include "game/renderer/shader.hpp"

namespace polygun::renderer {
	Shader::Shader() :
		m_program(0)
	{}

	Shader::Shader(const GLuint id) :
		m_program(id)
	{}

	Shader::Shader(const char* vertex_path, const char* fragment_path) {
			std::string vertex_code;
			std::string fragment_code;
			std::ifstream v_shader_file;
			std::ifstream f_shader_file;
			v_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
			f_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
			try {
				v_shader_file.open(vertex_path);
				f_shader_file.open(fragment_path);
				std::stringstream v_shader_stream, f_shader_stream;
				v_shader_stream << v_shader_file.rdbuf();
				f_shader_stream << f_shader_file.rdbuf();		
				v_shader_file.close();
				f_shader_file.close();
				vertex_code = v_shader_stream.str();
				fragment_code = f_shader_stream.str();			
			}
			catch (std::ifstream::failure& e) {
				std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
			}
			const char* v_shader_code = vertex_code.c_str();
			const char * f_shader_code = fragment_code.c_str();
			unsigned int vertex, fragment;
			vertex = glCreateShader(GL_VERTEX_SHADER);
			glShaderSource(vertex, 1, &v_shader_code, NULL);
			glCompileShader(vertex);
			check_compile_errors(vertex, "VERTEX");
			fragment = glCreateShader(GL_FRAGMENT_SHADER);
			glShaderSource(fragment, 1, &f_shader_code, NULL);
			glCompileShader(fragment);
			check_compile_errors(fragment, "FRAGMENT");
			m_program = glCreateProgram();
			glAttachShader(m_program, vertex);
			glAttachShader(m_program, fragment);
			glLinkProgram(m_program);
			check_compile_errors(m_program, "PROGRAM");
			glDeleteShader(vertex);
			glDeleteShader(fragment);

	}

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

	void Shader::bind() {
		glUseProgram(m_program);
	}

	void Shader::unbind() {
		glUseProgram(0);
	}

	void Shader::set_uniform(const GLchar* u_name, unsigned int value) {
		glUniform1i(glGetUniformLocation(m_program, u_name), value);
	}

	void Shader::set_uniform(const GLchar* u_name, int value) {
		glUniform1i(glGetUniformLocation(m_program, u_name), value);
	}

	void Shader::set_uniform(const GLchar* u_name, GLfloat value) {
		glUniform1i(glGetUniformLocation(m_program, u_name), value);
	}

	void Shader::set_uniform(const GLchar* u_name, GLfloat x, GLfloat y) {
		glUniform2f(glGetUniformLocation(m_program, u_name), x, y);
	}

	void Shader::set_uniform(const GLchar* u_name, GLfloat x, GLfloat y, GLfloat z) {
		glUniform3f(glGetUniformLocation(m_program, u_name), x, y, z);
	}    

	void Shader::set_uniform(const GLchar* u_name, const glm::vec3& vector) {
		glUniform3f(glGetUniformLocation(m_program, u_name), vector.x, vector.y, vector.z);
	}

	void Shader::set_uniform(const GLchar* u_name, GLfloat x, GLfloat y, GLfloat z, GLfloat w) {
		glUniform4f(glGetUniformLocation(m_program, u_name), x, y, z, w);
	}

	void Shader::set_uniform(const GLchar* name, const glm::mat4 &mat) {
		glUniformMatrix4fv(glGetUniformLocation(m_program, name), 1, GL_FALSE, &mat[0][0]);
	}

	void Shader::set_uniform(const GLchar* u_name, const glm::vec2& vector) {
		glUniform2f(glGetUniformLocation(m_program, u_name), vector.x, vector.y);
	}

	void Shader::set_uniform(const GLchar* u_name, const glm::vec4& vector){
		glUniform4f(glGetUniformLocation(m_program, u_name), vector.x, vector.y, vector.z, vector.w);
	}

	void Shader::set_uniform(const GLchar* u_name, GLuint tex2d, GLint unit){ // sample 2d
		glActiveTexture(GL_TEXTURE0 + unit);
		glBindTexture(GL_TEXTURE_2D, tex2d);
		glUniform1i(glGetUniformLocation(m_program, u_name), unit);
	}

	void Shader::check_compile_errors(GLuint shader, const std::string& type) {
		GLint success;
		GLchar info_log[1024];
		if(type != "PROGRAM") {
			glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
			if(!success) {
				glGetShaderInfoLog(shader, 1024, NULL, info_log);
				std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << info_log << "\n -- --------------------------------------------------- -- " << std::endl;
			}
		}
		else {
			glGetProgramiv(shader, GL_LINK_STATUS, &success);
			if(!success) {
				glGetProgramInfoLog(shader, 1024, NULL, info_log);
				std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << info_log << "\n -- --------------------------------------------------- -- " << std::endl;
			}
		}
	}
}
