/*
====================================================================================================

    Copyright (C) 2021 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

/* RENDERTARGETS: 3,7,11 */
layout(location = 0) out vec3 indirectCurrent;
layout(location = 1) out vec4 historyGData;
layout(location = 2) out vec4 indirectHistory;

#include "/lib/head.glsl"
#include "/lib/util/encoders.glsl"

const bool colortex7Clear   = false;
const bool colortex11Clear   = false;

in vec2 coord;

flat in vec3 blocklightColor;

uniform sampler2D colortex3;
uniform sampler2D colortex4;
uniform sampler2D colortex7;
uniform sampler2D colortex11;

uniform sampler2D depthtex0;
uniform sampler2D depthtex2;

#ifdef ssptEnabled

uniform float far, near;

uniform vec2 pixelSize, viewSize;
uniform vec2 taaOffset;

uniform vec3 cameraPosition, previousCameraPosition;

uniform mat4 gbufferModelView, gbufferModelViewInverse;
uniform mat4 gbufferProjection, gbufferProjectionInverse;
uniform mat4 gbufferPreviousProjection, gbufferPreviousModelView;

#define FUTIL_MAT16
#include "/lib/fUtil.glsl"
#include "/lib/util/transforms.glsl"

/* ------ reprojection ----- */
vec3 reproject(vec3 sceneSpace, bool hand) {
    vec3 prevScreenPos = hand ? vec3(0.0) : cameraPosition - previousCameraPosition;
    prevScreenPos = sceneSpace + prevScreenPos;
    prevScreenPos = viewMAD(gbufferPreviousModelView, prevScreenPos);
    prevScreenPos = viewMAD(gbufferPreviousProjection, prevScreenPos) * (0.5 / -prevScreenPos.z) + 0.5;

    return prevScreenPos;
}

#define maxFrames 40.0

/* ------ ATROUS ------ */

#include "/lib/offset/gauss.glsl"

#define atrousDepthTreshold 0.001
#define atrousDepthOffset 0.25
#define atrousDepthExp 2.0

#define atrousNormalExponent 16

/*
luma exp: big = sharper     now smol = sharp
luma offset: big = moar min blur
*/

#define atrousLumaExp 4.0
#define atrousLumaOffset 8.0

ivec2 clampTexelPos(ivec2 pos) {
    return clamp(pos, ivec2(0.0), ivec2(viewSize));
}

float mean5x5(sampler2D tex, vec2 coord) {
    ivec2 pos           = ivec2(coord * viewSize);
    float sumMean   = 0.0;

    for (int i = 0; i<25; i++) {
        ivec2 deltaPos     = kernelO_5x5[i];

        vec3 col    = texelFetch(tex, clampTexelPos(pos + deltaPos), 0).rgb;
        float lum   = avgOf(col);

        sumMean    += lum;
    }
    sumMean  /= 25.0;

    return sumMean;
}

vec2 computeVariance(sampler2D tex, ivec2 pos) {
    float sumMsqr   = 0.0;
    float sumMean   = 0.0;

    for (int i = 0; i<9; i++) {
        ivec2 deltaPos     = kernelO_3x3[i];

        vec3 col    = texelFetch(tex, clampTexelPos(pos + deltaPos), 0).rgb;
        float lum   = getLuma(col);

        sumMsqr    += sqr(lum);
        sumMean    += lum;
    }
    sumMsqr  /= 9.0;
    sumMean  /= 9.0;

    return vec2(abs(sumMsqr - sqr(sumMean)) * rcp(max(sumMean, 1e-20)), sumMean);
}

vec4 atrousSVGF(sampler2D tex, sampler2D gData, vec2 coord, const int size) {
    ivec2 pos           = ivec2(coord * viewSize);

    vec4 centerData     = texelFetch(gData, pos, 0);
        centerData.xyz  = centerData.xyz * 2.0 - 1.0;
        centerData.w    = (centerData.w) * far;

    vec4 centerColor    = texelFetch(tex, pos, 0);
    float centerLuma    = getLuma(centerColor.rgb);

    //return centerColor;

    vec2 variance       = computeVariance(tex, pos);

    float sigmaL        = rcp(1.0 + atrousLumaExp * variance.x);
    float maxLumDelta   = pi;

    vec4 total          = centerColor;
    float totalWeight   = 1.0;
    float totalWeightSqr = 1.0;

    for (int i = 0; i<25; i++) {
        ivec2 deltaPos      = kernelO_5x5[i];
        if (deltaPos.x == 0 && deltaPos.y == 0) continue;

        ivec2 samplePos     = pos + deltaPos * size;

        bool valid          = all(greaterThanEqual(samplePos, ivec2(0))) && all(lessThan(samplePos, ivec2(viewSize)));

        if (!valid) continue;

        vec4 currentData    = texelFetch(gData, clampTexelPos(samplePos), 0);
            currentData.xyz = currentData.xyz * 2.0 - 1.0;
            currentData.w   = (currentData.w) * far;

        float depthDelta    = abs(currentData.w - centerData.w) * atrousDepthExp;

        float weight = pow(max(0.0, dot(currentData.xyz, centerData.xyz)), atrousNormalExponent * 1.0);

        //if (weight < 1e-20) continue;

        vec4 currentColor   = texelFetch(tex, clampTexelPos(samplePos), 0);
        float currentLuma   = getLuma(currentColor.rgb);

        float lumaDelta     = abs(centerLuma - currentLuma) / clamp(variance.y, 1e-2, 2e2);

            weight         *= exp(-depthDelta - sigmaL * clamp(lumaDelta, 0.0, maxLumDelta));

        //accumulate stuff
        total       += currentColor * weight;

        totalWeight += weight;
    }

    //compensate for total sampling weight
    total *= rcp(max(totalWeight, 1e-25));

    return total;
}

#endif

void main() {
    historyGData    = vec4(1.0);
    indirectHistory = vec4(0.0);
    indirectCurrent = stex(colortex3).rgb;

    #ifdef ssptEnabled

    vec2 lowresCoord    = coord / indirectResScale;
    ivec2 pixelPos      = ivec2(floor(coord * viewSize) / indirectResScale);
    float sceneDepth    = texelFetch(depthtex0, pixelPos, 0).x;

    if (landMask(sceneDepth) && saturate(lowresCoord) == lowresCoord) {
        vec2 coord      = saturate(lowresCoord);
        vec2 scaledCoord = coord * indirectResScale;

        vec3 viewPos    = screenToViewSpace(vec3(coord, sceneDepth));
        vec3 scenePos   = viewToSceneSpace(viewPos);

        bool hand       = sceneDepth < texelFetch(depthtex2, pixelPos, 0).x;

        vec3 reprojection   = reproject(scenePos, hand);
        bool offscreen      = saturate(reprojection) != reprojection;

        vec3 cameraMovement = mat3(gbufferModelView) * (cameraPosition - previousCameraPosition);

        vec4 currentGData   = texture(colortex4, scaledCoord);
            currentGData.rgb = currentGData.rgb * 2.0 - 1.0;
            currentGData.a  = length(viewPos);

        historyGData        = texture(colortex7, reprojection.xy * indirectResScale);
        historyGData.rgb    = historyGData.rgb * 2.0 - 1.0;

        float distanceDelta = distance(historyGData.a * far, currentGData.a) - abs(cameraMovement.z);
        float depthRejection = (offscreen) ? 0.0 : exp(-max(distanceDelta - 0.2, 0.0) * pi);

        indirectHistory     = texture(colortex11, reprojection.xy * indirectResScale);
        indirectHistory.a   = saturate(indirectHistory.a);

        float normalWeight  = sqr(saturate(dot(historyGData.rgb, currentGData.rgb)));
            normalWeight    = mix(1.0, normalWeight, 1.0 - depthRejection * sqr(indirectResScale));

        vec4 filteredSample = atrousSVGF(colortex3, colortex4, scaledCoord, 1);

        indirectCurrent     = filteredSample.rgb;

        //float lumaMean      = mean5x5(colortex3, coord);
        //float lumaDelta     = distance(texture(colortex12, reprojection.xy).x, avgOf(indirectCurrent.rgb)) / lumaMean;

        float accumulationR0        = mix(0.1, 0.01 * euler, sqrt(indirectHistory.a));
        float accumulationWeight    = depthRejection;
            //accumulationWeight     *= normalWeight;
            //accumulationWeight     *= mix(1.0, exp(-clamp(lumaDelta, 0.0, pi)), indirectHistory.a / pi);
            accumulationWeight      = saturate(1.0 - accumulationWeight);

        if (!offscreen) {
            indirectHistory.rgb     = mix(indirectHistory.rgb, indirectCurrent, max(accumulationWeight, accumulationR0));
            indirectHistory.a       = mix(0.0, saturate(indirectHistory.a + rcp(maxFrames)), float(distanceDelta < 0.16));
        } else {
            indirectHistory         = vec4(indirectCurrent, 0.0);
        }

        indirectCurrent     = indirectHistory.rgb;

        historyGData.rgb    = currentGData.rgb * 0.5 + 0.5;
        historyGData.a      = currentGData.a / far;

        indirectCurrent    += filteredSample.a * blocklightColor / tau;

    }

    #endif

    indirectCurrent     = clamp16F(indirectCurrent);
    indirectHistory     = clamp16F(indirectHistory);
}