#include "/lib/shadowconst.glsl"
#include "/lib/shadow_distort.glsl"

uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;


// #define bias 0.0003


vec2 R2(float n) {
	const float s0 = 0.5;
	const vec2 alpha = 1.0 / vec2(PLASTIC_CONSTANT, PLASTIC_CONSTANT * PLASTIC_CONSTANT);
	return fract(s0 + n * alpha);
}

// float getShadowBias(vec3 viewPos) {
//     return mix(0.008, 0.01, clamp(length(viewPos)/12.0, 0.0, 1.0));
// }

// vec3 shadow(vec3 p, vec3 n) {
	
//     float lightDot = dot(n, normalize(sunPosition));
//     if(lightDot > 0.0) {
//         vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
// 		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
// 		float distortFactor = getDistortFactor(shadowPos.xy);
// 		shadowPos.xyz = distort(shadowPos.xyz, distortFactor); //apply shadow distortion
// 		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5; //convert from -1 ~ +1 to 0 ~ 1
// 		shadowPos.z -= Shadow_Distortion_Bias * (distortFactor * distortFactor) / abs(lightDot); //apply shadow bias

//         if (shadowPos.w > 0.0) {
// 			//for invisible and colored shadows, first check the closest OPAQUE thing to the sun.
// 			if (texture2D(shadowtex0, shadowPos.xy).r < shadowPos.z) {
// 				//surface is in shadows. reduce light level.
// 				return vec3(0.0);
// 			} else {
// 				//surface is in direct sunlight. increase light level.
// 				//when colored shadows are enabled and there's nothing OPAQUE between us and the sun,
// 				//perform a 2nd check to see if there's anything translucent between us and the sun.
// 				vec3 s = vec3(1.0);
// 				return s;
// 				if (texture2D(shadowtex0, shadowPos.xy).r < shadowPos.z) {
// 					//surface has translucent object between it and the sun. modify its color.
// 					//if the block light is high, modify the color less.
// 					vec4 shadowLightColor = texture2D(shadowcolor0, shadowPos.xy);
// 					//make colors more intense when the shadow light color is more opaque.
// 					shadowLightColor.rgb = mix(vec3(1.0), shadowLightColor.rgb, shadowLightColor.a);
// 					// //also make colors less intense when the block light level is high.
// 					// shadowLightColor.rgb = mix(shadowLightColor.rgb, vec3(1.0), lm.x);
// 					// //apply the color.
// 					s *= shadowLightColor.rgb;
// 					return s;
// 				}
// 			}
// 		}
//     } else {
//         return vec3(0.0);
//     }
// }

float sampleShadow(sampler2D tex, vec3 pos, float Bias) {
	// pos.xy = pos.xy*0.5 + 0.5;
	if (floor(pos.xy) != vec2(0.0) || pos.z >= 1.0) return 1.0;
	return step(pos.z - Bias, textureLod(tex, pos.xy, 0.0).x);
}
vec3 sampleColour(sampler2D tex, vec3 pos, float Bias) {
	// pos.xy = pos.xy*0.5 + 0.5;
	if (floor(pos.xy) != vec2(0.0) || pos.z >= 1.0) return vec3(1.0);
	vec4 col = textureLod(tex, pos.xy, 0.0);
	return col.a > 0.6 ? col.rgb : vec3(1.0);
}

vec3 convertShadowScreenSpace(vec3 p) {
	vec4 playerPos = vec4(p, 1.0);
	vec4 shadowPos = shadowProjection*(shadowModelView * playerPos); //convert to shadow screen space
	shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;

	return (shadowPos.xyz);
}

vec3 shadowFast(vec3 p, vec3 n) {
	
    float lightDot = dot(n, normalize(lightDirView));
    if(lightDot > 0.0 || true) {
        vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
		float bias = calcShadowBias(shadowPos.xy);

		// return textureLod(shadowcolor0, distortShadowSpace(shadowPos.xyz).xy * 0.5 + 0.5, 0.0).xyz;

		float s = sampleShadow(shadowtex0, distortShadowSpace(shadowPos.xyz), bias);

		return vec3(s); 
    } else {
        return vec3(0.0);
    }
}

vec3 shadowFast1(vec3 p, vec3 n) {
	
    float lightDot = dot(n, normalize(lightDirView));
    if(lightDot > 0.0 || true) {
        vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
		float bias = calcShadowBias(shadowPos.xy);

		float s = sampleShadow(shadowtex1, distortShadowSpace(shadowPos.xyz), bias);
		return vec3(s); 
    } else {
        return vec3(0.0);
    }
}
vec3 shadowFast1Coloured(vec3 p, vec3 n) {
	
    float lightDot = dot(n, normalize(lightDirView));
    if(lightDot > 0.0 || true) {
        vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
		float bias = calcShadowBias(shadowPos.xy);

		shadowPos.xyz = distortShadowSpace(shadowPos.xyz);
		float s = sampleShadow(shadowtex1, shadowPos.xyz, bias);
		return vec3(s);//*sampleColour(shadowcolor1, shadowPos.xyz, bias); 
    } else {
        return vec3(0.0);
    }
}


vec3 shadowColour(vec3 p, vec3 n) {
	
    float lightDot = dot(n, normalize(lightDirView));
    if(lightDot > 0.0 || true) {
        vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
		float bias = calcShadowBias(shadowPos.xy);

		shadowPos.xyz = distortShadowSpace(shadowPos.xyz);
		return sampleColour(shadowcolor0, shadowPos.xyz, bias); 
    } else {
        return vec3(0.0);
    }
}

bool shadowIsWater(vec3 p, vec3 n, out vec4 tintCol) {//Worldpos, viewPos
	
    // float lightDot = dot(n, normalize(lightDirView));
    // if(lightDot > 0.0 || true) {
		vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
		float bias = calcShadowBias(shadowPos.xy);

		// return textureLod(shadowcolor0, distortShadowSpace(shadowPos.xyz).xy * 0.5 + 0.5, 0.0).xyz;
		shadowPos.xyz = distortShadowSpace(shadowPos.xyz);
		// shadowPos.xy = shadowPos.xy*0.5 + 0.5;
		// float z0 = textureLod(shadowtex0, shadowPos.xy, 0.0).x;
		// float z1 = textureLod(shadowtex1, shadowPos.xy, 0.0).x;

		tintCol = textureLod(shadowcolor0, shadowPos.xy, 0.0);
		return abs(tintCol.r - 0.998) < 0.001;
		// return z1 > z0+bias*2; 
    // } else {
    //     return false;
    // }
}

// vec3 getShadowTint(vec3 p, vec3 n, out vec4 tintCol, out float visiblity) {//Worldpos, viewPos
	
//     // float lightDot = dot(n, normalize(lightDirView));
//     // if(lightDot > 0.0 || true) {
// 		vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
// 		vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
// 		shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
// 		float bias = calcShadowBias(shadowPos.xy);

// 		// return textureLod(shadowcolor0, distortShadowSpace(shadowPos.xyz).xy * 0.5 + 0.5, 0.0).xyz;
// 		shadowPos.xyz = distortShadowSpace(shadowPos.xyz);
// 		shadowPos.xy = shadowPos.xy*0.5 + 0.5;
// 		float z0 = textureLod(shadowtex0, shadowPos.xy, 0.0).x;
// 		float z1 = textureLod(shadowtex1, shadowPos.xy, 0.0).x;

// 		tintCol = textureLod(shadowcolor0, shadowPos.xy, 0.0);

// 		return vec3(float(z1 <= z0+bias*2)); 
//     // } else {
//     //     return false;
//     // }
// }

float blockerSearch(sampler2D shadowTex, vec3 p, vec2 dither) {
	vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
	vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
	shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
	float bias = calcShadowBias(shadowPos.xy);

	float searchScale  = 16.0 / abs(shadowProjectionInverse[2].z);
    float spread       = tan(radians(0.75)) * abs(shadowProjectionInverse[2].z) * shadowProjection[0].x;

    float searchRad = spread * searchScale;
	// float searchRad = min(0.5 * shadowProjection[0].x, 13.404*shadowProjection[0].x / TAU);

    if (shadowPos.w > 0.0) {
		float depth = 0.0;
		float samples = 0.00001;
    	for (uint i = 0; i < 6; i++) {
			vec2 offset = mod(dither+vec2(GOLDEN_RATIO, PLASTIC_CONSTANT)*float(i), vec2(2.0));//R2((i + dither) * 64.0);
        	offset = vec2(cos(offset.x * TAU), sin(offset.x * TAU)) * sqrt(offset.y)*searchRad*0.4;
			vec3 offsetPos = distortShadowSpace(shadowPos.xyz + vec3(offset, 0.0));//0.005;

			float shadowSample = textureLod(shadowTex, offsetPos.xy, 0.0).r;
			// float isGoodSample = float(offsetPos.z - shadowSample > bias);
			depth += (offsetPos.z - shadowSample);//*isGoodSample;
			samples += 1.0;
    	}
		// return depth / samples;
		return (depth / samples) * spread * int(MC_SHADOW_QUALITY * shadowMapResolution * 2);
	}
	return 0.0;
}

vec3 shadow(sampler2D shadowTex, vec3 p, vec3 n, const int samples, vec2 dither, bool isPlant, float blockerDepth) {
	// if(!isPlant && dot(n, ))
	blockerDepth = clamp(blockerDepth, 0.4, 2.2);
	if(isPlant) blockerDepth = 7.3;

    vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
	vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
	shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;
	float bias = calcShadowBias(shadowPos.xy);

	float s = 0.0;

    for (uint i = 0; i < samples; i++) {
		vec2 offset = mod(dither+vec2(GOLDEN_RATIO, PLASTIC_CONSTANT)*float(i), vec2(2.0));//R2((i + dither) * 64.0);
    	offset = vec2(cos(offset.x * TAU), sin(offset.x * TAU)) * sqrt(offset.y) * blockerDepth * invShadowMapRes * 0.4;
		vec3 offsetPos = shadowPos.xyz + vec3(offset, 0.0);

		s += sampleShadow(shadowTex, distortShadowSpace(offsetPos), bias);
    }
	s /= float(samples);
	return vec3(s);
}

float shadowCaustic(vec3 p) {
	
    vec4 playerPos = gbufferModelViewInverse * vec4(p, 1.0);
	vec4 shadowPos = shadowProjection * (shadowModelView * playerPos); //convert to shadow screen space
	shadowPos.xyz = shadowPos.xyz * 0.5 + 0.5;

	float data = textureLod(shadowcolor1, distortShadowSpace(shadowPos.xyz).xy*0.5 + 0.5, 0.0).a;
	return data;
}
