#version 430

#define DUMP_PREPROCESSED 1

// bind light setup to buffer 1
#define LIGHT_PROPERTIES_BINDING 1

uniform sampler2D s_texture0;	// depth
uniform usampler2D s_texture1;	// normal + material (encoded)
uniform sampler2D s_texture2;	// color

uniform sampler2D s_LTC1;
uniform sampler2D s_LTC2;

in vec2 vTexcoord0;
in vec4 vFrustum;

uniform vec4 vNearFarPlane;
uniform vec3 vCameraPosition;

uniform float g_global_time;

// output

out vec4 outColor;

#include <../materials/commons.glsl>

//#define ENABLE_FILTERING
//#define INCLUDE_LIGHTING
//#define ENABLE_SHADOWS
#define ENABLE_FALLOFF

float linearizeDepth(float d)
{
	return vNearFarPlane.z / (vNearFarPlane.y + vNearFarPlane.x - d * vNearFarPlane.w);
}

vec3 positionFromDepth(vec4 vDirection, float depth)
{
	return vCameraPosition.xyz + (vDirection.xyz * depth);
}

float sampleShadowPCF(in sampler2DShadow sampler, in vec4 coords, out float in_frustum)
{
	const float spreadscale = 1.0 / (1.0 * 1024.0);
	float fact = 0.0;
	float x, y;
	
	in_frustum = 0.0;

	// no shading if fragment behind
	if (coords.w <= 0.0)
		return 0.0;

	// no shading if fragment outside of light frustum
	if (coords.x < -coords.w || coords.x > coords.w || coords.y < -coords.w || coords.y > coords.w)
		return 1.0;

	in_frustum = 1.0;

	// NOTE: we need to shift after projection. this works here but we dont do it in directional. most likely because projection matrix is simple so it works...
	//       but this shift in directional might actualy introduce tiny inaccuracies
	// NOTE: can we factor that into projection matrix? most likely we can!
	coords.xy = coords.xy * vec2(0.5) + vec2(0.5) * coords.w;
	vec3 samp = coords.xyz / coords.w;

	for(x=-1.5; x<1.5; x+=3.0/8.0)
	{
		for(y=-1.5; y<1.5; y+=3.0/8.0)
		{
			float shadow = textureProj(sampler, coords + vec4(x * spreadscale * coords.w, y * spreadscale * coords.w, 0.0, 0.0));
			fact += shadow;
		}
	}
	
	return fact * (1.0 / 64.0);
}

float sampleShadow(in sampler2DShadow sampler, in vec4 coords, out float in_frustum, out vec4 projector_color)
{
	in_frustum = 0.0;

	// no shading if fragment behind
	if (coords.w <= 0.0)
		return 0.0;

	// no shading if fragment outside of light frustum
	if (coords.x < -coords.w || coords.x > coords.w || coords.y < -coords.w || coords.y > coords.w)
		return 1.0;

	in_frustum = 1.0;

	// NOTE: we need to shift after projection. this works here but we dont do it in directional. most likely because projection matrix is simple so it works...
	//       but this shift in directional might actualy introduce tiny inaccuracies
	// NOTE: can we factor that into projection matrix? most likely we can!
	coords.xy = coords.xy * vec2(0.5) + vec2(0.5) * coords.w;
	vec3 samp = coords.xyz / coords.w;

	float shadow = textureProj(sampler, coords);
	// TODO: Reimplement
	projector_color = vec4(0.0);// texture(LightProjectorSamplers[light.projector_sampler], coords.xy / coords.w);// * 10.0;
	return shadow;
}

float sampleShadowDist(in sampler2DShadow sampler, in vec4 coords, out float in_frustum, out vec4 projector_color, out float shadow_dist)
{
	in_frustum = 0.0;

	// no shading if fragment behind
	if (coords.w <= 0.0)
		return 0.0;

	// no shading if fragment outside of light frustum
	if (coords.x < -coords.w || coords.x > coords.w || coords.y < -coords.w || coords.y > coords.w)
		return 1.0;

	in_frustum = 1.0;

	coords.xy = coords.xy * vec2(0.5) + vec2(0.5) * coords.w;
	vec3 samp = coords.xyz / coords.w;

	float shadow = textureProj(sampler, coords);

	// TODO: Reimplement
	projector_color = vec4(0.0);
	shadow_dist = 0.0;
	//projector_color = texture(LightProjectorSamplers[light.projector_sampler], coords.xy / coords.w);
	//shadow_dist = texture(LightShadowmapCmpSamplers[light.projector_sampler], coords.xy / coords.w).r - coords.z / coords.w;
	return shadow;
}

float calculate_shadow_for_position(in LightProperties light, vec3 world, float depth, out vec4 cascadeColor, out vec4 projector_color)
{
	vec4 vShadowCoords = light.mat_shadow_mvp[0] * vec4(world.xyz, 1.0);
	float in_frustum;
	cascadeColor = vec4(1.0, 0.1, 0.1, 1.0);
	float shadow = sampleShadow(LightShadowmapCmpSamplers[light.shadowmap_sampler0], vShadowCoords, in_frustum, projector_color);
	shadow *= in_frustum;
	return shadow;
}

float calculate_shadow_dist_for_position(in LightProperties light, vec3 world, float depth, out vec4 cascadeColor, out vec4 projector_color, out float shadow_dist)
{
	vec4 vShadowCoords = light.mat_shadow_mvp[0] * vec4(world.xyz, 1.0);
	float in_frustum;
	cascadeColor = vec4(1.0, 0.1, 0.1, 1.0);
	float shadow = sampleShadowDist(LightShadowmapCmpSamplers[light.shadowmap_sampler0], vShadowCoords, in_frustum, projector_color, shadow_dist);
	shadow *= in_frustum;
	projector_color *= in_frustum;
	return shadow;
}

// ray-cone intersection. could be removed when we finally use the bounding primitive
// code based on https://www.shadertoy.com/view/MtcXWr

struct Cone
{
	float cosa;	// half cone angle
	float h;	// height
	vec3 c;		// tip position
	vec3 v;		// axis
};

struct Ray
{
	vec3 o;		// origin
	vec3 d;		// direction
};

bool inside_light_cone(vec3 p0, vec3 p, float angle, float height)
{
	float hsquared = height*height;
	float cosangle = angle;
	float cDistance = dot(p, p);
	return cDistance<=hsquared && dot(p0, p) >= sqrt(cDistance)*cosangle;
}

bool intersect_cone(Cone s, Ray r, out float v)
{
	v = 0.0;

	if (inside_light_cone(s.v, r.o - s.c, s.cosa, s.h))
	{
	//	v = 0.5;
		return true;
	}

	vec3 co = r.o - s.c;

	float a = dot(r.d,s.v)*dot(r.d,s.v) - s.cosa*s.cosa;
	float b = 2. * (dot(r.d,s.v)*dot(co,s.v) - dot(r.d,co)*s.cosa*s.cosa);
	float c = dot(co,s.v)*dot(co,s.v) - dot(co,co)*s.cosa*s.cosa;

	float det = b*b - 4.*a*c;
	if (det < 0.)
	{
		//v = 0.7;
		return false;
	}

	det = sqrt(det);
	float t1 = (-b - det) / (2. * a);
	float t2 = (-b + det) / (2. * a);

	// This is a bit messy; there ought to be a more elegant solution.
	float t = t1;
	//	if (t < 0.) t = t2;

	if (t < 0. || t2 > 0. && t2 < t) t = t2;
	//if (t < 0. || t2 > t) t = t2;		// we actualy test for further intersection
	//if (t < 0.) return false;

	if (t < 0.0)
		return false;

	vec3 cp = r.o + t*r.d - s.c;
	float h = dot(cp, s.v);
	if (h < 0.0)
	{
		// can happen if nearest intersection is for the 'negative cone'
		cp = r.o + max(t1, t2)*r.d - s.c;
		h = dot(cp, s.v);
		if (h < 0.0)
			return false;
	}
	if (h < s.h)
		return true;
	
	// check for far intersection if exists
	{
		cp = r.o + max(t1, t2)*r.d - s.c;
		h = dot(cp, s.v);
		if (h > 0.0 && h < s.h)
			return true;
	}

	//v = 0.1;
	return false;
	
	// cap the cone with a plane. NOTE: not needed because we also check the further intersection which seems to be enough
#if 0
	{
		vec3 P0 = s.c + s.h * s.v;
		float d = dot(normalize(s.v), r.d);
		if (d > 0.0)
			return false;

		//float tp = -(dot(r.o, normalize(s.v)) + length(P0)) / d;
		float tp = -(dot(r.o - P0, normalize(s.v))) / d;

		if (t1 > t2)
		{
			float st = t1;
			t1 = t2;
			t2 = st;
		}

		if (tp < t1 || tp > t2)
			return false;
	}
	return true;
#endif
}

#include <../materials/noise/noise3d.glsl>
#include <support/volumetric_component.glsl>

// all the positions are in world coords
vec4 calculate_lighting_world(LightProperties light, in vec3 pos, in vec3 normal, in vec3 light_pos, in vec3 cam_pos, in float NdotL)
{
	float d = NdotL;
	if (d < 0.0)
		d = 0.0;
	
	vec3 specular = vec3(0.0);
	if (d > 0.0)
		specular = pow(max(0.0, dot(reflect(-normalize(pos - light_pos), normalize(normal)), -normalize(cam_pos - pos))), 14.0) * light.diffuse.xyz;

	return vec4(vec3(d) * light.diffuse.xyz + specular, 1.0);
	//return vec4(vec3(d) * light.diffuse.xyz, 1.0);
}

//#include <support/ltc_line.glsl>
#include <support/ltc_quad.glsl>

void main() {

	uint encoded_normal_material = texture(s_texture1, vTexcoord0).r;
	vec3 vNorm = decode_normal(encoded_normal_material);
	int materialId = decode_material(encoded_normal_material);

	float depth = linearizeDepth(texture(s_texture0, vTexcoord0).r);
	vec3 world = positionFromDepth(vFrustum, depth);

	// iterate through lights
	for(int light_idx = 0; light_idx < g_lights_num; light_idx++)
	{
		LightProperties light = lights.light_properties[light_idx];

		// remove when we use the proxy object... -- kiero
		// NOTE: also, dont check for non-volumetric lights
	#ifdef CALCULATE_VOLUMETRIC
		{
			Cone cone = Cone(light.cutoff * 1.0, light.range * 1.5, light.position.xyz, light.direction.xyz);
			Ray ray = Ray(vCameraPosition.xyz, normalize(vFrustum.xyz));
			float vv = 0.0;
			if (intersect_cone(cone, ray, vv) == false)
			{
				outColor = vec4(0.0);
				return;
			}
		}
	#endif

		float NdotL = dot(normalize(vNorm), normalize(world.xyz - light.position.xyz));

		// for particles don't include directional component
		if ((materialId & MATERIAL_ID_MASK_ATTR) == ATTR_PARTICLE)
			NdotL = 1.0;

		vec4 lighting = calculate_lighting_world(light, world, vNorm, light.position.xyz, vCameraPosition.xyz, NdotL);
		
		vec4 vShadowCoords = light.mat_shadow_p[0] * light.mat_shadow_mv * vec4(world.xyz, 1.0);
		
		// shadows
		float in_frustum = 0.0;
		float shadow = 0.0;
	#ifdef ENABLE_SHADOWS

		if ((materialId & MATERIAL_ID_MASK_ATTR) == ATTR_BACKGROUND) // TODO: stencil could do this perfectly fine..
		{
			shadow = 0.0;
			lighting = vec4(1.0);
		}
		else
		{
			shadow = sampleShadowPCF(LightShadowmapCmpSamplers[light.shadowmap_sampler0], vShadowCoords, in_frustum);
			shadow = clamp(shadow, 0.0, 1.0);
		}

	#endif
		//shadow = 0.0;

		if (NdotL <= 0.0)
			shadow = 1.0;

	#ifdef INCLUDE_LIGHTING
		if (shadow < 1.0)
		{
			outColor = vec4(vec3(1.0 - shadow), 1.0) * lighting;
		}
	#else
		outColor = vec4(0.1 - shadow * 0.1);
	#endif
	
	#ifdef ENABLE_FALLOFF
	#ifdef INCLUDE_LIGHTING
		float cutoff = light.cutoff;
		float length_light_world = length(light.position.xyz - world.xyz);
		float falloff = dot(light.direction.xyz, (world.xyz - light.position.xyz) / length_light_world);
	
		if (falloff > cutoff && length_light_world < light.range)
		{
			float attenuation = 1.0 - (1.0 - falloff) / (1.0 - cutoff);
			attenuation *= (1.0 - clamp(length_light_world / light.range, 0.0, 1.0));
			attenuation = pow(attenuation, 2.0);
			outColor = outColor * attenuation;
		}
		else
		{
			outColor = vec4(0.0);
		}

		// TODO: Externalize. Experimental LTC lighting support. Move to external shader and apply then in bulk? -- kiero
		if (light.is_area != 0)
		{
			if ((materialId & MATERIAL_ID_MASK_ATTR) == ATTR_PARTICLE)
			{
				outColor.rgb = normalize(light.diffuse.rgb) * light.intensity;
			}
			else
			{
				Rect rect;
				InitRect(light, rect);

				vec3 points[4];
				InitRectPoints(rect, points);

				float ltc_roughness = light.roughness;	// HACK!!
				float ltc_intensity = light.intensity;

				vec3 lcol = vec3(ltc_intensity);
				vec3 dcol = vec3(light.diffuse.rgb);
				vec3 scol = dcol;

				vec3 col = vec3(0.0);
				vec3 pos = world.xyz;

				vec3 N = -normalize(vNorm);
				//vec3 V = normalize(world.xyz - vCameraPosition.xyz);
				vec3 V = normalize(vCameraPosition.xyz - world.xyz);

				float ndotv = clamp(dot(N, V), 0.05, 1.0);

				vec2 uv = vec2(ltc_roughness, sqrt(1.0 - ndotv));
				uv = uv*LUT_SCALE + LUT_BIAS;

				vec4 t1 = texture(s_LTC1, uv);
				vec4 t2 = texture(s_LTC2, uv);
		#if 0 // line handling
				ltc_Minv = mat3(
					vec3(t1.x, 0, t1.y),
					vec3(  0,  1,    0),
					vec3(t1.z, 0, t1.w)
				);

				vec3 spec = LTC_Evaluate(N, V, pos);
				// BRDF shadowing and Fresnel
				spec *= scol*t2.x + (vec3(1.0) - scol)*t2.y;

				ltc_Minv = mat3(1);
				vec3 diff = LTC_Evaluate(N, V, pos);
				col  = lcol*(spec + dcol*diff);
				col /= 2.0*PI;
		#else // quad handling
				mat3 Minv = mat3(
					vec3(t1.x, 0, t1.y),
					vec3(  0,  1,    0),
					vec3(t1.z, 0, t1.w)
				);

				vec3 spec = LTC_Evaluate(N, V, pos, Minv, points, twoSided);
				// BRDF shadowing and Fresnel
				spec *= scol*t2.x + (1.0 - scol)*t2.y;

				vec3 diff = LTC_Evaluate(N, V, pos, mat3(1), points, twoSided);
				col  = lcol*(spec + dcol*diff);
		#endif
		
				outColor.rgb = col;
			}
		}

	#else
		// just 1/0 for the shadow frustum. used actualy for the point lights lighting
		if (in_frustum > 0.0 && NdotL > 0.0)
		{
			vec3 l = world - light.position.xyz;
			float dist = sqrt(dot(l, l));
			if (dist < light.range)
			{
				outColor = lighting * vec4(1.0 - dist / light.range) * (1.0 - shadow);
			}
			else
			{
				outColor = vec4(0.0);
			}
		}
		else
		{
			outColor = vec4(0.0);
		}

	#endif
	#endif
	
	#ifdef CALCULATE_VOLUMETRIC
		// experimental lightshafts (volumetric)
		float attenuation = 0.0;
		vec4 projector_color = vec4(0.0);
		float volume_attenuation = volumetric_sample_shadow_spot(light, vFrustum, depth, attenuation, projector_color);
		//outColor.a = projector_color.r * volume_attenuation;
		outColor.a = light.diffuse.a * volume_attenuation;
		//outColor.a = pow(volume_attenuation * light.diffuse.a, 2.0);
	#else
		outColor.a = 0.0;
	#endif
	}

	//outColor = vec4(0.5, 0.5, 0.5, 0.0);//outColor * colorDiffuse;
	//outColor = vec4(1.0);
	//outColor = vec4(texture(s_texture0, vTexcoord0).r);
	//outColor = vec4(world.x);

}