#version 430

#define LIGHT_PROPERTIES_BINDING 1
#define MATERIAL_PROPERTIES_BINDING 1

uniform sampler2D  sDepth;
uniform usampler2D sNormalMaterial;
uniform sampler2D  sAlbedo;
uniform usampler2D sMetalnessRoughnessMaterialTags;	// metalness roughness material index
uniform samplerCube sEnviromentMap;
uniform sampler2D sRaytrace;

#include <shaders/materials/commons_deferred.glsl>

struct DeferredRenderLightsParams
{
	float env_map_intensity;
};

layout(std140, row_major) uniform BasicDeferredParamsBuffer{
	BasicDeferredParams basic_params;
};

layout(std140, row_major) uniform DeferredRenderLightsParamsBuffer {
	DeferredRenderLightsParams render_lights_params;
};

uniform sampler2D s_LTC1;
uniform sampler2D s_LTC2;
uniform sampler2DArray s_BlueNoise;

in vec2 vTexcoord0;
in vec4 vFrustum;

uniform float g_global_time;

// output

out vec4 outColor;

#include <shaders/commons_hlsl.glsl>
#include <shaders/materials/commons.glsl>
#include <shaders/deferred/lighting_support.glsl>

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

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

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 <shaders/materials/noise/noise3d.glsl>
#include "support/volumetric_component.glsl"

// all the positions are in world coords
vec3 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(light_pos - pos), normalize(normal)), -normalize(cam_pos - pos))), 14.0) * light.diffuse.xyz;

	return vec3(vec3(d) * light.diffuse.xyz + specular);
}

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

#if 0
void getPBRParams(PBRFactors params, out float3 diffuseColor, out float3 specularColor, out float perceptualRoughness, out float alpha)
{
    // Metallic and Roughness material properties are packed together
    // In glTF, these factors can be specified by fixed scalar values
    // or from a metallic-roughness map
    alpha = 0.0;
    perceptualRoughness = 0.0;
    diffuseColor = float3(0.0, 0.0, 0.0);
    specularColor = float3(0.0, 0.0, 0.0);
    float metallic = 0.0;
    float3 f0 = float3(0.04, 0.04, 0.04);

    float4 baseColor = getBaseColor(Input, params);

#ifdef MATERIAL_SPECULARGLOSSINESS
    float4 sgSample = getSpecularGlossinessTexture(Input);
    perceptualRoughness = (1.0 - sgSample.a * params.glossinessFactor); // glossiness to roughness
    f0 = sgSample.rgb * params.specularFactor; // specular

    // f0 = specular
    specularColor = f0;
    float oneMinusSpecularStrength = 1.0 - max(max(f0.r, f0.g), f0.b);
    diffuseColor = baseColor.rgb * oneMinusSpecularStrength;

#ifdef DEBUG_METALLIC
    // do conversion between metallic M-R and S-G metallic
    metallic = solveMetallic(baseColor.rgb, specularColor, oneMinusSpecularStrength);
#endif // ! DEBUG_METALLIC
#endif // ! MATERIAL_SPECULARGLOSSINESS

#ifdef MATERIAL_METALLICROUGHNESS
    // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
    // This layout intentionally reserves the 'r' channel for (optional) occlusion map data
    float4 mrSample = getMetallicRoughnessTexture(Input);
    perceptualRoughness = mrSample.g * params.roughnessFactor;
    metallic = mrSample.b * params.metallicFactor;

    diffuseColor = baseColor.rgb * (float3(1.0, 1.0, 1.0) - f0) * (1.0 - metallic);
    specularColor = lerp(f0, baseColor.rgb, metallic);
#endif // ! MATERIAL_METALLICROUGHNESS

    perceptualRoughness = clamp(perceptualRoughness, 0.0, 1.0);

    alpha = baseColor.a;
}
#endif

float computeLODFromRoughness(float perceptualRoughness)
{
	return perceptualRoughness * 4.0;
}

vec3 ibl(vec3 n, vec3 v, vec3 diffuseColor, vec3 f0, vec3 f90, float perceptualRoughness)
{
    vec3 r = reflect(v, n);
    vec3 Ld = textureLod(sEnviromentMap, r, 6.0).rgb * diffuseColor; // sample "diffuse" LOD
#if 0
    float lod = computeLODFromRoughness(perceptualRoughness);
    vec3 Lld = textureCube(prefilteredEnvMap, r, lod);
    vec2 Ldfg = textureLod(dfgLut, vec2(dot(n, v), perceptualRoughness), 0.0).xy;
    vec3 Lr =  (f0 * Ldfg.x + f90 * Ldfg.y) * Lld;
    return Ld + Lr;
#else
	float lod = computeLODFromRoughness(perceptualRoughness);
	vec3 fake_spec = textureLod(sEnviromentMap, r, lod).rgb;
	return (Ld + fake_spec) * render_lights_params.env_map_intensity;
#endif
}


void main() {
	ivec2 screen_pos = ivec2(gl_FragCoord.xy);

	vec4 baseColor = texture(sAlbedo, vTexcoord0);
	uint encoded_normal_material = texture(sNormalMaterial, vTexcoord0).r;
	vec3 vNorm = decode_normal(encoded_normal_material);
	int materialId = decode_material(encoded_normal_material);

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

	outColor = vec4(0.0);

	float metalness;
	float roughness;
	uint material;
	decode_metalness_roughness_material(textureLod(sMetalnessRoughnessMaterialTags, vTexcoord0, 0.0).rg, metalness, roughness, material);
	vec3 specularColor;
	vec3 diffuseColor;

	// Assume base color is 'pure' color and should be modulated by material color
	baseColor.rgb *= materials.material_properties[material].diffuse.rgb;

	// NOTE: because the model of PBR we use, and it allowing for 0...1 value for base color it doesn't play
	// well with our totally mixed up pipe. For RT we do a hacky job and use color and magnitude separately.
	// How this is going to play? No idea

	vec3 base_emissive = vec3(0.0);
	bool is_raytraced = (materials.material_properties[material].flags & (MaterialFlag_Raytrace | MaterialFlag_Reflective)) != 0;
	if (is_raytraced)
	{
		float color_magnitude = length(baseColor.rgb);
		if (color_magnitude > 1.0)
		{
			base_emissive = baseColor.rgb - baseColor.rgb / color_magnitude;
			baseColor.rgb -= base_emissive;
		}
		baseColor.rgb = saturate(baseColor.rgb);
	}

	{
		vec3 f0 = vec3(0.04, 0.04, 0.04);
		float metallic = metalness;
		diffuseColor = baseColor.rgb * (vec3(1.0, 1.0, 1.0) - f0) * (1.0 - metallic);
		specularColor = mix(f0, baseColor.rgb, metallic);
	}

	//outColor.rgb = specularColor;
	//return;

	// Roughness is authored as perceptual roughness; as is convention,
    // convert to material roughness by squaring the perceptual roughness [2].
	float alphaRoughness = roughness * roughness;

	vec3 specularEnvironmentR0 = specularColor.rgb;
    // Anything less than 2% is physically impossible and is instead considered to be shadowing. Compare to "Real-Time-Rendering" 4th editon on page 325.
    float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
    vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * clamp(reflectance * 25.0, 0.0, 1.0);

	MaterialInfo materialInfo =
    {
        roughness,
        specularEnvironmentR0,
        alphaRoughness,
        diffuseColor,
        specularEnvironmentR90,
        specularColor
    };

	vec3 view = normalize(basic_params.camera_position.xyz - world.xyz);

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

		// remove when we use the proxy object... -- kiero
		// NOTE: also, dont check for non-volumetric lights

		if ((light.type & LightType_Spot) != 0)
		{
			if ((light.type & LightType_Volumetric) != 0)
			{
				Cone cone = Cone(light.cutoff * 1.0, light.range * 1.5, light.position.xyz, light.direction.xyz);
				Ray ray = Ray(basic_params.camera_position.xyz, normalize(vFrustum.xyz));
				float vv = 0.0;
				if (intersect_cone(cone, ray, vv) == false)
					continue;
			}
		}

		vec3 pointToLight = light.position.xyz - world.xyz;
		if ((light.type & LightType_Directional) != 0)
			pointToLight = -light.direction.xyz;

		float NdotL = dot(vNorm, normalize(pointToLight));

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

		//vec3 lighting = calculate_lighting_world(light, world, vNorm, light.position.xyz, basic_params.camera_position.xyz, NdotL);
		vec3 lighting = getPointShade(pointToLight, materialInfo, vNorm, view) * light.intensity * light.diffuse.rgb;

		float shadow = 0.0;
		bool calculate_shadows = ((materials.material_properties[material].flags & MaterialFlag_ShadowReceive) != 0)
		  && ((light.type & LightType_Shadowcasting) != 0);

		if ((light.type & (LightType_Spot | LightType_Directional)) != 0 && calculate_shadows)
		{
			vec4 vShadowCoords = light.mat_shadow_p[0] * light.mat_shadow_mv * vec4(world.xyz, 1.0);

			// shadows
			float in_frustum = 0.0;

			if ((materialId & MATERIAL_ID_MASK_ATTR) == ATTR_BACKGROUND) // TODO: stencil could do this perfectly fine..
			{
				shadow = 0.0;
				lighting = vec3(1.0);
			}
			else
			{
				// TODO: Make this configurable. We don't need contact shadows for all the lights as they are expensive
				float penumbraSampleNoise = texelFetch(s_BlueNoise, ivec3(screen_pos.xy & ivec2(127), 0), 0).r;
				float penumbra = PenumbraFromOccluderSearch(
					LightShadowmapSamplers[light.shadowmap_sampler0],
					penumbraSampleNoise,
					vShadowCoords,
					max(0.5, roughness * light.roughness_modifier) / 1024.0,
					8,
					materials.material_properties[material].shadowmap_bias);
				
				//shadow = sampleShadowPCF(LightShadowmapCmpSamplers[light.shadowmap_sampler0], vShadowCoords, in_frustum, 8);
				shadow = sampleShadowPCFNoiseOffset(
					LightShadowmapCmpSamplers[light.shadowmap_sampler0],
					vShadowCoords,
					screen_pos,
					in_frustum,
					12,
					max(0.5, penumbra * roughness * light.roughness_modifier) / 1024.0,
					materials.material_properties[material].shadowmap_bias);

				//lighting.rgb = vec3(penumbra) + 0.01;
				//shadow = 0.0;
				//shadow = clamp(shadow, 0.0, 1.0);
				//light_color.rgb = vec3(penumbra);
			}

			if (NdotL < 0.0)
				shadow = max(shadow, smoothstep(0.0, -0.05, NdotL));

			//shadow = 0.0;

		}

		// NOTE: This is a kludge for very bright lights until we get temporal/spatial filtering for
		// shadows. This kindof tweaks the falloff for the filtering. It only adjusts the curve, so
		// should not affect fully lit/shadowed surfaces!
		//if (shadow > 0.01)
		//	shadow = pow(shadow, 1.0 / max(1.0, 0.0005 * light.intensity));
		light_color = lighting.rgb * (1.0 - shadow);
		//light_color = vec3(NdotL);

		if ((light.type & (LightType_Spot | LightType_Attenuating)) == (LightType_Spot | LightType_Attenuating))
		{
			float attenuation = light_calculate_spot_attenuation(light, world.xyz);
			light_color.rgb *= attenuation;
		}

		// 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)
			{
				light_color.rgb = normalize(light.diffuse.rgb) * light.intensity;
			}
			else
			{
				Rect rect;
				InitRect(light, rect);

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

				float ltc_roughness = roughness;
				float ltc_intensity = light.intensity;

				vec3 dcol = light.diffuse.rgb;
				vec3 scol = dcol;

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

				vec3 N = vNorm;
				vec3 V = normalize(basic_params.camera_position.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_DiffuseOnly(N, pos, mat3(1), points, twoSided);
				vec3 diff = LTC_Evaluate(N, V, pos, mat3(1), points, twoSided);
				col  = ltc_intensity * (spec * specularColor.rgb + dcol * diff * diffuseColor.rgb);
		#endif
		
				light_color.rgb = col;
			}

			if ((light.type & (LightType_Attenuating)) == (LightType_Attenuating))
			{
				float attenuation = light_calculate_area_attenuation(light, world.xyz);
				light_color.rgb *= attenuation;
			}
		}

		// raytyraced surfaces are hacky as fuck. they already had reflections traced, so if we are in shadow, we still contribute
		if (is_raytraced)
		{
			vec4 raytraceColor = texture(sRaytrace, vTexcoord0).rgba;
			light_color.rgb = light_color.rgb * (1.0 - raytraceColor.a) + specularColor.rgb * raytraceColor.rgb;
			//light_color.rgb = specularColor.rgb * raytraceColor.rgb;
		}

		if ((light.type & LightType_Volumetric) != 0)
		{
			// 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);
			//light_color.a = projector_color.r * volume_attenuation;
			outColor.a += light.diffuse.a * volume_attenuation;
			//light_color.a = pow(volume_attenuation * light.diffuse.a, 2.0);
		}

		outColor.rgb += light_color;
	}

	outColor.rgb += base_emissive.rgb;
	outColor.rgb += ibl(vNorm, view, materialInfo.diffuseColor.rgb, specularEnvironmentR0, specularEnvironmentR90, materials.material_properties[material].roughness) * 0.1;

	// This is 'artsy' as fuck
	if ((materialId & MATERIAL_ID_MASK_ATTR) != ATTR_BACKGROUND)
	{
		outColor.rgb *= materials.material_properties[material].emissive.rgb * materials.material_properties[material].emissive_factor * baseColor.rgb + 1.0;
		outColor.rgb += materials.material_properties[material].emissive.rgb * materials.material_properties[material].emissive_factor * baseColor.rgb;
	}

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

}
