#version 330 core

uniform sampler2D depth_buffer;
uniform sampler2D normal_depth;
uniform sampler2D color_buffer;
uniform sampler2D rotate;
uniform mat4 ProjectionMatrixInverse;
uniform float projCoeffs[5];

uniform vec4 CameraRange; // near, far, (far - near), far*near
uniform vec2 ScreenSize;
uniform float radius;
uniform float attBias;
uniform float attScale;
uniform float giScale;
uniform float attPower;
uniform float depthScale;
uniform vec3 gi_samples[16];
uniform vec2 TexelSize;

in vec2 uv;

layout (location = 0) out vec4 frag_color;

float linearizeDepth(in float z)
{
	return (2.0 * CameraRange.x) / (CameraRange.y + CameraRange.x - z * CameraRange.z);
}

vec3 readNormal(in vec2 coord)
{
	return texture(normal_depth, coord).xyz;
}

vec3 calcPosition(in vec2 coord)
{
	float d = texture(depth_buffer, coord).x;
#if 1
	vec4 wpos;
	wpos.x = (coord.x - 0.5)*2.0*projCoeffs[0];
	wpos.y = (coord.y - 0.5)*2.0*projCoeffs[1];
	wpos.z = projCoeffs[2];
	wpos.w = d*projCoeffs[3] + projCoeffs[4];
	return wpos.xyz / wpos.w;
#else
	vec4 wpos = vec4((coord.x - 0.5)*2.0, (coord.y - 0.5)*2.0, d, 1.0);
	wpos = ProjectionMatrixInverse * wpos;
	return wpos.xyz / wpos.w;
#endif
}

void getDiffNormalColor(in vec2 coord, in vec3 pos, out vec4 diff, out vec3 normal, out vec3 color)
{
	diff.xyz = calcPosition(coord) - pos;
	diff.w = length(diff.xyz);
	diff.xyz /= diff.w;
	normal = readNormal(coord);
	color = texture(color_buffer, coord).rgb;
}

float aoFactor(in vec4 diff, in vec3 cnorm, in vec3 dnorm)
{
	return (1.0 - clamp(dot(dnorm, -diff.xyz),0.0,1.0)) *
		clamp(dot( cnorm, diff.xyz ), 0.0, 1.0) * (1.0 - 1.0/sqrt(1.0/(diff.w*diff.w) + 1.0));
}


float giFactor(in vec4 diff, in vec3 cnorm, in vec3 dnorm)
{
	return clamp(dot(dnorm, -diff.xyz), 0.0, 1.0) *
		clamp(dot(cnorm, diff.xyz), 0.0, 1.0) / (diff.w*diff.w + 1.0);
}


void main()
{
	vec3 n = readNormal(uv);
	vec3 p = calcPosition(uv);
	vec3 col = texture(color_buffer, uv).rgb;

	vec2 fres = vec2(ScreenSize.x/32.0, ScreenSize.y/32.0);
	vec3 random = texture(rotate, uv*fres.xy).xyz*2.0 - vec3(1.0);

	float ao = 0.0;
	vec3  gi = vec3(0.0);
	float incx = TexelSize.x*radius;
	float incy = TexelSize.y*radius;
	float pw = incx;
	float ph = incy;
	float cdepth = texture(depth_buffer, uv).r;

	for (int i=0; i < 3; i++)
	{
		float npw = (pw + 0.0007*random.x) / cdepth;
		float nph = (ph + 0.0007*random.y) / cdepth;

		vec4 diff;
		vec3 dnorm, dcol;

		getDiffNormalColor(uv + vec2(npw,nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(npw,-nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(-npw,nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(-npw,-nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(0.0,nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(0.0,-nph), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(npw,0.0), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		getDiffNormalColor(uv + vec2(-npw,0.0), p, diff, dnorm, dcol);
		ao +=  aoFactor(diff, n, dnorm);
		gi +=  giFactor(diff, n, dnorm) * dcol;

		pw += incx;
		ph += incy;
	}

	ao = pow(1.0 - (ao/24.0), attPower)*attScale + attBias;
	gi = (gi/24.0) * giScale;

	//frag_color.rgb = (col + gi)*ao;
	frag_color.rgb = col*ao + gi;
	frag_color.a = ao;
	//frag_color.rgb = random*0.5 + vec3(0.5);
	//frag_color = vec4(gi, ao);
	//frag_color = vec4(ao);
	//frag_color = vec4( linearizeDepth(cdepth) );
}
