#include <sstream>
#include <fstream>
#include "Shader.h"

#include "../Exceptions/IOException.h"
#include "../Exceptions/Shaders/ShaderCompileException.h"
#include "../exceptions/shaders/CreateProgramException.h"

namespace Nebulix 
{
	// debugging
	const char* vertexShaderSource = "#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"
		"layout (location = 1) in vec3 aColor;\n"
		"out vec3 ourColor;\n"
		"void main()\n"
		"{\n"
		"   gl_Position = vec4(aPos, 1.0);\n"
		"   ourColor = aColor;\n"
		"}\0";
	const char* fragmentShaderSource = "#version 330 core\n"
		"out vec4 FragColor;\n"
		"in vec3 ourColor;\n"
		"void main()\n"
		"{\n"
		"   FragColor = vec4(ourColor, 1.0f);\n"
		"}\n\0";

	Shader::Shader(std::string& vertexShaderfile, std::string& fragmentShaderfile): _vertexFile{vertexShaderfile}, _fragmentFile{fragmentShaderfile}
	{
		std::ifstream vertexShaderSource(vertexShaderfile), fragmentShaderSource(fragmentShaderfile);
		GLuint vertexShader = 0, fragmentShader = 0;

		CompileShadercode(vertexShaderSource, vertexShader, ShaderType::Vertex);
		CompileShadercode(fragmentShaderSource, fragmentShader, ShaderType::Fragment);

		CreateProgram(vertexShader, fragmentShader);
	}

	void Shader::CompileShadercode(std::ifstream& shaderFile, GLuint& shaderObject, ShaderType type)
	{
		if (!shaderFile.is_open())
		{
			throw IOException("ERROR::SHADER::CANNOT_OPEN_FILE");
		}

		std::string code;
		const char* shaderCode;

		shaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
		try
		{
			std::stringstream shaderStream;
			shaderStream << shaderFile.rdbuf();
			shaderFile.close();
			code = shaderStream.str();
		}
		catch (const std::exception&)
		{
			throw Nebulix::IOException("ERROR::SHADER::READ_FILE");
		}
		shaderCode = code.c_str();

		if (type == ShaderType::Vertex)
			shaderObject = glCreateShader(GL_VERTEX_SHADER);
		else if (type == ShaderType::Fragment)
			shaderObject = glCreateShader(GL_FRAGMENT_SHADER);
		else
			throw std::runtime_error("ERROR::SHADER::TYPE \nCannot create shader program with the given shader type '" + std::to_string((int)type) + "'");

		glShaderSource(shaderObject, 1, &shaderCode, NULL);
		glCompileShader(shaderObject);

		int success;
		char infoLog[512];
		glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(shaderObject, 512, NULL, infoLog);
			throw Nebulix::ShaderCompileException(type, infoLog);
		}
	}

	void Shader::CreateProgram(GLuint& vertexShader, GLuint& fragmentShader)
	{
		shaderId = glCreateProgram();
		glAttachShader(shaderId, vertexShader);
		glAttachShader(shaderId, fragmentShader);
		glLinkProgram(shaderId);

		int success;
		char infoLog[512];
		glGetProgramiv(shaderId, GL_LINK_STATUS, &success);
		if (!success) {
			glGetProgramInfoLog(shaderId, 512, NULL, infoLog);
			throw Nebulix::CreateProgramException(infoLog);
		}

		glDeleteShader(vertexShader);
		glDeleteShader(fragmentShader);
	}
}
