#include <assert.h>
#include <algorithm>
#include "utils/logger.h"
#include "render/shadersystem.h"
#include "render/shader.h"
#include <render/gl_shared.h>

static const char* g_uniformNameTable[UNIFORM_MAX] =
{
	"u_modelMatrix",
	"u_viewMatrix",
	"u_projectionMatrix",
	"u_modelViewProjection",
	"u_customColor",
	"u_sunDirection",
	"u_sunColor",
	"u_sunAmbientColor",
};

static const char* g_samplersNameTable[SAMPLER_MAX] =
{
	"u_albedoTexture",
	"u_normalTexture",
	"u_lightmapTexture",
};

ShaderSystem* g_shaderSystem = nullptr;

ShaderSystem::ShaderSystem()
{
}

ShaderSystem::~ShaderSystem()
{
}

void ShaderSystem::Init()
{
	Msg("Initializing Shader System ...");
}

void ShaderSystem::Shutdown()
{
	for (int i = 0; i < m_shaders.size(); i++)
	{
		ShaderData& shaderData = m_shaders[i];

		if (shaderData.shader)
		{
			delete shaderData.shader;
			shaderData.shader = nullptr;
		}
	}

	m_shaders.clear();
}

Shader* ShaderSystem::CreateShader(const char* name, const char* vsfilepath, const char* psfilepath, InputLayoutDesc_t* inputLayout /*= nullptr*/, int inputLayoutCount/* = 0*/)
{
	auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [=](const ShaderData& shaderData) { return shaderData.shadername == name; });
	if ( it != m_shaders.end() )
	{
		return it->shader;
	}

	Shader* pShader = new Shader();
	
	if ( inputLayout && inputLayoutCount > 0 )
	{
		memcpy( pShader->m_layouts, inputLayout, inputLayoutCount * sizeof( InputLayoutDesc_t ) );
		pShader->m_layout_count = inputLayoutCount;
	}

	pShader->Create( name, vsfilepath, psfilepath );

	ShaderData shaderData = { name, pShader };
	m_shaders.push_back(shaderData);

	return pShader;
}

size_t g_vertex_attribs_sizetable[VERTEXATTR_MAX] =
{
	2, // VERTEXATTR_VEC2
	3, // VERTEXATTR_VEC3
	4, // VERTEXATTR_VEC4
};

size_t g_vertex_attribs_realsizetable[VERTEXATTR_MAX] =
{
	8, // VERTEXATTR_VEC2
	12, // VERTEXATTR_VEC3
	16, // VERTEXATTR_VEC4
};

void ApplyVertexFormat(const Shader* shader, size_t stride)
{
	size_t appliedOffset = 0;
	for (int i = 0; i < shader->m_layout_count; i++)
	{
		const InputLayoutDesc_t* layoutEntry = &shader->m_layouts[i];

		glEnableVertexAttribArray(GLuint(i));

		glVertexAttribPointer(GLuint(i), GLint(g_vertex_attribs_sizetable[layoutEntry->attribute]),
			GL_FLOAT, GL_FALSE, static_cast<GLsizei>(stride),
			(appliedOffset > 0) ? (void*)(appliedOffset * sizeof(float)) : (void*)0);

		appliedOffset += g_vertex_attribs_sizetable[layoutEntry->attribute];
	}
}

void ShaderSystem::SetShader(const Shader* shader)
{
	assert( shader );
	glUseProgram( shader->m_program );
	
	// apply input layout

	//for ( int i = 0; i < shader->m_layout_count; i++ )
	//{
	//	const InputLayoutDesc_t& layout = shader->m_layouts[ i ];
	//
	//	//glEnableVertexAttribArray( shader->m_glLayouts[ i ] );
	//	//glVertexAttribPointer( shader->m_glLayouts[ i ], GetVertexAttributeSize( layout.attribute ), GL_FLOAT, GL_FALSE, shader->m_stride, NULL );

	//	glEnableVertexAttribArray( i );
	//	glVertexAttribPointer( i, GetVertexAttributeSize( layout.attribute ), GL_FLOAT, GL_FALSE, shader->m_stride, NULL );
	//}

	ApplyVertexFormat( shader, shader->m_stride );
	//for ( int i = 0; i < shader->m_layout_count; i++ )
	//{
	//	const InputLayoutDesc_t& layout = shader->m_layouts[ i ];
	//
	//	glEnableVertexAttribArray( shader->m_glLayouts[ i ] );
	//	glVertexAttribPointer( shader->m_glLayouts[ i ], GetVertexAttributeSize( layout.attribute ), GL_FLOAT, GL_FALSE, shader->m_stride, NULL );
	//}

}

void ShaderSystem::SetUniformSampler(const Shader* shader, ShaderSamplers_t sampler, int index)
{
	glGetError();

	GLint uniformLocation = glGetUniformLocation(shader->m_program, g_samplersNameTable[sampler]);
	glUniform1i(uniformLocation, static_cast<GLint>(index));
}

void ShaderSystem::SetUniformFloat4(const Shader* shader, ShaderUniform_t uniform, const void* data)
{	
	GLint location = glGetUniformLocation( shader->m_program, g_uniformNameTable[ uniform ] );
	glUniform4fv( location, 1, ( const GLfloat* )data );
}

void ShaderSystem::SetUniformMatrix( const Shader* shader, ShaderUniform_t uniform, const void* data )
{
	GLint location = glGetUniformLocation( shader->m_program, g_uniformNameTable[ uniform ] );
	glUniformMatrix4fv( location, 1, GL_FALSE, ( const GLfloat* )data );
}

// #TODO: Move out

static const char* s_attributeNameTable[SHADERSEMANTIC_MAX] =
{
	"a_position",//SHADERSEMANTIC_POSITION,
	"a_color", //SHADERSEMANTIC_COLOR
	"a_texcoord",//SHADERSEMANTIC_TEXCOORD,
	"a_texcoord0", //SHADERSEMANTIC_TEXCOORD0,
	"a_texcoord1",//SHADERSEMANTIC_TEXCOORD1,
	"a_normal",//SHADERSEMANTIC_NORMAL,
	"a_tangent",//SHADERSEMANTIC_TANGENT,
	"a_bitangent"//SHADERSEMANTIC_BITANGENT,
};

void Shader::AllocateAttributes()
{
	// Allocate input layout
	for ( int i = 0; i < m_layout_count; i++ )
	{
		const InputLayoutDesc_t& layout = m_layouts[ i ];
	//	Msg( "Shader::AllocateAttributes: Bind %i as %s", i, s_attributeNameTable[ layout.semantic ] );
		glBindAttribLocation( m_program, i, s_attributeNameTable[ layout.semantic ] );

		GL_CHECK_ERROR();

		// m_glLayouts[ i ] = glGetAttribLocation( m_program, s_attributeNameTable[i] );
		// assert( m_glLayouts[ i ] );
	}
}
