#ifndef ALLIN13DSHADER_LIGHT_INCLUDED
#define ALLIN13DSHADER_LIGHT_INCLUDED


#if ALLIN1_USE_FORWARD_PLUS
    #define LIGHT_LOOP_BEGIN_ALLIN13D(lightCount, data) { \
    uint lightIndex; \
    ClusterIterator _urp_internal_clusterIterator = ClusterInit(data.normalizedScreenSpaceUV, data.vertexWS, 0); \
    [loop] while (ClusterNext(_urp_internal_clusterIterator, lightIndex)) { \
        lightIndex += URP_FP_DIRECTIONAL_LIGHTS_COUNT; \
        ALLIN1_FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
    #define LIGHT_LOOP_END_ALLIN13D } }
#else
    #define LIGHT_LOOP_BEGIN_ALLIN13D(lightCount, data) \
	if(lightCount > 0) { \
    for (uint lightIndex = 0u; lightIndex < lightCount; ++lightIndex) {
    #define LIGHT_LOOP_END_ALLIN13D } }
#endif

float GetMainLightIntensity()
{
	return length(GetMainLightColorRGB());
}

#if defined(_SHADINGMODEL_PBR) || defined(_SPECULARMODEL_ANISOTROPIC) || defined(_SPECULARMODEL_ANISOTROPICTOON)
	#include "../ShaderLibrary/AllIn13DShader_BRDF.hlsl"
#endif

float3 GetBitangentWS(float4 tangentOS, float3 tangentWS, float3 normalWS)
{
    float tangentSign = tangentOS.w * unity_WorldTransformParams.w;
    float3 res = cross(normalWS, tangentWS) * tangentSign;
    return res;
}

float3 DiffuseTerm(AllIn1LightData lightData, float3 normal, float isAdditionalLight)
{
	float rawNdotL = dot(normal, lightData.lightDir);
	float3 lightModel = 0;
	float3 lightColor = lightData.lightColor.rgb * lightData.distanceAttenuation * lightData.shadowColor.rgb;

#if !defined(FORWARD_ADD_PASS)
	#if defined(_LIGHTMODEL_HALFLAMBERT) || defined(_LIGHTMODEL_FAKEGI) || defined(_LIGHTMODEL_TOONRAMP)
		lightColor = isAdditionalLight > 0 ? lightColor : lightData.lightColor.rgb;
		rawNdotL = isAdditionalLight > 0 ? rawNdotL : rawNdotL * GetLuminance(lightData.shadowColor.rgb);
	#endif
#endif

#if defined(_LIGHTMODEL_CLASSIC)
	float NdotL = saturate(rawNdotL);
	lightModel = NdotL;

#elif defined(_LIGHTMODEL_TOON)
	float NdotL = saturate(rawNdotL);
	lightModel = smoothstep(ACCESS_PROP_FLOAT(_ToonCutoff), ACCESS_PROP_FLOAT(_ToonCutoff) + ACCESS_PROP_FLOAT(_ToonSmoothness), NdotL); 

#elif defined(_LIGHTMODEL_TOONRAMP)
	float NdotL = (rawNdotL * 0.5) + 0.5;
	lightModel = SAMPLE_TEX2D_LOD(_ToonRamp, float4(NdotL, 0, 0, 0)).rgb;

#elif defined(_LIGHTMODEL_HALFLAMBERT)
	float NdotL = saturate(rawNdotL);
	float halfLambertTerm = (NdotL * ACCESS_PROP_FLOAT(_HalfLambertWrap)) + (1 - ACCESS_PROP_FLOAT(_HalfLambertWrap));
	lightModel = halfLambertTerm * halfLambertTerm;

#elif defined(_LIGHTMODEL_FAKEGI)
	float NdotL = saturate(rawNdotL);
	lightModel = (NdotL * ACCESS_PROP_FLOAT(_HardnessFakeGI)) + 1.0 - ACCESS_PROP_FLOAT(_HardnessFakeGI);

#elif defined(_LIGHTMODEL_FASTLIGHTING)
	lightModel = saturate(rawNdotL);

#endif
	
	float3 res = lightModel * lightColor;

    return res;
}

#ifdef SPECULAR_ON

float3 SpecularTerm(float3 objectColor, AllIn1LightData lightData, 
		float3 normalWS, float3 tangentWS, float3 bitangentWS,
		float3 viewDirWS, float glossiness, float2 mainUV, float4 specularTex)
{
	float3 lightColor = lightData.lightColor.rgb * lightData.distanceAttenuation * lightData.shadowColor.rgb;
    float3 reflectionDir = reflect(-lightData.lightDir, normalWS);
    float rawVdotL = dot(viewDirWS, reflectionDir);
	
    float3 specularModel = 0;
	

#if defined(_SPECULARMODEL_CLASSIC) || defined(_SPECULARMODEL_TOON)
	float3 halfVector = normalize(viewDirWS + lightData.lightDir);
	float NdotH = saturate(dot(normalWS, halfVector));
	float specularIntensity = pow(NdotH, glossiness);
	
#elif defined(_SPECULARMODEL_ANISOTROPIC) || defined(_SPECULARMODEL_ANISOTROPICTOON)
	float3 H = normalize(viewDirWS + lightData.lightDir);

	float TdotH = saturate(dot(tangentWS, H));
	float BdotH = saturate(dot(bitangentWS, H));

	float TdotV = saturate(dot(tangentWS, viewDirWS));
	float BdotV = saturate(dot(bitangentWS, viewDirWS));

	float TdotL = saturate(dot(tangentWS, lightData.lightDir));
	float BdotL = saturate(dot(bitangentWS, lightData.lightDir));

	float NdotH = saturate(dot(normalWS, H));
	float NdotL = saturate(dot(normalWS, lightData.lightDir));
	float NdotV = saturate(dot(normalWS, viewDirWS));

	float VdotH = saturate(dot(viewDirWS, H));

	float anisotropy = ACCESS_PROP_FLOAT(_Anisotropy);
	float anisoShininess = ACCESS_PROP_FLOAT(_AnisoShininess);
	float at = max((1 - anisoShininess) * (1.0 + anisotropy), 0.001);
	float ab = max((1 - anisoShininess) * (1.0 - anisotropy), 0.001);

	float3 specularIntensity = SpecularAnisoTerm(
		at, ab, 
		float3(1.0, 1.0, 1.0), 
		NdotH, NdotV, NdotL,
		TdotV, BdotV, 
		TdotL, BdotL, 
		TdotH, BdotH,
		H, tangentWS.xyz, bitangentWS.xyz) * NdotL;

	specularIntensity = saturate(specularIntensity);
#endif

#if defined(_SPECULARMODEL_ANISOTROPICTOON) || defined(_SPECULARMODEL_TOON)
	float specularSmoothness = max(ACCESS_PROP_FLOAT(_SpecularToonSmoothness), 0.001);
	specularIntensity = smoothstep(ACCESS_PROP_FLOAT(_SpecularToonCutoff), ACCESS_PROP_FLOAT(_SpecularToonCutoff) + specularSmoothness, specularIntensity);
#endif

	specularModel = specularIntensity * lightColor;
	float3 res = specularModel * ACCESS_PROP_FLOAT(_SpecularAtten) * specularTex.r;
	
	return res;
}

float3 SpecularTerm_Basic(float3 objectColor, AllIn1LightData lightData, 
		float3 normalWS, float3 tangentWS, float3 bitangentWS, float3 viewDirWS, float2 mainUV, float4 specularTex)
{
	float3 res = SpecularTerm(objectColor, lightData,
		normalWS, tangentWS, bitangentWS,
		viewDirWS, ACCESS_PROP_FLOAT(_Shininess) * ACCESS_PROP_FLOAT(_Shininess), mainUV, specularTex);
	return res;
}

#endif

float3 RimTermOperation(float3 normalWS, float3 viewDirWS, float minRim, float maxRim, float rimAttenuation, float3 rimColor)
{
	float NdotV = saturate(dot(normalWS, viewDirWS));
	float rimIntensity = (1 - NdotV);
	rimIntensity = smoothstep(minRim, maxRim, rimIntensity) * rimAttenuation;

	float3 res = rimIntensity * rimColor;
	return res;
}

float3 DirectLighting(float3 objectColor, EffectsData effectsData, AllIn1LightData lightData, float4 specularTex, float isAdditionalLight)
{
	float3 res = 0;

	float3 diffuseTerm = DiffuseTerm(lightData, effectsData.normalWS, isAdditionalLight) * objectColor;
	
#if defined(_CUSTOM_SHADOW_COLOR_ON) && !defined(FORWARD_ADD_PASS)
	float shadowT = saturate(lightData.realtimeShadow + 1.0 - global_shadowColor.a);
	diffuseTerm = lerp(global_shadowColor, diffuseTerm, shadowT);
#endif
	
	float3 specularTerm = 0;
#ifdef SPECULAR_ON
	
	float NdotL = saturate(dot(effectsData.normalWS, lightData.lightDir));

    specularTerm = SpecularTerm_Basic(objectColor, lightData,
		effectsData.normalWS, effectsData.tangentWS, effectsData.bitangentWS, 
		effectsData.viewDirWS, effectsData.mainUV, specularTex) * NdotL;
#endif
	
	res = diffuseTerm + specularTerm;

	return res;
}

float3 DirectLighting(float3 objectColor, float2 ssaoFactor, EffectsData effectsData, AllIn1GI gi)
{
	AllIn1LightData mainLightData = GetMainLightData(effectsData.vertexWS, effectsData, gi);


	float3 res = objectColor;
	float4 specularTex = float4(1, 1, 1, 1);

#ifdef ALLIN1_USE_LIGHT_LAYERS
	uint meshRenderingLayers = AllIn1GetMeshRenderingLayer();
	res = 0;
	if (IsMatchingLightLayer(mainLightData.layerMask, meshRenderingLayers))
	{
		res = objectColor;
#endif

#ifdef SPECULAR_ON
	specularTex = SAMPLE_TEX2D(_SpecularMap, effectsData.mainUV);
#endif

	res = DirectLighting(objectColor, effectsData, mainLightData, specularTex, 0);

#ifdef ALLIN1_USE_LIGHT_LAYERS
		}
#endif



#if defined(ADDITIONAL_LIGHT_LOOP) && !defined(_LIGHTMODEL_FASTLIGHTING)
	// Additional light loop for non-main directional lights. This block is specific to Forward+.
	#if USE_CLUSTER_LIGHT_LOOP
	UNITY_LOOP for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
	{
		AllIn1LightData additionalLightData = GetPointLightData(lightIndex, effectsData.vertexWS, effectsData.normalWS, effectsData, gi);
		#ifdef ALLIN1_USE_LIGHT_LAYERS
		if (IsMatchingLightLayer(additionalLightData.layerMask, meshRenderingLayers))
		{
		#endif
			res += DirectLighting(objectColor, effectsData, additionalLightData, specularTex, 1);
		#ifdef ALLIN1_USE_LIGHT_LAYERS
		}
		#endif
	}
	#endif
	
	uint numAdditionalLights = NUM_ADDITIONAL_LIGHTS;
	LIGHT_LOOP_BEGIN_ALLIN13D(numAdditionalLights, effectsData)
		AllIn1LightData additionalLightData = GetPointLightData(lightIndex, effectsData.vertexWS, effectsData.normalWS, effectsData, gi);
	#ifdef ALLIN1_USE_LIGHT_LAYERS
		if (IsMatchingLightLayer(additionalLightData.layerMask, meshRenderingLayers))
		{
	#endif
		res += DirectLighting(objectColor, effectsData, additionalLightData, specularTex, 1);
	#ifdef ALLIN1_USE_LIGHT_LAYERS
		}
	#endif

	LIGHT_LOOP_END_ALLIN13D
#endif

	res *= ssaoFactor.x;

	return res;
}

float3 IndirectLighting_Basic(float3 objectColor, float2 ssaoFactor, EffectsData effectsData, AllIn1GI gi)
{
	//float3 ambientColor = GetAmbientColor(effectsData);

	float3 ao = 1.0;

	float3 reflections = 0;
#ifdef REFLECTIONS_ON
	reflections = GetSkyColor(effectsData.vertexWS, effectsData.normalizedScreenSpaceUV, effectsData.normalWS, effectsData.viewDirWS, 1.0);
#endif
	
	float3 indirectLighting = /*(ambientColor + lightmap)*/gi.diffuse * objectColor;
	indirectLighting += reflections;

	indirectLighting *= ssaoFactor.y;

	return indirectLighting;
}

float3 CalculateLighting_Basic(float3 objectColor, float alpha, EffectsData effectsData, AllIn1GI gi)
{
	float2 ssaoFactor = GetSSAO(effectsData.normalizedScreenSpaceUV.xy, alpha); 

	float3 directLighting = DirectLighting(objectColor, ssaoFactor, effectsData, gi);
	
	
	float3 indirectLighting = 0;	
	//We add IndirectLighting only once
#ifndef FORWARD_ADD_PASS
	indirectLighting = IndirectLighting_Basic(objectColor, ssaoFactor, effectsData, gi);
	
	#ifdef _AOMAP_ON
		float3 aoMapValue = GetAOMapTerm(effectsData.mainUV);
		indirectLighting *= aoMapValue;
	#endif
#endif

	float3 res = directLighting + indirectLighting;

	return res;
}

#ifdef _SHADINGMODEL_PBR
float3 CalculateLighting_MetallicWorkflow(
	float3 vertexWS, 
	float3 normalWS, float3 tangentWS, float3 bitangentWS, 
	float3 objectColor, float alpha,
	float3 shadows, float3 ambientCol, float3 viewDirWS, 
	float2 mainUV, FragmentData fragmentData, EffectsData effectsData, AllIn1GI gi)
{
	float3 res = CalculateLighting_PBR(objectColor, alpha, effectsData, gi);

	return res;
}

#endif

float3 CalculateLighting(float3 vertexWS, 
	float3 normalWS, float3 tangentWS, float3 bitangentWS, 
	float3 objectColor, float alpha,
	float shadows, float3 ambientCol, float3 viewDirWS, 
	float2 mainUV,
	float3 lightColor, float3 lightDir,
	FragmentData fragmentData, float lightAtten, EffectsData effectsData, 
	AllIn1GI gi)
{
#ifdef _CUSTOM_SHADOW_COLOR_ON
	float3 shadowColor = lerp(1.0, global_shadowColor, 1 - shadows);
#else
	float3 shadowColor = shadows;
#endif
	
	float3 res = 0;
#ifdef _LIGHTMODEL_FASTLIGHTING
	res = CalculateLighting_Basic(objectColor, alpha, effectsData, gi);
#else
	#ifdef _SHADINGMODEL_PBR
		res = CalculateLighting_MetallicWorkflow(
			vertexWS, 
			normalWS, tangentWS, bitangentWS, 
			objectColor, alpha, 
			shadowColor, ambientCol, viewDirWS, 
			mainUV, fragmentData, effectsData, gi);
	#else
		res = CalculateLighting_Basic(objectColor, alpha, effectsData, gi);
	#endif
#endif
	
	return res;
}

#endif