#ifndef ALLIN13DSHADER_HELPER_BIRP_INCLUDED
#define ALLIN13DSHADER_HELPER_BIRP_INCLUDED

#include "AutoLight.cginc"

#define OBJECT_TO_WORLD_MATRIX unity_ObjectToWorld

//#if !defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
//	#if defined(SHADOWS_SHADOWMASK) && !defined(UNITY_NO_SCREENSPACE_SHADOWS)
//		#define ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS 1
//	#endif
//#endif

#ifdef REQUIRE_SCENE_DEPTH

	UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

	float GetRawDepth(float4 projPos)
	{
		//float res = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(projPos));
		float res = tex2Dproj(_CameraDepthTexture, projPos);
		return res;
	}

	float GetLinearDepth01(float4 projPos)
	{
		float rawDepth = GetRawDepth(projPos);
		float res = Linear01Depth (rawDepth);

		return res;
	}

	float GetSceneDepthDiff(float4 projPos)
	{
		float rawDepth = GetRawDepth(projPos);
		float res = LinearEyeDepth(rawDepth) - projPos.z;
		return res;
	}

#endif

float3 GetNormalWS(float3 normalOS)
{
	float3 normalWS = UnityObjectToWorldNormal(normalOS);
	return normalWS;
}

float3 GetViewDirWS(float3 vertexWS)
{
	float3 res = UnityWorldSpaceViewDir(vertexWS);
	return res;
}

float3 GetPositionVS(float3 positionOS)
{
	float3 res = UnityObjectToViewPos(positionOS);
	return res;
}

float3 GetPositionWS(float4 positionOS)
{
	return mul(unity_ObjectToWorld, positionOS).xyz;
}

float3 GetPositionOS(float4 positionWS)
{
	return mul(unity_WorldToObject, positionWS);
}

float3 GetDirWS(float4 dirOS)
{
	return mul(unity_ObjectToWorld, float4(dirOS.xyz, 0));
}

float3 GetDirOSFloat3(float3 dirOS)
{
	return mul(unity_WorldToObject, float4(dirOS.xyz, 0));
}

float3 GetDirOS(float4 dirOS)
{
	return mul(unity_WorldToObject, dirOS);
}

float3 GetMainLightDir(float3 vertexWS)
{
#ifdef _LIGHTMODEL_FASTLIGHTING
	float3 mainLightDir = global_lightDirection;
#else
	float3 mainLightDir = normalize(_WorldSpaceLightPos0.xyz - vertexWS * _WorldSpaceLightPos0.w);
#endif

	return mainLightDir;
}

float3 GetMainLightColorRGB()
{
#ifdef _LIGHTMODEL_FASTLIGHTING
	float3 res = global_lightColor.rgb;
#else
	float3 res = _LightColor0.rgb;
#endif

	return res;
}

float2 GetSSAO(float2 normalizedScreenSpaceUV, float alpha)
{
	float2 res = float2(1, 1);
	return res;
}

float FadeShadows (float3 worldPos, float shadowAtten, AllIn1GI gi) 
{
	float res = shadowAtten;
#if defined(SHADOWS_SHADOWMASK)
	float bakedAttenuation = UnitySampleBakedOcclusion(gi.uvLightmap, worldPos);
	float zDist = dot(_WorldSpaceCameraPos.xyz - worldPos, UNITY_MATRIX_V[2].xyz);
	float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, zDist);
	float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
	
	res = UnityMixRealtimeAndBakedShadows(shadowAtten, bakedAttenuation, shadowFade);
#endif

	return res;
}

float GetShadowAttenuation(EffectsData effectsData, AllIn1GI gi)
{ 
	float res = 1;

#if !defined(_LIGHTMODEL_FASTLIGHTING)
	UNITY_LIGHT_ATTENUATION(attenuation, effectsData, effectsData.vertexWS);
	#if !defined(FORWARD_ADD_PASS)
		attenuation = FadeShadows(effectsData.vertexWS, attenuation, gi);
	#endif
	
	res = attenuation;
#endif

	return res;
}

AllIn1LightData GetPointLightData(float3 vertexWS, float3 normalWS, float3 lightPositionWS, float3 lightColor, float pointLightAtten, EffectsData effectsData, AllIn1GI gi)
{
	AllIn1LightData lightData;

	float3 lightVec = lightPositionWS - vertexWS;
	float3 lightDir = normalize(lightVec);

	float atten = 1 / (1 + dot(lightVec, lightVec) * pointLightAtten);

	lightData.lightColor = lightColor;
	lightData.lightDir = lightDir;
	
#ifdef _RECEIVE_SHADOWS_ON
	lightData.realtimeShadow = GetShadowAttenuation(effectsData, gi);
#else
	lightData.realtimeShadow = 1.0;
#endif
	lightData.distanceAttenuation = atten;
	
	lightData.shadowColor = /*lerp(0, 1.0, lightData.realtimeShadow * lightData.distanceAttenuation)*/lightData.realtimeShadow;
	lightData.layerMask = 1.0;

	return lightData;
}

AllIn1LightData GetMainLightData(float3 vertexWS, EffectsData effectsData, AllIn1GI gi)
{
	AllIn1LightData lightData;
	
	#if defined(SUBTRACTIVE_LIGHTING)
		lightData.lightColor = 0;
		lightData.lightDir = GetMainLightDir(vertexWS);
		lightData.realtimeShadow = 1.0;
		lightData.distanceAttenuation = 1.0;
		lightData.shadowColor = 1.0;
	#else
		lightData.lightColor = GetMainLightColorRGB();
		lightData.lightDir = GetMainLightDir(vertexWS);

		#if defined(_LIGHTMODEL_NONE)
			lightData.realtimeShadow = 1.0;
		#else
			#ifdef _RECEIVE_SHADOWS_ON
			lightData.realtimeShadow = GetShadowAttenuation(effectsData, gi);
				#if defined(_RECEIVEDSHADOWSTYPE_STYLIZED) && !defined(FORWARD_ADD_PASS)
					lightData.realtimeShadow = AntiAliasing(lightData.realtimeShadow, ACCESS_PROP_FLOAT(_ShadowCutoff));
				#endif
			#else
			lightData.realtimeShadow = 1.0;
			#endif
		#endif
		
		#ifdef _LIGHTMODEL_FASTLIGHTING
			lightData.shadowColor = 1.0;
		#else
			#ifdef FORWARD_ADD_PASS
			lightData.shadowColor = lightData.realtimeShadow;
			#else
			lightData.shadowColor = lerp(SHADOW_COLOR, 1.0, lightData.realtimeShadow);
			#endif
		#endif

		lightData.distanceAttenuation = 1.0;
	#endif

	lightData.layerMask = 1.0;

	return lightData;
}

float3 GetPointLightPosition(int index)
{
	float3 pointLightPosition = float3(unity_4LightPosX0[index], unity_4LightPosY0[index], unity_4LightPosZ0[index]);
	return pointLightPosition;
}

AllIn1LightData GetPointLightData(int index, float3 vertexWS, float3 normalWS, EffectsData effectsData, AllIn1GI gi)
{
	return GetPointLightData(vertexWS, normalWS, GetPointLightPosition(index), unity_LightColor[index], unity_4LightAtten0[index], effectsData, gi);
}

FragmentDataShadowCaster GetClipPosShadowCaster( /*VertexData v*/VertexData v, FragmentDataShadowCaster o)
{
	//float4 res = 0;
//#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
//		// Rendering into point light (cubemap) shadows
//		//#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
//		//#define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);

//		float3 vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz;
//		res = UnityObjectToClipPos(v.vertex);
//#else
//		// Rendering into directional or spot light shadows
//	res = UnityClipSpaceShadowCasterPos(v.vertex, v.normal);
//	res = UnityApplyLinearShadowBias(res);
//	#endif

	//res = UnityClipSpaceShadowCasterPos(v.vertex.xyz, v.normal);
	//res = UnityApplyLinearShadowBias(res);
	
	TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
	return o;
	
	//return res; 
} 

//float GetShadowAttenuation(FragmentData i, float3 vertexWS)
//{
//	//float shadowAttenuation = UNITY_SHADOW_ATTENUATION(i, vertexWS);

//	//float3 lightCoord = mul(unity_WorldToLight, float4(vertexWS, 1)).xyz;
// //   fixed shadow = UNITY_SHADOW_ATTENUATION(i, vertexWS);
// //   float res = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;

//	/*TODO: Fix this*/
//	//shadowAttenuation = 1.0;

//	UNITY_LIGHT_ATTENUATION(attenuation, i, vertexWS);
	
//	return attenuation;
//}

ShadowCoordStruct GetShadowCoords(VertexData v, float4 clipPos, float3 vertexWS, float2 uvLightmap)
{
	ShadowCoordStruct res;

	res.pos = clipPos;
	res._ShadowCoord = 0;
	
	UNITY_TRANSFER_SHADOW(res, uvLightmap);
	
	return res;
}
 
float3 GetLightmap(float2 uvLightmap, EffectsData data)
{
	float3 res = 0.0;
#if defined(_AFFECTED_BY_LIGHTMAPS_ON) && defined(LIGHTMAP_ON)
	res = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uvLightmap));
	#ifdef SUBTRACTIVE_LIGHTING
		float attenuation = GetShadowAttenuation(data);
		float ndotl = saturate(dot(data.normalWS, _WorldSpaceLightPos0.xyz));
		float3 shadowedLightEstimate =
				ndotl * (1 - attenuation) * _LightColor0.rgb;
		float3 subtractedLight = res - shadowedLightEstimate;
		subtractedLight = max(subtractedLight, unity_ShadowColor.rgb);
		subtractedLight = lerp(subtractedLight, res, _LightShadowData.x);
	
		res = subtractedLight;
	#endif
#endif

	return res;
}

float3 GetAmbientColor(EffectsData data)
{
	float3 res = float3(0, 0, 0);

#ifdef _CUSTOM_AMBIENT_LIGHT_ON
	res = ACCESS_PROP_FLOAT4(_CustomAmbientColor).rgb;
#else
	res = ShadeSH9(float4(data.normalWS, 1.0));
#endif
	
	return res;
}

AllIn1GI CalculateGI(float2 uvLightmap, EffectsData data)
{
	AllIn1GI res;
	INIT_GI(res);

#if defined(LIGHTMAP_ON)
	res.diffuse = GetLightmap(uvLightmap, data);
#else
	res.diffuse = GetAmbientColor(data);
#endif

#if defined(SHADOWS_SHADOWMASK)
	res.shadowMask = UnitySampleBakedOcclusion(uvLightmap, data.vertexWS);
#endif

	res.uvLightmap = uvLightmap;

	return res;
}

//float GetFogFactor(FragmentData fragmentData)
//{
//	float res = 0;

//#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
//	UNITY_TRANSFER_FOG(fragmentData, fragmentData.pos);
//	res = fragmentData.fogCoord;
//#endif
	
//	return res;
//}

float GetFogFactor(float4 clipPos)
{
	float res = 0;
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
	FogStruct fogStruct;
	UNITY_TRANSFER_FOG(fogStruct, clipPos);
	res = fogStruct.fogCoord;
#endif
	
	return res;
}


#if defined(FOG_ENABLED)
float4 CustomMixFog(float fogFactor, float4 col)
{
	float4 res = col;
	UNITY_APPLY_FOG(fogFactor, res);
	return res;
}
#endif

#ifdef REFLECTIONS_ON

float3 BoxProjection (
	float3 direction, float3 position,
	float4 cubemapPosition, float3 boxMin, float3 boxMax
) {
	#if UNITY_SPECCUBE_BOX_PROJECTION
		UNITY_BRANCH
		if (cubemapPosition.w > 0) {
			float3 factors =
				((direction > 0 ? boxMax : boxMin) - position) / direction;
			float scalar = min(min(factors.x, factors.y), factors.z);
			direction = direction * scalar + (position - cubemapPosition);
		}
	#endif
	return direction;
}

float3 GetReflectionsSimple(float3 worldRefl, float cubeLod, float3 positionWS)
{
	float3 reflUV0 = BoxProjection(worldRefl, positionWS, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
	float4 probe0HDR = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflUV0, cubeLod);
	float3 probe0 = DecodeHDR(probe0HDR, unity_SpecCube0_HDR);


	float3 res = probe0;
#if UNITY_SPECCUBE_BLENDING
	float interpolator = unity_SpecCube0_BoxMin.w;

	if(interpolator < 0.99999)
    {
		float3 reflUV1 = BoxProjection(worldRefl, positionWS, unity_SpecCube1_ProbePosition, unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax);
		float4 probe1HDR = UNITY_SAMPLE_TEXCUBE_SAMPLER_LOD(unity_SpecCube1, unity_SpecCube0, reflUV1, cubeLod);
		float3 probe1 = DecodeHDR(probe1HDR, unity_SpecCube1_HDR);
                    
        res = lerp(probe1, probe0, interpolator);
	}
	else
	{
		res = probe0;
	}
#endif

	return res;
}

float3 GetSkyColor(float3 positionWS, float2 normalizedScreenSpaceUV, 
	float3 normalWS, float3 viewDirWS, float cubeLod)
{
	float3 worldRefl = normalize(reflect(-viewDirWS, normalWS));
	
	float3 res = GetReflectionsSimple(worldRefl, cubeLod, positionWS);

#ifdef _REFLECTIONS_TOON
	float posterizationLevel = lerp(2, 20, ACCESS_PROP_FLOAT(_ToonFactor));
	res = floor(res * posterizationLevel) / posterizationLevel;
#endif

	res *= ACCESS_PROP_FLOAT(_ReflectionsAtten);

	return res;
}
#endif

float3 ShadeSH(float4 normalWS)
{
	float3 res = ShadeSH9(normalWS);
	return res;
}


#define NUM_ADDITIONAL_LIGHTS 4;

#define OBJECT_TO_CLIP_SPACE(v) UnityObjectToClipPos(v.vertex)
#define OBJECT_TO_CLIP_SPACE_FLOAT4(pos) UnityObjectToClipPos(pos)
#define ALLIN1_APPLY_CROSSFADE(input) \
	float2 __vpos = i.projPos.xy / i.projPos.w * _ScreenParams.xy; \
	UNITY_APPLY_DITHER_CROSSFADE(__vpos);

#endif