#include "world.inc"
#include "material.inc"
#include "texture.inc."
#include "light.inc"
#if OIT
#include "oit.inc"
#endif

#ifndef ALPHAMAP
#define ALPHAMAP TEXTURE
#endif
#ifndef USE_TANGENT
#define USE_TANGENT NORMALMAP
#endif

#if TEXTURE
Texture2D ColorMap : register( t0 );
SamplerState ColorMapSampler : register( s0 );
#endif
#if ALPHAMAP
Texture2D AlphaMap : register( t1 );
SamplerState AlphaMapSampler : register( s1 );
#endif
#if NORMALMAP
Texture2D NormalMap : register( t2 );
SamplerState NormalMapSampler : register( s2 );
#endif
#if SHADOW
Texture2D ShadowMap : register( t3 );
SamplerState ShadowMapSampler : register( s3 );
#endif
#if ENVMAP
Texture2D EnvMap : register( t4 );
SamplerState EnvMapSampler : register( s4 );
Texture2D EnvDiffuseMap : register( t5 );
SamplerState EnvDiffuseMapSampler : register( s5 );
Texture2D EnvSpecularMap : register( t6 );
SamplerState EnvSpecularMapSampler : register( s6 );
#endif

struct VS_INPUT
{
	float4 Pos : POSITION;
	float3 Normal : NORMAL;
#if 1 //always //TEXTURE || ALPHAMAP || USE_TANGENT
	float2 TexCoord : TEXCOORD0;
#endif
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#if VERTEXCOLOR
	float4 Col : COLOR0;
#endif
#if USE_TANGENT
	float4 Tangent : TANGENT;
#endif
};

struct VS_OUTPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	float2 TexCoord : TEXCOORD0;
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#endif
#if USE_TANGENT
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD4;
#if SHADOW
	float4 SMPos : TEXCOORD5;
#endif
};

struct PS_INPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	float2 TexCoord : TEXCOORD0;
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#endif
#if USE_TANGENT
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD4;
#if SHADOW
	float4 SMPos : TEXCOORD5;
#endif
	bool IsBack : SV_IsFrontFace; // Why inverted?
#if MSAA
    uint Coverage : SV_COVERAGE;
#endif
};

struct PS_OUTPUT
{
	float4 Color    : SV_Target0;
#if RENDER_NORMAL
	float4 Normal   : SV_Target1;
#endif
};

#include "oit_store.inc"


static const float PI = 3.141592653589793;

float2 dirToUV(float3 dir)
{
	return float2(
		atan2(dir.z, dir.x) / (PI*2),
		acos(dir.y) / PI);
}

float2 envmapUV(float3 dir)
{
	return dirToUV(dir) + float2(EnvMapRotate, 0.0);
}

// Vertex shader
VS_OUTPUT VS(const VS_INPUT In)
{
	VS_OUTPUT Out;
	Out.Pos = mul(In.Pos, WorldViewProj);
#if VERTEXCOLOR
	Out.Col.xyz = In.Col.xyz;	//RGB
	Out.Col.w = BaseColor.w * In.Col.w;
#else
	Out.Col = BaseColor;
#endif
	Out.Normal = In.Normal;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	Out.TexCoord = In.TexCoord;
#if MULTIUV >= 1
	Out.TexCoord1 = In.TexCoord1;
#endif
#if MULTIUV >= 2
	Out.TexCoord2 = In.TexCoord2;
#endif
#if MULTIUV >= 3
	Out.TexCoord3 = In.TexCoord3;
#endif
#endif
#if USE_TANGENT
	Out.Tangent = In.Tangent.xyz;
	Out.Binormal = normalize(cross(In.Tangent.xyz, In.Normal)) * In.Tangent.w;
#endif
	Out.WorldPos = In.Pos;
#if SHADOW
	Out.SMPos = mul(In.Pos, ShadowMapProj);
#endif
	return Out;
}


// Geometry shader
//   Convert LineAdjacent (with 4 vertices) to TriangleStrip(with 3 or 4 vertices).
[maxvertexcount(4)]
void GSpatch(lineadj VS_OUTPUT input[4], inout TriangleStream<VS_OUTPUT> stream)
{
	VS_OUTPUT output;

	output.Pos = input[1].Pos;
	output.Col = input[1].Col;
	output.Normal = input[1].Normal;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	output.TexCoord = input[1].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[1].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[1].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[1].TexCoord3;
#endif
#endif
#if USE_TANGENT
	output.Tangent = input[1].Tangent;
	output.Binormal = input[1].Binormal;
#endif
	output.WorldPos = input[1].WorldPos;
#if SHADOW
	output.SMPos = input[1].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[0].Pos;
	output.Col = input[0].Col;
	output.Normal = input[0].Normal;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	output.TexCoord = input[0].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[0].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[0].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[0].TexCoord3;
#endif
#endif
#if USE_TANGENT
	output.Tangent = input[0].Tangent;
	output.Binormal = input[0].Binormal;
#endif
	output.WorldPos = input[0].WorldPos;
#if SHADOW
	output.SMPos = input[0].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[2].Pos;
	output.Col = input[2].Col;
	output.Normal = input[2].Normal;
#if TEXTURE || ALPHAMAP || USE_TANGENT
	output.TexCoord = input[2].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[2].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[2].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[2].TexCoord3;
#endif
#endif
#if USE_TANGENT
	output.Tangent = input[2].Tangent;
	output.Binormal = input[2].Binormal;
#endif
	output.WorldPos = input[2].WorldPos;
#if SHADOW
	output.SMPos = input[2].SMPos;
#endif
	stream.Append(output);

	if(length(input[3].Normal) > 0.0){
		output.Pos = input[3].Pos;
		output.Col = input[3].Col;
		output.Normal = input[3].Normal;
#if TEXTURE || ALPHAMAP || USE_TANGENT
		output.TexCoord = input[3].TexCoord;
#if MULTIUV >= 1
		output.TexCoord1 = input[3].TexCoord1;
#endif
#if MULTIUV >= 2
		output.TexCoord2 = input[3].TexCoord2;
#endif
#if MULTIUV >= 3
		output.TexCoord3 = input[3].TexCoord3;
#endif
#endif
#if USE_TANGENT
		output.Tangent = input[3].Tangent;
		output.Binormal = input[3].Binormal;
#endif
		output.WorldPos = input[3].WorldPos;
#if SHADOW
		output.SMPos = input[3].SMPos;
#endif
		stream.Append(output);
	}

	stream.RestartStrip();
}

#if TEXTURE || ALPHAMAP || USE_TANGENT
float2 GetTexCoord(PS_INPUT In, int channel)
{
#if MULTIUV >= 1
	if((channel & 0x30) == 0x10) return In.TexCoord1;
#endif
#if MULTIUV >= 2
	if((channel & 0x30) == 0x20) return In.TexCoord2;
#endif
#if MULTIUV >= 3
	if((channel & 0x30) == 0x30) return In.TexCoord3;
#endif
	return In.TexCoord;
}
#endif

float3 GetNormal(PS_INPUT In)
{
#if NORMALMAP
	float3 normal_col = NormalMap.Sample(NormalMapSampler, GetTexCoord(In,HasBumpMap)).xyz * 2.0f - 1.0f;
	normal_col *= NormalMapFlip;

	float3x3 mtx = {In.Tangent, In.Binormal, In.Normal};
	return normalize(mul(normal_col, mtx));
#else
	return normalize(In.Normal);
#endif
}

float GetDiffuse(PS_INPUT In, float3 nv, float3 light_dir)
{
#if PHONG || BLINN
	float dif = dot(nv, light_dir);
	return saturate(dif);
#else
	return 0.0;
#endif
}

float GetShadow(PS_INPUT In)
{
#if (PHONG || BLINN) && SHADOW
	float2 smt = float2((In.SMPos.x+1)*0.5, 1.0-(In.SMPos.y+1)*0.5);
	float smz = ShadowMap.Sample(ShadowMapSampler, smt.xy).r;
	float sm_coef = (In.SMPos.z < smz+0.005) ? 1.0 : 0.5;
	return sm_coef;
#else
	return 1.0;
#endif
}

float GetSpecular(PS_INPUT In, float3 V, float3 nv, float3 light_dir)
{
#if PHONG
	float3 Reflect = normalize(2 * dot(nv, light_dir) * nv - light_dir);
	return pow(saturate(dot(Reflect, V)), SpecularPower);
#elif BLINN
	float3 Reflect = normalize(V + light_dir);
	return pow(saturate(dot(Reflect, nv)), SpecularPower);
#else
	return 0.0;
#endif
}

float4 GetBaseColor(PS_INPUT In)
{
	float4 col = In.Col;
#if TEXTURE
	float4 tex_col = LINEARtoSRGB(ColorMap.Sample(ColorMapSampler, GetTexCoord(In,HasTextureMap)), (HasTextureMap&3)==2);
	col *= tex_col;
#endif
#if ALPHAMAP
	float4 alpha_col = AlphaMap.Sample(AlphaMapSampler, GetTexCoord(In,HasAlphaMap));
	col.a *= alpha_col.a;
#endif

	// Cancel to write if the color is almost transparent.
	clip(col.a - 1.0/255.0);
	return col;
}

// Pixel shader
#if OIT
[earlydepthstencil]
#endif
PS_OUTPUT PS(PS_INPUT In)
{
	PS_OUTPUT output;

	if(IsArbClipped(In.WorldPos.xyz))
		discard;

	float3 V = normalize(CameraPos - In.WorldPos.xyz);

	// Calculate a normal vector of the face.
	float3 nv = GetNormal(In);
	if(In.IsBack){
		nv = -nv;
		In.Col.xyz *= BackFaceColor.xyz;
	}

	// Lighting
	float4 col = GetBaseColor(In);
#if MULTILIGHT
	float3 dif = float3(0,0,0);
	float3 spc = float3(0,0,0);
	for(int i=0; i<LIGHT_MAX && i<LightNum; i++){
		float3 light_dir;
		if(LightPos[i].w == 0){
			light_dir = normalize(LightPos[i].xyz - In.WorldPos.xyz); // point light
		}else{
			light_dir = LightPos[i].xyz; // directional light
		}
		dif += LightCol[i] * GetDiffuse(In, nv, light_dir);
		spc += LightSpc[i] * GetSpecular(In, V, nv, light_dir);
	}
#else
	float dif = GetDiffuse(In, nv, LightDir);
	float spc = GetSpecular(In, V, nv, LightDir);
#endif
#if SHADOW
	dif *= GetShadow(In);
#endif
	col.xyz = col.xyz * (Emissive.xyz + Diffuse.xyz * dif) + Specular.xyz * spc;

#if ENVMAP
	col.xyz += Ambient.xyz * LINEARtoSRGB(EnvDiffuseMap.Sample(EnvDiffuseMapSampler, envmapUV(nv)), HasEnvMap == 2).xyz;
#else
	col.xyz += Ambient.xyz * GlobalAmbient.xyz;
#endif

#if ENVMAP
	if(Reflect != 0){
		float3 refv = -normalize(reflect(V, nv));
		float3 envcol = LINEARtoSRGB(EnvMap.Sample(EnvMapSampler, envmapUV(refv)), HasEnvMap == 2).xyz;
		col.xyz = lerp(col.xyz, envcol, Reflect);
	}
#endif

	output.Color = saturate(col);

#if OIT
    StoreOIT(In, output.Color);
    output.Color = float4(0,0,0,0);	// This does not affect anything because RenderTargetWriteMask is 0.
#endif

#if RENDER_NORMAL
	output.Normal = float4(mul(In.Normal.xyz, (float3x3)WorldView), 1);
#endif

	return output;
}

