//#define PROJECTED
//#define DIRLIGHT
//#define POSLIGHT
//#define SPOTLIGHT
//#define DIFFUSE
//#define SPECULAR
//#define SHADOW
//#define SMOOTHSHADOW
//#define SHADOWMAP

//#define FOGGING

//#define LightCount 2

#ifdef PROJECTED
#define tex2DLight tex2Dproj
#else 
#define tex2DLight tex2D
#endif


#if defined(DIRLIGHT) && defined(SPECULAR)
#define VERTEXDIR
#endif

#ifndef LightCount
#define LightCount 1
#endif

// Pixel data
sampler2D RenderTex;
sampler2D NormalTex;
sampler2D MaterialTex;
sampler2D LightTex;		// Optional

sampler1D ACosTex;		// Contains acos(x)/PI*0.5 [x=0...1]
sampler2D PowTex;		// Contains pow(x, y) [x=0...1, y=0...255]

// Camera data
float3 CamDir;
//float3 CamPos;

// Light data
struct dcLightData{
	float4 Data[5];
#ifdef SHADOW
#ifndef INSTANCED
	float4x4 Matrix;
	float4 OffsetData;
#endif
#endif
};

// Utility functions
#include "dcLineLine.HLSL"
#include "dcOutputPos.HLSL"
#include "dcCubeNormalize.HLSL"

float3 GetPos(dcLightData Light){
	return Light.Data[0].xyz;
}
float GetInnerAngle(dcLightData Light){
	return Light.Data[0].w;
}
	
float3 GetDir(dcLightData Light){
	return Light.Data[1].xyz;
}
float GetAngleInvRange(dcLightData Light){
	return Light.Data[1].w;
}
	
float3 GetDiffuse(dcLightData Light){
	return Light.Data[2].xyz;
}
float GetNear(dcLightData Light){
	return Light.Data[2].w;
}
	
float3 GetSpecular(dcLightData Light){
	return Light.Data[3].xyz;
}
float GetInvRange(dcLightData Light){
	return Light.Data[3].w;
}

float GetFogging(dcLightData Light){
	return Light.Data[4].x;
}

float GetFogFalloff(dcLightData Light){
	return Light.Data[4].y;
}


float4x4 GetMatrix(dcLightData Light){
#ifdef SHADOW
#ifndef INSTANCED
	return Light.Matrix;
#else
	return 0;
#endif
#endif
}

float2 GetOffsetSize(dcLightData Light){
#ifdef SHADOW
#ifndef INSTANCED
	return Light.OffsetData.xy;
#else
	return 0;
#endif
#endif
}

float2 GetInvOffsetSize(dcLightData Light){
#ifdef SHADOW
#ifndef INSTANCED
	return Light.OffsetData.zw;
#else
	return 0;
#endif
#endif
}

float GetOuterAngle(dcLightData Light){
	float Angle = GetInnerAngle(Light);
	if (GetAngleInvRange(Light) != 0){
		Angle += (1.0f / GetAngleInvRange(Light));
	}
	return Angle;
}

float GetFar(dcLightData Light){
	float Range = GetNear(Light);
	if (GetInvRange(Light) != 0){
		Range += (1.0f / GetInvRange(Light));
	}
	return Range;
}

#ifndef INSTANCED
dcLightData Lights[LightCount];
#endif

#if defined(SPOTLIGHT) || defined(DIRLIGHT)
sampler2D Shadow;
#else
samplerCUBE Shadow;
#endif

sampler2D XYFalloff;
sampler1D ZFalloff;

// Pixel input
struct PS_INPUT{
#ifdef PROJECTED
	float4 Tex				: TEXCOORD0;
#else
	float2 Tex				: TEXCOORD0;
#endif

	float3 ViewDir			: TEXCOORD1;
	
#ifdef INSTANCED
	dcLightData Light		: TEXCOORD2;
#endif
};

// Pixel output
struct PS_OUTPUT{
	float4 Color			: COLOR;
};

// Functions
//static float OffsetSize = 512;
//static float InvOffsetSize = 1.0f / 512;

#if defined(SPOTLIGHT) || defined(DIRLIGHT)
float ComputeShadow(float3 Pos, dcLightData Light){
#ifdef SHADOW
	float4 LightProj = mul(float4(Pos, 1), GetMatrix(Light));
	
	float Depth1 = LightProj.z;//length(GetPos(Light) - Pos);//LightProj.z;
#ifdef SMOOTHSHADOW
#ifndef SHADOWMAP
	float2 ShadowCoords = LightProj.xy;// / LightProj.w;
	
	float2 Floor = floor(ShadowCoords * GetOffsetSize(Light)) * GetInvOffsetSize(Light);

	float2 ShadowPos[4];
	ShadowPos[0] = float2(Floor.x, Floor.y);
	ShadowPos[1] = float2(Floor.x + GetInvOffsetSize(Light).x, Floor.y);
	ShadowPos[2] = float2(Floor.x, Floor.y + GetInvOffsetSize(Light).y);
	ShadowPos[3] = float2(Floor.x + GetInvOffsetSize(Light).x, Floor.y + GetInvOffsetSize(Light).y);
				
	float4 Depth2;
	Depth2[0] = tex2D(Shadow, ShadowPos[0]).x;
	Depth2[1] = tex2D(Shadow, ShadowPos[1]).x;
	Depth2[2] = tex2D(Shadow, ShadowPos[2]).x;
	Depth2[3] = tex2D(Shadow, ShadowPos[3]).x;
				
	float4 Data = step(Depth1, Depth2);
	
	float2 Fraction = clamp((ShadowCoords - Floor) * GetOffsetSize(Light), 0, 1);
	float2 OneMinus = 1.0f - Fraction;
								
	float b1 = OneMinus.x * Data[0] + Fraction.x * Data[1];
	float b2 = OneMinus.x * Data[2] + Fraction.x * Data[3];
				
	float ShadowColor = OneMinus.y * b1 + Fraction.y * b2;
	return ShadowColor;
#else
	return tex2Dproj(Shadow, LightProj);
#endif
#else
	float Depth2 = tex2Dproj(Shadow, LightProj);
	
	return Depth1 < Depth2;
#endif
#else
	return 1;
#endif
}
#else
float ComputeShadow(float3 Pos, dcLightData Light){
#ifdef SHADOW
	float4 LightProj = mul(float4(Pos, 1), GetMatrix(Light));
	
	float Depth1 = LightProj.z;
	float Depth2 = texCUBE(Shadow, Dir);

	return Depth2 > Depth1;
#else
	return 1;
#endif
}
#endif

float ComputeAttenuation(float3 Pos, dcLightData Light){
	float3 LightDiff = GetPos(Light) - Pos;
	float LightDist = Length(LightDiff);
	
	float RangeFalloff = 1.0f - clamp((LightDist - GetNear(Light)) * GetInvRange(Light), 0, 1);
	
#ifdef SPOTLIGHT
	float ShadowFalloff = ComputeShadow(Pos, Light);
	
	float ConeFalloff = dot(GetDir(Light), Normalize(LightDiff));

#if 0
	float Angle = acos(ConeFalloff);
#else
	float Angle = tex1D(ACosTex, ConeFalloff) * 1.5707963267948966192313216916398f;
#endif
	float AngleFalloff = 1.0f - clamp((Angle - GetInnerAngle(Light)) * GetAngleInvRange(Light), 0, 1);
	
	float Attenuation = AngleFalloff * RangeFalloff * ShadowFalloff;
#else
	float ShadowFalloff = ComputeShadow(Pos, Light);

	float Attenuation = RangeFalloff * ShadowFalloff;
#endif
	
	return Attenuation;
}

PS_OUTPUT main(const PS_INPUT Input){
	float3 Diffuse = 0;
	float3 Specular = 0;

	for (int i = 0; i < LightCount; i++){
#ifdef INSTANCED
		dcLightData Light = Input.Light;
#else
		dcLightData Light = Lights[i];
#endif
		// Other
		
		
		float4 NormalData = tex2DLight(NormalTex, Input.Tex);
		
		float3 Normal = UnpackNormal(NormalData.xyz);
		float3 Pos = UnpackPos(NormalData.w, Input.ViewDir);
		
		float SpecularPower = 32;
		
		float4 Material = tex2DLight(MaterialTex, Input.Tex);
	
#ifdef POSLIGHT
		// Compute direction
		float3 LightDir = Normalize(GetPos(Light) - Pos);
		
		// Compute attenuation
		float Attenuation = ComputeAttenuation(Pos, Light);
#else
		float3 LightDir = GetDir(Light);
		
		float Attenuation = ComputeShadow(Pos, Light);
#endif

		// Compute diffuse
#ifdef DIFFUSE
		float Dot = clamp(dot(LightDir, Normal) * GetFogFalloff(Light), 0, 1);
		Diffuse += GetDiffuse(Light) * Dot * Attenuation;
#endif
		
	// Compute Specular
#ifdef SPECULAR
		float3 N = Normal;
		float3 L = LightDir;
#ifndef VERTEXDIR
		float3 V = Normalize(CamPos - Pos);
#else
		float3 V = Normalize(Input.ViewDir);
#endif
		
		float3 R = 2 * dot(N, L) * N - L;
		float Shininess = clamp(dot(R, V) * GetFogFalloff(Light), 0, 1);
		
#ifndef PS14
		Specular += clamp(GetSpecular(Light) * pow(Shininess, SpecularPower) * Material.xyz * Attenuation, 0, 1);
#else
		Specular += clamp(GetSpecular(Light) * tex2D(PowTex, float2(Shininess, SpecularPower / 255)) * Material.xyz * Attenuation, 0, 1);
#endif

#endif
	
#ifdef FOGGING
		// Fogging
		// For the fogging we find the Z for the position which the ray coming from the camera is closest to the light origin
		// After we have found the position for this point we attenuate it with the usual function

#ifdef POSLIGHT
		float3 Origin = CamPos;
		float3 Dir = Pos - Origin;
#ifdef SPOTLIGHT
		// Spotlight fogging

		float3 ConeOrigin = GetPos(Light);
		float3 ConeDir = -GetDir(Light);
		float ConeLength = GetFar(Light);
		
		ConeOrigin -= ConeDir;
		ConeLength -= 1;
		
		float3 Closest[2];
		float T[2];
		
		float DistSq = LineLine(ConeOrigin, ConeDir, ConeLength, Origin, Normalize(Dir), length(Dir), Closest, T);
		
		float3 FogPos = Closest[1];

#else	//SPOTLIGHT

		// Omni light fogging
		float T;
		LineVertexDist(GetPos(Light), Origin, Dir, T);
			
		float3 FogPos = Origin + Dir * T;	
#endif	//SPOTLIGHT

		// Positional light fogging (Spot&Omni)
		float3 FogAttenuation = ComputeAttenuation(FogPos, Light);
#else	//POSLIGHT

		// Directional light fogging
		float3 FogAttenuation = clamp(dot(LightDir, -CamDir), 0, 1);
#endif	//POSLIGHT

		// Fogging
		float3 Fogging = GetDiffuse(Light) * FogAttenuation * GetFogging(Light);// * GetFogFalloff(;
#ifndef SPECULAR
#define SPECULAR
		float3 Specular = Fogging;
#else
		Specular += Fogging;
#endif
#endif	//FOGGING
	}
	
	float4 Render = tex2DLight(RenderTex, Input.Tex);
	
	float4 Result = float4(((Diffuse * Render.rgb) + Specular), Render.a);

#ifdef LIGHTTEX
	float4 Light = tex2DLight(LightTex, Input.Tex);
	Result *= Light;
#endif
	
	PS_OUTPUT Out;
	Out.Color = Result;
	return Out;
}