#version 450
#include "common.glsl"
#include "fog.glsl"

#ifndef FXAA_REDUCE_MIN
    #define FXAA_REDUCE_MIN   (1.0/ 128.0)
#endif
#ifndef FXAA_REDUCE_MUL
    #define FXAA_REDUCE_MUL   (1.0 / 8.0)
#endif
#ifndef FXAA_SPAN_MAX
    #define FXAA_SPAN_MAX     8.0
#endif

layout(
    set = 1,
    binding = GLSL_COMBINE_COLOR_INDEX
) uniform sampler2D uColorSampler;

layout (
    set = 1,
    binding = GLSL_COMBINE_LIGHT_INDEX
) uniform sampler2D uLightSampler;

layout (
    set = 1,
    binding = GLSL_COMBINE_PARTICLES_INDEX
) uniform sampler2D uParticlesSampler;

layout (
    set = 1,
    binding = GLSL_COMBINE_NORMALS_INDEX
) uniform sampler2D uNormalsSampler;

layout (
    set = 1,
    binding = GLSL_COMBINE_POS_SPEC_INDEX
) uniform sampler2D uPosSpecSampler;

layout (
    set = 1,
    binding = GLSL_COMBINE_GOD_RAYS_INDEX
) uniform sampler2D uGodRaysSampler;

layout(location = 0) in vec2 passFar;
layout(location = 1) in vec2 passScreenSize;
layout(location = 2) flat in int passDebugMode;
layout(location = 3) flat in int passFxaaEnabled;
layout(location = 4) in vec2 passSunPos;
layout(location = 5) flat in int passGodRaySamples;
layout(location = 6) in vec3 passSkyColor;

layout(location = 0) out vec4 outColor;

vec3 hableTonemap(vec3 x) {
    const float A = 0.15;
    const float B = 0.50;
    const float C = 0.10;
    const float D = 0.20;
    const float E = 0.02;
    const float F = 0.30;
    const float W = 11.2;
    vec3 curr = ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
    float white = ((W*(A*W+C*B)+D*E)/(W*(A*W+B)+D*F))-E/F;
    return curr/white;
}

vec4 computeSolid(vec2 screenPos)
{
    vec4 worldPos = texture(uPosSpecSampler, screenPos);
    float fogDistHorizontal = length(worldPos.xyz) - (passFar.r - 16.0);
    float fogDistVertical = abs(worldPos.z) - (passFar.g - 16.0);

    float fogFactorHorizontal = getFogFactor(FOG_DENSITY, fogDistHorizontal);
    float fogFactorVerical = getFogFactor(FOG_DENSITY, fogDistVertical);
    float fogFactor = max(fogFactorHorizontal, fogFactorVerical);

    vec4 color = texture(uColorSampler, screenPos);
    vec4 light = texture(uLightSampler, screenPos);
    light.a = 1.0;

    vec4 outColor = color * light;
    outColor = mix(outColor, vec4(passSkyColor, 1.0), fogFactor);
    outColor.rgb = hableTonemap((color * light).rgb);
    return outColor;
}

vec4 computeParticle(vec2 screenPos)
{
    return texture(uParticlesSampler, screenPos);
}

vec4 computeGodRays(vec2 screenPos)
{
    const float exposure = 0.1;
    const float baseDecay = 0.07506059167;
    const int maxSamples = 100;

	vec2 deltatexCoord = screenPos - passSunPos;
	deltatexCoord *= 1.0 / float(passGodRaySamples);
	float illuminationDecay = 1.0f;
    float decay = pow(baseDecay, 1.0 / float(passGodRaySamples));

	vec4 godRayColor = vec4(0.0);
	for (int i = 0; i < maxSamples; i++)
	{
        if (i >= passGodRaySamples)
        {
            break;
        }

		vec4 samp = texture(uGodRaysSampler, screenPos)*0.4;
		samp *= illuminationDecay;
		godRayColor += samp;
		illuminationDecay *= decay;
        screenPos -= deltatexCoord;
	}

    godRayColor.a = 0.0;
    return godRayColor * exposure;
}

vec4 computeRawPixel(vec2 screenPos)
{
    vec4 rawColor = vec4(0.0);

    if (passDebugMode == GLSL_DEBUGMODE_OFF)
    {        
        rawColor = alphaBlend(rawColor, computeSolid(screenPos));
        rawColor += computeGodRays(screenPos);
        rawColor = alphaBlend(rawColor, computeParticle(screenPos));
    }
    else if (passDebugMode == GLSL_DEBUGMODE_COLORS)
    {
        rawColor = alphaBlend(rawColor, texture(uColorSampler, screenPos));
    }
    else if (passDebugMode == GLSL_DEBUGMODE_NORMALS)
    {
        rawColor = alphaBlend(rawColor, (0.5 * (texture(uNormalsSampler, screenPos) + 1.0)) * computeSolid(screenPos).aaaa);
    }
    else if (passDebugMode == GLSL_DEBUGMODE_SPECULAR)
    {
        rawColor = alphaBlend(rawColor, texture(uPosSpecSampler, screenPos).wwww * computeSolid(screenPos).aaaa);
    }
    else if (passDebugMode == GLSL_DEBUGMODE_LIGHT)
    {
        vec4 lightValue = texture(uLightSampler, screenPos);
        lightValue.rgb = hableTonemap(lightValue.rgb);
        rawColor = alphaBlend(rawColor, lightValue * computeSolid(screenPos).aaaa);
    }

    rawColor.a = 1.0;
    return rawColor;
}

vec4 fxaa()
{
    vec2 inverseVP = 1.0 / passScreenSize;
    vec2 v_rgbNW = (gl_FragCoord.xy + vec2(-1.0, -1.0)) * inverseVP;
    vec2 v_rgbNE = (gl_FragCoord.xy + vec2(1.0, -1.0)) * inverseVP;
    vec2 v_rgbSW = (gl_FragCoord.xy + vec2(-1.0, 1.0)) * inverseVP;
    vec2 v_rgbSE = (gl_FragCoord.xy + vec2(1.0, 1.0)) * inverseVP;
    vec2 v_rgbM = vec2(gl_FragCoord.xy * inverseVP);

    vec4 color;
    vec3 rgbNW = computeRawPixel(v_rgbNW).xyz;
    vec3 rgbNE = computeRawPixel(v_rgbNE).xyz;
    vec3 rgbSW = computeRawPixel(v_rgbSW).xyz;
    vec3 rgbSE = computeRawPixel(v_rgbSE).xyz;
    vec4 texColor = computeRawPixel(v_rgbM);
    vec3 rgbM  = texColor.xyz;
    vec3 luma = vec3(0.299, 0.587, 0.114);
    float lumaNW = dot(rgbNW, luma);
    float lumaNE = dot(rgbNE, luma);
    float lumaSW = dot(rgbSW, luma);
    float lumaSE = dot(rgbSE, luma);
    float lumaM  = dot(rgbM,  luma);
    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
    
    vec2 dir;
    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));
    
    float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
                        (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
    
    float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
    dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
            max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
            dir * rcpDirMin)) * inverseVP;
    
    vec3 rgbA = 0.5 * (
        computeRawPixel(gl_FragCoord.xy * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
        computeRawPixel(gl_FragCoord.xy * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
    vec3 rgbB = rgbA * 0.5 + 0.25 * (
        computeRawPixel(gl_FragCoord.xy * inverseVP + dir * -0.5).xyz +
        computeRawPixel(gl_FragCoord.xy * inverseVP + dir * 0.5).xyz);

    float lumaB = dot(rgbB, luma);
    if ((lumaB < lumaMin) || (lumaB > lumaMax))
    {
        color = vec4(rgbA, texColor.a);
    }
    else
    {
        color = vec4(rgbB, texColor.a);
    }

    return color;
}

void main()
{
    if (passFxaaEnabled != 0)
    {
        outColor = fxaa();
    }
    else
    {
        outColor = computeRawPixel(gl_FragCoord.xy / passScreenSize);
    }
}