/*
								+---------------------------------+
								|                                 |
								|  ***   OpenGL 3.0 shader   ***  |
								|                                 |
								|  Copyright  © -tHE SWINe- 2009  |
								|                                 |
								|           Shader3.cpp           |
								|                                 |
								+---------------------------------+
*/

/**
 *	@file gl3/Shader3.cpp
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief OpenGL 3.0 shaders
 */

#include "../NewFix.h"
#include "../CallStack.h"
#include "Shader3.h"
#include "../StlUtils.h"

/*
 *								=== CShaderStringTable ===
 */

class CShaderStringTable {
public:
	/*static const char *p_s_ARB_Program_String() { return "=== program (ARB) ===\n"; }
	static const char *p_s_ARB_VertexProgram_String() { return "=== vertex program (ARB) ===\n"; }
	static const char *p_s_ARB_FragmentProgram_String() { return "=== fragment program (ARB) ===\n"; }
	static const char *p_s_ARB_Shader_String() { return "=== shader (ARB) ===\n"; }
	static const char *p_s_ARB_VertexShader_String() { return "=== vertex shader (ARB) ===\n"; }
	static const char *p_s_ARB_FragmentShader_String() { return "=== fragment shader (ARB) ===\n"; }*/ // unused
	static const char *p_s_Core_Shader_String() { return "=== shader (OpenGL 3 core) ===\n"; }
	static const char *p_s_Core_VertexShader_String() { return "=== vertex shader (OpenGL 3 core) ===\n"; }
	static const char *p_s_Core_GeometryShader_String() { return "=== geometry shader (OpenGL 3 core) ===\n"; }
	static const char *p_s_Core_FragmentShader_String() { return "=== fragment shader (OpenGL 3 core) ===\n"; }
};

/*
 *								=== ~CShaderStringTable ===
 */

/*
 *								 === CGL3Shader ===
 */

CGL3Shader::CGL3Shader()
	:m_n_program_object(0), m_n_vs_object(0), m_n_gs_object(0), m_n_fs_object(0),
	m_b_compiled(false), m_b_linked(false)
{}

CGL3Shader::~CGL3Shader()
{
	Delete();
}

bool CGL3Shader::Compile(const char *p_s_vertex_shader,
	const char *p_s_geometry_shader, const char *p_s_fragment_shader,
	std::string &r_s_info_log)
{
	Delete();

	r_s_info_log.erase();

	m_n_program_object = glCreateProgram();

	const GLenum p_target[] = {GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER};
	const char *p_source_list[] = {p_s_vertex_shader, p_s_geometry_shader, p_s_fragment_shader};
	const char *p_header_list[] = {CShaderStringTable::p_s_Core_VertexShader_String(),
		CShaderStringTable::p_s_Core_GeometryShader_String(),
		CShaderStringTable::p_s_Core_FragmentShader_String()};
	GLuint *p_shader_object[] = {&m_n_vs_object, &m_n_gs_object, &m_n_fs_object};

	for(int n_unit = 0; n_unit < 3; ++ n_unit) {
		const char *p_s_source = p_source_list[n_unit];
		GLenum n_target = p_target[n_unit];
		if(*p_shader_object[n_unit] = (p_s_source)? glCreateShader(n_target) : 0) {
			GLuint n_shader_object = *p_shader_object[n_unit];
			int n_src_length = int(strlen(p_s_source));
			glShaderSource(n_shader_object, 1, &p_s_source, &n_src_length);
			glCompileShader(n_shader_object);
			// compile ...

			int n_tmp;
			glGetShaderiv(n_shader_object, GL_COMPILE_STATUS, &n_tmp);
			bool b_compiled = n_tmp == GL_TRUE;
			int n_log_length;
			glGetShaderiv(n_shader_object, GL_INFO_LOG_LENGTH, &n_log_length);
			// query status ...

			if(n_log_length > 1) {
				char *p_s_temp_info_log;
				if(!(p_s_temp_info_log = new(std::nothrow) char[n_log_length + 1]))
					return false;
				// alloc temp storage for log string

				glGetShaderInfoLog(n_shader_object, n_log_length, &n_log_length, p_s_temp_info_log);
				// get log string

				if((!r_s_info_log.empty() && !stl_ut::AppendCStr(r_s_info_log, "\n\n")) ||
				   !stl_ut::AppendCStr(r_s_info_log, p_header_list[n_unit]) ||
				   !stl_ut::AppendCStr(r_s_info_log, p_s_temp_info_log)) {
					delete[] p_s_temp_info_log;
					return false;
				}
				// append log string

				delete[] p_s_temp_info_log;
				// delete temp storage
			}
			// get/concat info-log

			if(!b_compiled)
				return false;
		}
		// compile shader
	}
	// compile shaders for different units in loop (should be less error prone)

	if(m_n_vs_object)
		glAttachShader(m_n_program_object, m_n_vs_object);
	if(m_n_gs_object)
		glAttachShader(m_n_program_object, m_n_gs_object);
	if(m_n_fs_object)
		glAttachShader(m_n_program_object, m_n_fs_object);
	// attach shaders to a program

	if(glGetError() == GL_NO_ERROR)
		m_b_compiled = true;

	return m_b_compiled;
}

bool CGL3Shader::BindAttribLocation(int n_location, const char *p_s_attrib_name)
{
	if(!m_b_compiled || m_b_linked)
		return false;
	// must be compiled, but not linked!

	glBindAttribLocation(m_n_program_object, n_location, p_s_attrib_name);

	return glGetError() == GL_NO_ERROR;
}

bool CGL3Shader::SetGeometry_InputType(GLenum n_input_type)
{
	if(!m_b_compiled || m_b_linked || !m_n_gs_object)
		return false;
	// must be compiled, but not linked, need geometry shader

	glProgramParameteri(m_n_gs_object, GL_GEOMETRY_INPUT_TYPE, n_input_type);

	return glGetError() == GL_NO_ERROR;
}

bool CGL3Shader::SetGeometry_OutputType(GLenum n_output_type)
{
	if(!m_b_compiled || m_b_linked || !m_n_gs_object)
		return false;
	// must be compiled, but not linked, need geometry shader

	glProgramParameteri(m_n_gs_object, GL_GEOMETRY_OUTPUT_TYPE, n_output_type);

	return glGetError() == GL_NO_ERROR;
}

int CGL3Shader::n_Geometry_MaxOutputVertices()
{
	int n_max_vertex_num;
	glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &n_max_vertex_num);
	return n_max_vertex_num;
}

int CGL3Shader::n_Geometry_MaxOutputComponents()
{
	int n_max_component_num;
	glGetIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &n_max_component_num);
	return n_max_component_num;
}

bool CGL3Shader::SetGeometry_VerticesOut(int n_max_vertex_num)
{
	if(!m_b_compiled || m_b_linked || !m_n_gs_object)
		return false;

	glProgramParameteri(m_n_gs_object, GL_GEOMETRY_VERTICES_OUT, n_max_vertex_num);

	return glGetError() == GL_NO_ERROR;
}

bool CGL3Shader::Link(std::string &r_s_info_log)
{
	r_s_info_log.erase();

	if(!m_b_compiled || m_b_linked)
		return false;

	glLinkProgram(m_n_program_object);
	int n_tmp;
	glGetProgramiv(m_n_program_object, GL_LINK_STATUS, &n_tmp);
	m_b_linked = n_tmp == GL_TRUE;
	// link

	int n_length;
	glGetProgramiv(m_n_program_object, GL_INFO_LOG_LENGTH, &n_length);
	if(n_length > 1) {
		char *p_s_temp_info_log;
		if(!(p_s_temp_info_log = new(std::nothrow) char[n_length + 1]))
			return false;
		// alloc temp log

		glGetProgramInfoLog(m_n_program_object, n_length, &n_length, p_s_temp_info_log);
		// get info log

		const char *p_s_header = CShaderStringTable::p_s_Core_Shader_String();
		if(!stl_ut::AssignCStr(r_s_info_log, p_s_header) ||
		   !stl_ut::AppendCStr(r_s_info_log, p_s_temp_info_log)) {
			delete[] p_s_temp_info_log;
			return false;
		}
		// concatenate strings

		delete[] p_s_temp_info_log;
		// release temp log
	}
	// get info-log

	return m_b_linked;
}

int CGL3Shader::n_Uniform_Num() const
{
	if(!m_b_compiled || !m_b_linked)
		return -1;

	int n_uniform_num;
	glGetProgramiv(m_n_program_object, GL_ACTIVE_UNIFORMS, &n_uniform_num);

	return (glGetError() == GL_NO_ERROR)? n_uniform_num : -1;
}

char *CGL3Shader::p_s_Uniform_Name(int n_index, GLenum &r_n_content_type, int &r_n_size) const
{
	if(!m_b_compiled || !m_b_linked)
		return 0;

	int n_max_name_length;
	glGetProgramiv(m_n_program_object,
		GL_ACTIVE_UNIFORM_MAX_LENGTH, &n_max_name_length);

	char *p_s_temp_name;
	if(!(p_s_temp_name = new(std::nothrow) char[n_max_name_length + 1]))
		return 0;

	int n_length;
	int n_size;
	GLenum n_type;
	glGetActiveUniform(m_n_program_object, n_index, n_max_name_length, &n_length,
		&n_size, &n_type, p_s_temp_name);

	r_n_size = n_size;
	r_n_content_type = n_type;

	if(glGetError() != GL_NO_ERROR) {
		delete[] p_s_temp_name;
		return 0;
	}

	if(strlen(p_s_temp_name) + 1 == n_max_name_length)
		return p_s_temp_name;
	else {
		char *p_s_name;
		if(!(p_s_name = new(std::nothrow) char[strlen(p_s_temp_name) + 1]))
			return p_s_temp_name;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
		strcpy_s(p_s_name, (strlen(p_s_temp_name) + 1) * sizeof(char), p_s_temp_name);
#else // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		strcpy(p_s_name, p_s_temp_name);
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		delete[] p_s_temp_name;
		return p_s_name;
	}
}

int CGL3Shader::n_GetUniformLocation(const char *p_s_uniform_name) const
{
	if(!m_b_compiled || !m_b_linked)
		return -1;
	return glGetUniformLocation(m_n_program_object, p_s_uniform_name);
}

void CGL3Shader::Delete()
{
	m_b_compiled = false;
	m_b_linked = false;

	if(m_n_program_object) {
		glDeleteProgram(m_n_program_object);
		m_n_program_object = 0;
	}
	if(m_n_vs_object) {
		glDeleteShader(m_n_vs_object);
		m_n_vs_object = 0;
	}
	if(m_n_gs_object) {
		glDeleteShader(m_n_gs_object);
		m_n_gs_object = 0;
	}
	if(m_n_fs_object) {
		glDeleteShader(m_n_fs_object);
		m_n_fs_object = 0;
	}
}

/*
 *								=== ~CGL3Shader ===
 */
