/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "gpu/gpu_material_shader.h"
		#include "gpu/gpu_material.h"
		#include "gpu/gpu_renderer.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::GPU;
		using GS::NML::Tag;


//------------------------------------------------------------------------------
bool				MaterialShader::SetUserUniformValue(const char *name, const Vector4 &v)
{
	uint match_count = 0;
	for (uint n = 0; n < variants.GetCount(); ++n)
		if	(Shader *shader = variants[n].c_ptr())
			if	(ShaderInput *input = shader->GetInput(name))
			{
				input->parm_v = v;
				++match_count;
			}

	return match_count > 0;
}
bool				MaterialShader::SetUserUniformValue(const char *name, Render::Texture *t)
{
	uint match_count = 0;
	for (uint n = 0; n < variants.GetCount(); ++n)
		if	(Shader *shader = variants[n].c_ptr())
			if	(ShaderInput *input = shader->GetInput(name))
			{
				input->parm_t = t;
				++match_count;
			}

	return match_count > 0;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				MaterialShader::CompileForwardPointLight(Core::Shader &shader, bool shadow)
{
	shader.Define("_POINT_LIGHT", Core::ShaderInput::Pixel);
	CompileShaderSection("OutputForwardLightModel", shader);

	if	(shadow)
	{
		shader.Define("_CAST_SHADOW", Core::ShaderInput::Pixel);

		shader.DeclareInput("psm", Core::ShaderInput::DataTextureShadow, Core::ShaderInput::LightShadowMap0, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		for	(int n = 0; n < 6; ++n)
			shader.DeclareInput(String::Format("psm_%d_projection_matrix", n), Core::ShaderInput::Matrix4, Core::ShaderInput::Semantic(Core::ShaderInput::LightShadowMatrix0 + n), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_iss", Core::ShaderInput::Float, Core::ShaderInput::InverseShadowMapSize, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_lsb", Core::ShaderInput::Float, Core::ShaderInput::LightShadowBias, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_vtl", Core::ShaderInput::Matrix4, Core::ShaderInput::ViewToLightMatrix, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_noise", Core::ShaderInput::DataTexture2D, Core::ShaderInput::NoiseMap, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);

		if	(Tag *t = renderer.GetPCFShaderTag())
			shader.pixel_decl += t->GetString();
		if	(Tag *t = renderer.GetPSMShaderTag())
			shader.pixel.Replace("%psm_pcf_evaluation%", t->GetString());
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				MaterialShader::CompileForwardSpotLight(Core::Shader &shader, bool shadow, bool projection_map)
{
	shader.Define("_SPOT_LIGHT", Core::ShaderInput::Pixel);
	CompileShaderSection("OutputForwardLightModel", shader);

	if	(shadow)
	{
		shader.Define("_CAST_SHADOW", Core::ShaderInput::Pixel);

		shader.DeclareInput("u_ssm", Core::ShaderInput::DataTextureShadow, Core::ShaderInput::Semantic(Core::ShaderInput::LightShadowMap0), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_ssm_projection", Core::ShaderInput::Matrix4, Core::ShaderInput::Semantic(Core::ShaderInput::LightShadowMatrix0), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_iss", Core::ShaderInput::Float, Core::ShaderInput::InverseShadowMapSize, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_lsb", Core::ShaderInput::Float, Core::ShaderInput::LightShadowBias, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_noise", Core::ShaderInput::DataTexture2D, Core::ShaderInput::NoiseMap, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);

		if	(Tag *t = renderer.GetPCFShaderTag())
			shader.pixel_decl += t->GetString();
	}

	if	(projection_map)
	{
		shader.Define("_PROJECTION_MAP", Core::ShaderInput::Pixel);
		shader.DeclareInput("u_pjm", Core::ShaderInput::DataTexture2D, Core::ShaderInput::Semantic(Core::ShaderInput::LightProjectionMap), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_pjm_projection", Core::ShaderInput::Matrix4, Core::ShaderInput::Semantic(Core::ShaderInput::LightShadowMatrix0), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				MaterialShader::CompileForwardLinearLight(Core::Shader &shader, bool shadow)
{
	shader.Define("_LINEAR_LIGHT", Core::ShaderInput::Pixel);
	CompileShaderSection("OutputForwardLightModel", shader);

	if	(shadow)
	{
		shader.Define("_CAST_SHADOW", Core::ShaderInput::Pixel);

		shader.DeclareInput("pssm", Core::ShaderInput::DataTextureShadow, Core::ShaderInput::LightShadowMap0, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		for	(int n = 0; n < renderer.registry.GetInteger("ShadowMapping:PSSM:Split", 3); ++n)
		{
			shader.DeclareInput(String::Format("pssm_%d_slice_distance", n), Core::ShaderInput::Float, Core::ShaderInput::Semantic(Core::ShaderInput::LightPSSMSliceDistance0 + n), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
			shader.DeclareInput(String::Format("pssm_%d_projection_matrix", n), Core::ShaderInput::Matrix4, Core::ShaderInput::Semantic(Core::ShaderInput::LightShadowMatrix0 + n), Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		}
		shader.DeclareInput("u_noise", Core::ShaderInput::DataTexture2D, Core::ShaderInput::NoiseMap, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_iss", Core::ShaderInput::Float, Core::ShaderInput::InverseShadowMapSize, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.DeclareInput("u_lsb", Core::ShaderInput::Float, Core::ShaderInput::LightShadowBias, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);

		if	(Tag *t = renderer.GetPCFShaderTag())
			shader.pixel_decl += t->GetString();

		if	(Tag *t = renderer.GetPSSMShaderTag())
			shader.pixel.Replace("%pssm_pcf_evaluation%", t->GetString());
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				MaterialShader::CompileShaderSection(const char *id, Core::Shader &shader)
{
	Tag *shader_tag = renderer.GetMaterialDict().GetTag(id);
	if	(!shader_tag)
		__ERR__(__LOG_E__ << "Missing shader section '" << id << "'.\n", false)

	if	(Tag *tag = shader_tag->GetTag("Input;"))
		shader.ParseInputTag(tag);
	if	(Tag *tag = shader_tag->GetTag("Varying;"))
		shader.ParseVaryingTag(tag);
	if	(Tag *tag = shader_tag->GetTag("Vertex;"))
		shader.vertex += tag->GetString();
	if	(Tag *tag = shader_tag->GetTag("Fragment;"))
		shader.pixel += tag->GetString();

	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				MaterialShader::SetupMaterialProgram(Variant p, Core::Shader &shader)
{
	if	(parm.use_alpha_test)
	{
		shader.DeclareInput("u_alpha_threshold", Core::ShaderInput::Float, Core::ShaderInput::MaterialAlphaThreshold, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
		shader.pixel += "if (%opacity% < u_alpha_threshold) discard;\n";
	}

	if	(parm.use_depth_bias)
		shader.DeclareInput("u_depth_bias", Core::ShaderInput::Float, Core::ShaderInput::MaterialDepthBias, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);

	if	(parm.use_skinning)
	{
		shader.Define("_SKINNED", (Core::ShaderInput::Scope)(Core::ShaderInput::Vertex | Core::ShaderInput::Pixel));
		shader.DeclareInput("bone_mtx", Core::ShaderInput::Matrix4, Core::ShaderInput::BoneMatrix, Core::ShaderInput::Uniform, Core::ShaderInput::Vertex, __PL_BONE_LIMIT__);
		shader.DeclareInput("bone_idx", Core::ShaderInput::Vector4, Core::ShaderInput::BoneIndex, Core::ShaderInput::Attribute, Core::ShaderInput::Vertex);
		shader.DeclareInput("bone_w", Core::ShaderInput::Vector4, Core::ShaderInput::BoneWeight, Core::ShaderInput::Attribute, Core::ShaderInput::Vertex);
		shader.DeclareVarying("v_skin_mtx", "mat4");
	}

	CompileShaderSection("InputPosition;", shader);

	// Assemble program shader.
	switch (p)
	{
		case Depth:
			CompileShaderSection("OutputDepth;", shader);
			break;

		case FS_Constant:
			CompileShaderSection("OutputForwardConstant;", shader);
			break;

		case FS_PointLight:
		case FS_PointLightShadowMapping:
			CompileForwardPointLight(shader, p == FS_PointLightShadowMapping);
			break;

		case FS_SpotLight:
		case FS_SpotLightShadowMapping:
		case FS_SpotLightProjection:
		case FS_SpotLightProjectionShadowMapping:
			CompileForwardSpotLight(shader, (p == FS_SpotLightShadowMapping) || (p == FS_SpotLightProjectionShadowMapping), (p == FS_SpotLightProjection) || (p == FS_SpotLightProjectionShadowMapping));
			break;

		case FS_LinearLight:
		case FS_LinearLightShadowMapping:
			CompileForwardLinearLight(shader, p == FS_LinearLightShadowMapping);
			break;

		case DS_GBufferMRT4:
			CompileShaderSection("OutputDeferred;", shader);
			break;

		case PP_NormalDepth:
			CompileShaderSection("OutputNormalDepth;", shader);
			break;

		case PP_Velocity:
			if	(parm.use_skinning)
				shader.DeclareInput("previous_bone_mtx", Core::ShaderInput::Matrix4, Core::ShaderInput::PreviousBoneMatrix, Core::ShaderInput::Uniform, Core::ShaderInput::Vertex, __PL_BONE_LIMIT__);
			CompileShaderSection("OutputVelocity;", shader);
			break;

		case Last:
			break;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static	bool		FindVarAssignation(const String &src, const char *var)	// TODO fix with proper regex, this is proof of concept code at best...
{
	if	(src.FindString(String::Format("%s =", var)))
		return true;
	if	(src.FindString(String::Format("%s\t=", var)))
		return true;

	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static void			DeclareVertexPosition(Core::Shader &shader, bool declare_varying)
{
	String decl = "vec4 %position%;\n";

	if	(!asbool(FindVarAssignation(shader.vertex, "%position%")))
	{
		Core::ShaderInput *input = shader.DeclareInput("a_position", Core::ShaderInput::Vector3, Core::ShaderInput::Position, Core::ShaderInput::Attribute, Core::ShaderInput::Vertex);
		// Core::ShaderInput *input = shader.GetInput(Core::ShaderInput::Position);
		decl << "%position% = vec4(" << input->name << ", 1.0);\n";
	}
	shader.vertex = decl + shader.vertex;
}
static void			DeclareVertexNormal(Core::Shader &shader)
{
	String decl = "vec3 %normal%;\n";

	if	(!asbool(FindVarAssignation(shader.vertex, "%normal%")))
	{
		Core::ShaderInput *input = shader.DeclareInput("a_normal", Core::ShaderInput::Vector3, Core::ShaderInput::Normal, Core::ShaderInput::Attribute, Core::ShaderInput::Vertex);
		// Core::ShaderInput *input = shader.GetInput(Core::ShaderInput::Normal);
		decl << "%normal% = " << input->name << ";\n";
	}
	shader.vertex = decl + shader.vertex;
}
static	void		InitializeShader(Core::Shader &shader)
{
	// Position
	{
		bool pixel_consumes = asbool(shader.pixel.FindString("%in.position%"));
		DeclareVertexPosition(shader, pixel_consumes);
	}

	// Normal
	{
		bool pixel_consumes = asbool(shader.pixel.FindString("%in.normal%"));
		bool pixel_provides = FindVarAssignation(shader.pixel, "%normal%");

		if	(pixel_consumes || !pixel_provides)
			DeclareVertexNormal(shader);

		if	(!pixel_provides)
			shader.pixel << "%normal% = %in.normal%;\n";

		shader.pixel = String("vec3 %normal%;\n") + shader.pixel;
	}

	{
		struct ShaderDefault
		{
			const char *type, *name, *var;
			Core::ShaderInput::DataType data_type;
			Core::ShaderInput::Semantic semantic;
		};

		static ShaderDefault ps_ic[] =
		{
			{ "vec4", "%diffuse%", "_u_mat_diff", Core::ShaderInput::Vector4, Core::ShaderInput::MaterialDiffuse },
			{ "vec4", "%specular%", "_u_mat_spec", Core::ShaderInput::Vector4, Core::ShaderInput::MaterialSpecular },
			{ "float", "%glossiness%", "_u_mat_glos", Core::ShaderInput::Float, Core::ShaderInput::MaterialGlossiness },
			{ "vec4", "%constant%", "_u_mat_const", Core::ShaderInput::Vector4, Core::ShaderInput::MaterialSelf },
			{ "float", "%opacity%", "_u_mat_opac", Core::ShaderInput::Float, Core::ShaderInput::MaterialOpacity },
			{ NULL, NULL, NULL }
		};

		// Declare mandatory outputs and set default value for unprovided entries.
		String decl;
		for (uint n = 0; ps_ic[n].type; ++n)
		{
			decl << ps_ic[n].type << " " << ps_ic[n].name << ";\n";

			if	(!FindVarAssignation(shader.pixel, ps_ic[n].name))
			{
				Core::ShaderInput *input = shader.DeclareInput(ps_ic[n].var, ps_ic[n].data_type, ps_ic[n].semantic, Core::ShaderInput::Uniform, Core::ShaderInput::Pixel);
				decl << ps_ic[n].name << " = " << input->name << ";\n";
			}
		}
		shader.pixel = decl + shader.pixel;
	}
}
static void			FinalizeShader(Core::Shader &shader)
{
	if	(asbool(shader.pixel.FindString("%in.position%")))
	{
		shader.DeclareVarying("_v_position", "vec4");
		shader.vertex << "_v_position = %position%;\n";
	}
	if	(asbool(shader.pixel.FindString("%in.normal%")))
	{
		shader.DeclareVarying("_v_normal", "vec3");
		shader.vertex << "_v_normal = %normal%;\n";
	}

	shader.pixel.ReplaceAll("%in.position%", "_v_position", true);
	shader.pixel.ReplaceAll("%in.normal%", "_v_normal", true);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static const char	*GetShaderVariantName(MaterialShader::Variant v)
{
	switch (v)
	{
		case MaterialShader::Depth:									return "Depth";

		case MaterialShader::DS_GBufferMRT4:						return "DS_GBufferMRT4";
		case MaterialShader::FS_Constant:							return "FS_Constant";

		case MaterialShader::FS_PointLight:							return "FS_PointLight";
		case MaterialShader::FS_PointLightShadowMapping:			return "FS_PointLightShadowMapping";

		case MaterialShader::FS_LinearLight:						return "FS_LinearLight";
		case MaterialShader::FS_LinearLightShadowMapping:			return "FS_LinearLightShadowMapping";

		case MaterialShader::FS_SpotLight:							return "FS_SpotLight";
		case MaterialShader::FS_SpotLightShadowMapping:				return "FS_SpotLightShadowMapping";
		case MaterialShader::FS_SpotLightProjection:				return "FS_SpotLightProjection";
		case MaterialShader::FS_SpotLightProjectionShadowMapping:	return "FS_SpotLightProjectionShadowMapping";

		case MaterialShader::PP_NormalDepth:						return "PP_NormalDepth";
		case MaterialShader::PP_Velocity:							return "PP_Velocity";

		case MaterialShader::Last:
			break;
	}
	return "UnknownVariant";
}
bool				MaterialShader::Create(Render::ResourceFactory &rf, const Core::Shader &shader, const MaterialShaderStaticParm &static_parm)
{
	__LOG_V__ << "Creating material shader '" << shader.name << "' variants.\n";

	name = shader.name;
	parm = static_parm;

	// Build variants.
	if	(!variants.Allocate(Last))
		return false;

	for	(int n = 0; n < Last; ++n)
	{
		if	(renderer.render_technique == Renderer::TechniqueDeferred)
			if	((n >= FS_Constant) && (n <= FS_SpotLightProjectionShadowMapping))
				continue;	// no forward

		if	(renderer.render_technique == Renderer::TechniqueForward)
			if	(n == DS_GBufferMRT4)
				continue;	// no deferred

		if	(!renderer.gpu_config.enable_shadow)
			if	(
					(n == Depth) ||
					(n == FS_PointLightShadowMapping) ||
					(n == FS_LinearLightShadowMapping) ||
					(n == FS_SpotLightShadowMapping) ||
					(n == FS_SpotLightProjectionShadowMapping)
				)
				continue;	// no shadow-mapping

		if	(!renderer.gpu_config.use_rtt)
			if	(
					(n == PP_Velocity) ||
					(n == PP_NormalDepth)
				)
				continue;	// no post-processes

		if	(parm.no_lighting)
			if	((n > FS_Constant) && (n <= FS_SpotLightProjectionShadowMapping))	// keep constant!
				continue;	// no lighting pass

		// Specialize shader for this program.
		Core::Shader shader_variant;
		if	(!shader.Clone(shader_variant))
			return false;

		InitializeShader(shader_variant);
		if	(!SetupMaterialProgram((Variant)n, shader_variant))
			return false;
		FinalizeShader(shader_variant);

		shader_variant.name << "_" << GetShaderVariantName((MaterialShader::Variant)n);

		// Compile it.
		if	((variants[n] = (Shader *)renderer.NewShader()) != NULL)
			if	(!variants[n]->Create(rf, shader_variant))
				return false;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
MaterialShader::MaterialShader(Renderer &r) : renderer(r)
{	renderer.material_shaders.Add(this);	}
MaterialShader::~MaterialShader()
{	renderer.material_shaders.Remove(this);	}
//------------------------------------------------------------------------------
