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


		#include "core/shader_isl_to_hlsl.h"
		#include "core/shader.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
String	RewriteTextureSampling(const char *source, StringList &arg)
{
	// ISL arg0: texture object variable.
	// ISL arg1: UV variable.
	return String::Format("%s_res.Sample(%s, %s)", arg[0].c_str(), arg[0].c_str(), arg[1].c_str());
}
String	RewriteShadowSampling(const char *source, StringList &arg)
{
	// ISL arg0: texture object variable.
	// ISL arg1: UV variable.
	return String::Format("%s_res.SampleCmpLevelZero(%s, (%s).xy, (%s).z * 2.0 - 1.0)", arg[0].c_str(), arg[0].c_str(), arg[1].c_str(), arg[1].c_str());
}
String	RewritePCFCall(const char *source, StringList &arg)
{
	// Make sure a call to ComputePCF is submitted and not the function definition (which already has 5 parameters by now).
	if	(arg[3].IndexOf(',') != -1)
		return source;
	return String::Format("ComputePCF(%s, %s, %s, %s_res, %s)", arg[0].c_str(), arg[1].c_str(), arg[2].c_str(), arg[2].c_str(), arg[3].c_str());
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
String				ISLtoHLSL::GetCategoryCBufferName(ShaderInput::Category c)
{
	return String::Format("CBuffer%s", ShaderInput::GetCategoryName(c));
}
bool				ISLtoHLSL::GetType(ShaderInput::DataType type, String &decl)
{
	switch (type)
	{
		case ShaderInput::Float:	decl = "float";		return true;
		case ShaderInput::Vector2:	decl = "vec2";		return true;
		case ShaderInput::Vector3:	decl = "vec3";		return true;
		case ShaderInput::Vector4:	decl = "vec4";		return true;
		case ShaderInput::Matrix3:	decl = "mat3";		return true;
		case ShaderInput::Matrix4:	decl = "mat4";		return true;
	}
	return false;
}
static void			BuildInputLocalDecl(const ShaderInput * input, String &local_decl)
{
	String type_decl;
	switch (input->data_type)
	{
		case ShaderInput::DataTexture2D:
		case ShaderInput::DataTextureCube:
			local_decl = String::Format("SamplerState %s;\nTexture2D %s_res;\n", input->name.c_str(), input->name.c_str());
			break;

		case ShaderInput::DataTextureShadow:
			local_decl = String::Format("SamplerComparisonState %s;\nTexture2D %s_res;\n", input->name.c_str(), input->name.c_str());
			break;

		default:
			if	(ISLtoHLSL::GetType(input->data_type, type_decl))
			{
				String input_decl = input->array_size > 1 ? String::Format("%s[%d]", input->name.c_str(), input->array_size) : input->name;
				local_decl = String::Format("%s %s;\n", type_decl.c_str(), input_decl.c_str());
			}
			break;
	}
}
static void			DeclareInputs(const Shader &shader, String &vertex_decl, String &pixel_decl)
{
	// Declare constants with one cbuffer per category.
	String local_decl;

	for	(uint c = 0; c < ShaderInput::CategoryLast; ++c)
	{
		bool vertex_constant_scope_open = false,
			 pixel_constant_scope_open = false;

		ListForeachPtr(ShaderInput *, input, shader.input_list)
		{
			if	(input->type != ShaderInput::Uniform)
				continue;
			if	(ShaderInput::semantic_desc[input->semantic].category != (ShaderInput::Category)c)
				continue;	// filter on category

			BuildInputLocalDecl(input, local_decl);

			if	(input->scope & ShaderInput::Vertex)
			{
				if	(!vertex_constant_scope_open && !input->ConsumesTextureUnit())
				{
					vertex_decl += String::Format("\ncbuffer CBuffer%s\n{\n", ShaderInput::GetCategoryName((ShaderInput::Category)c));
					vertex_constant_scope_open = true;
				}
				vertex_decl += local_decl;
			}
			if	(input->scope & ShaderInput::Pixel)
			{
				if	(!pixel_constant_scope_open && !input->ConsumesTextureUnit())
				{
					pixel_decl += String::Format("\ncbuffer CBuffer%s\n{\n", ShaderInput::GetCategoryName((ShaderInput::Category)c));
					pixel_constant_scope_open = true;
				}
				pixel_decl += local_decl;
			}
		}

		// Close cbuffer scope.
		if	(vertex_constant_scope_open)
			vertex_decl += "};\n";
		if	(pixel_constant_scope_open)
			pixel_decl += "};\n";
	}
}
bool				ISLtoHLSL::Translate(const Shader &shader, String &hlsl_vertex, String &hlsl_pixel)
{
	hlsl_vertex = shader.vertex;
	hlsl_pixel = shader.pixel;

	// Vertex/pixel structures.
	String	v_in_struct = "struct VertexInput\n{\n",
			p_in_struct = "struct PixelInput\n{\n",
			type_decl;

	p_in_struct += "    vec4 position : SV_Position;\n";

	ListForeachPtr(ShaderInput *, input, shader.input_list)
		if	((input->type == ShaderInput::Attribute) && GetType(input->data_type, type_decl))
		{
			String	input_decl = input->array_size > 1 ? String::Format("%s[%d]", input->name.c_str(), input->array_size) : input->name;
			String local_decl = String::Format("    %s %s : %s;\n", type_decl.c_str(), input_decl.c_str(), ShaderInput::semantic_desc[input->semantic].name);

			if	(input->scope & ShaderInput::Vertex)
				v_in_struct += local_decl;
			if	(input->scope & ShaderInput::Pixel)
				p_in_struct += local_decl;

			hlsl_vertex.ReplaceAll(input->name, String::Format("IN.%s", input->name.c_str()), true);
		}

	int	i_interpolator = 0;
	ListForeachPtr(ShaderVarying *, varying, shader.varying_list)
	{
		p_in_struct += String::Format("    %s %s : Varying%d;\n", varying->type.c_str(), varying->name.c_str(), i_interpolator);

		// Reserve enough registers for the varying type.
				if	(varying->type == "mat4")
			i_interpolator += 4;
		else	if	(varying->type == "mat3")
			i_interpolator += 3;
		else
			i_interpolator++;

		hlsl_vertex.ReplaceAll(varying->name, String::Format("OUT.%s", varying->name.c_str()), true);
		hlsl_pixel.ReplaceAll(varying->name, String::Format("IN.%s", varying->name.c_str()), true);
	}

	v_in_struct += "};\n\n";
	p_in_struct += "};\n\n";

	// Pixel output.
	String p_out_struct = "struct PixelOutput\n{\n";
	if	(hlsl_pixel.Contains("%out.color%"))	p_out_struct += "    vec4 color : SV_Target;\n";
	if	(hlsl_pixel.Contains("%out.color0%"))	p_out_struct += "    vec4 color0 : SV_Target1;\n";
	if	(hlsl_pixel.Contains("%out.color1%"))	p_out_struct += "    vec4 color1 : SV_Target2;\n";
	if	(hlsl_pixel.Contains("%out.color2%"))	p_out_struct += "    vec4 color2 : SV_Target3;\n";
	if	(hlsl_pixel.Contains("%out.color3%"))	p_out_struct += "    vec4 color3 : SV_Target4;\n";
	if	(hlsl_pixel.Contains("%out.depth%"))	p_out_struct += "    float depth : SV_Depth;\n";
	p_out_struct += "};\n\n";

	//
	String vertex_decl, pixel_decl;

	String header = String::Format("// GSFramework ISL to HLSL converter.\n// File: '%s'\n\n", shader.name.c_str());
	vertex_decl += header;
	pixel_decl += header;

	vertex_decl += v_in_struct;
	vertex_decl += p_in_struct;
	pixel_decl += p_in_struct;
	pixel_decl += p_out_struct;

	// Translation helper.
	vertex_decl += "\n#define mix lerp\n";
	pixel_decl += "\n#define mix lerp\n";

	vertex_decl += "\n#define n_mtx_mul mul\n";
	pixel_decl += "\n#define n_mtx_mul mul\n";

	String m4tom3 = "\nfloat3x3 _mat4_to_mat3(const float4x4 m) { return (float3x3)m; }\n";
	vertex_decl += m4tom3;
	pixel_decl += m4tom3;

	String buildm3 = "\nfloat3x3 _build_mat3(const float3 a, const float3 b, const float3 c) { return float3x3(a.x, b.x, c.x, a.y, b.y, c.y, a.z, b.z, c.z); }\n";
	vertex_decl += buildm3;
	pixel_decl += buildm3;

	// Declare uniforms.
	DeclareInputs(shader, vertex_decl, pixel_decl);

	// Create function declaration.
	if	(!hlsl_vertex.Replace("%main%", "void main(in VertexInput IN, out PixelInput OUT)"))
		hlsl_vertex = String("void main(in VertexInput IN, out PixelInput OUT)\n{\n") + hlsl_vertex + "}";
	if	(!hlsl_pixel.Replace("%main%", "void main(in PixelInput IN, out PixelOutput OUT)"))
		hlsl_pixel = String("void main(in PixelInput IN, out PixelOutput OUT)\n{\n") + hlsl_pixel + "}";

	// Assemble final source.
	hlsl_vertex = vertex_decl + shader.vertex_decl + hlsl_vertex;
	hlsl_pixel = pixel_decl + shader.pixel_decl + hlsl_pixel;

	// Convert shadow sampling.
	hlsl_pixel.Replace("ComputePCF(vec3 fvp, mat4 pjm, sampler2DShadow tsampler, float k)", "ComputePCF(float3 fvp, float4x4 pjm, SamplerComparisonState tsampler, Texture2D tsampler_res, float k)", String::CaseSensitive);
	hlsl_pixel = String::RewritePatternAll(hlsl_pixel, "ComputePCF(%,%,%,%)", RewritePCFCall);

	// Convert texture sampling.
	hlsl_vertex = String::RewritePatternAll(hlsl_vertex, "texture2D(%,%)", RewriteTextureSampling);
	hlsl_pixel = String::RewritePatternAll(hlsl_pixel, "texture2D(%,%)", RewriteTextureSampling);
	hlsl_vertex = String::RewritePatternAll(hlsl_vertex, "shadow2D(%,%)", RewriteShadowSampling);
	hlsl_pixel = String::RewritePatternAll(hlsl_pixel, "shadow2D(%,%)", RewriteShadowSampling);
	hlsl_vertex = String::RewritePatternAll(hlsl_vertex, "textureCube(%,%)", RewriteTextureSampling);
	hlsl_pixel = String::RewritePatternAll(hlsl_pixel, "textureCube(%,%)", RewriteTextureSampling);

	// Convert symbols.
	{
		static const char *isl_symbol[] = { "%out.position%", NULL };
		static const char *hsl_symbol[] = { "OUT.position", NULL };
		hlsl_vertex.ReplaceAll(isl_symbol, hsl_symbol, true);
	}

	{
		static const char *isl_symbol[] = { "%in.fragcoord%",       "%out.color%", "%out.color0%", "%out.color1%", "%out.color2%", "%out.color3%", "%out.depth%", NULL };
		static const char *hsl_symbol[] = { "IN.position", "OUT.color",   "OUT.color0",   "OUT.color1",   "OUT.color2",   "OUT.color3",   "OUT.depth",   NULL };
		hlsl_pixel.ReplaceAll(isl_symbol, hsl_symbol, true);
	}

	//
	{
		static const char *outputs[] =	{	"%position%", "%normal%", "%diffuse%", "%specular%", "%glossiness%", "%constant%", "%opacity%", NULL	};
		static const char *out_vars[] =	{	"_o_vertex", "_o_normal", "_o_diffuse", "_o_specular", "_o_glossiness", "_o_constant", "_o_opacity", NULL	};
		hlsl_vertex.ReplaceAll(outputs, out_vars, true);
		hlsl_pixel.ReplaceAll(outputs, out_vars, true);
	}

	{
		static const char *isl_symbol[] = { "vec2",   "vec3",   "vec4",   "mat3",     "mat4",     "sampler2DShadow", NULL };
		static const char *hsl_symbol[] = { "float2", "float3", "float4", "float3x3", "float4x4", "SamplerComparisonState",	NULL };
		hlsl_vertex.ReplaceAll(isl_symbol, hsl_symbol, true);
		hlsl_pixel.ReplaceAll(isl_symbol, hsl_symbol, true);
	}

	// [EJ] PIX won't display sources correctly unless they use Windows eol markers.
	hlsl_vertex.NormalizeEOL(String::EOLWindows);
	hlsl_pixel.NormalizeEOL(String::EOLWindows);
	return true;
}
//------------------------------------------------------------------------------
