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

    Copyright (C) 2020 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.

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


#include "/lib/head.glsl"

/*DRAWBUFFERS:37*/
layout(location = 0) out vec4 indirectLight;
layout(location = 1) out uvec4 temporalData;

#include "/lib/util/colorspace.glsl"

const int shadowMapResolution   = 3072;     //[512 1024 1536 2048 2560 3072 3584 4096 6144 8192 16384]
const bool shadowHardwareFiltering = false;

const bool colortex7Clear   = false;

in vec2 coord;

flat in vec3 blocklightColor;

#if (!defined DIM || DIM == 1)
    flat in vec3 directLightColor;
#endif

#if (!defined DIM || !defined rtaoSkySample || !defined rtaoEnabled)
    flat in vec3 indirectLightCol;
#endif

uniform sampler2D colortex0;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex5;
uniform sampler2D colortex6;
uniform usampler2D colortex7;

uniform sampler2D depthtex0;
uniform sampler2D depthtex2;

uniform sampler2D noisetex;

uniform int frameCounter;

uniform float near, far;

uniform vec2 skyCaptureResolution;
uniform vec2 taaOffset;
uniform vec2 pixelSize, viewSize;

uniform vec3 cameraPosition, previousCameraPosition;
uniform vec3 lightvec, lightvecView;

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

#ifndef NOSHADOWMAP
    uniform sampler2D shadowtex0;
    uniform sampler2D shadowtex1;
    uniform sampler2D shadowcolor0;
    uniform sampler2D shadowcolor1;

    uniform mat4 shadowModelView, shadowModelViewInverse;
    uniform mat4 shadowProjection, shadowProjectionInverse;
#endif

#if (defined NOSHADOWMAP && defined rsmBounce)
    #undef rsmBounce
#endif


/* ------ includes ------*/
#define FUTIL_LINDEPTH
#define FUTIL_ROT2
#define FUTIL_LIGHTMAP
#include "/lib/fUtil.glsl"
#include "/lib/util/encoders.glsl"

#include "/lib/util/transforms.glsl"

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

    return prevScreenPos;
}


/* ------ various functions ------ */

vec2 unprojectSphere(vec3 dir) {
    vec2 lonlat     = vec2(atan(-dir.x, dir.z), acos(dir.y));
    return lonlat * vec2(rcp(tau), rcp(pi)) + vec2(0.5, 0.0);
}

float diffuseLambert(vec3 normal, vec3 lightDir) {
    float lamb  = dot(normal, lightDir);
        lamb    = saturate(lamb);
    return lamb * rpi;
}

float ditherBluenoise() {
    ivec2 coord = ivec2(fract(gl_FragCoord.xy/256.0)*256.0);
    float noise = texelFetch(noisetex, coord, 0).a;
        noise   = fract(noise+float(frameCounter)/pi);

    return noise;
}
float ditherBluenoise2() {
    ivec2 coord = ivec2(fract(gl_FragCoord.xy/256.0)*256.0);
    float noise = texelFetch(noisetex, coord, 0).a;

        noise   = fract(noise + (frameCounter * 7.0 * rcp(255.0)));

    return noise;
}

float bayer2(vec2 c) { 
    c = 0.5 * floor(c);
    return fract(1.5 * fract(c.y) + c.x);
}
float bayer4(vec2 c) {
    return 0.25 * bayer2 (0.5 * c) + bayer2(c);
}
float bayer8(vec2 c) {
    return 0.25 * bayer4 (0.5 * c) + bayer2(c);
}
float bayer16(vec2 c) {
    return 0.25 * bayer8 (0.5 * c) + bayer2(c);
}
float bayer32(vec2 c) {
    return 0.25 * bayer16(0.5 * c) + bayer2(c);
}
float bayer32() {
    return fract(bayer32(gl_FragCoord.xy) + float(frameCounter) / pi);
}

#define HASHSCALE1 .1031
float hash12(vec2 x) {
    vec3 x3  = fract(vec3(x.xyx) * HASHSCALE1);
        x3  += dot(x3, x3.yzx + 19.19);
    return fract((x3.x + x3.y) * x3.z);
}

float ditherHashNoise() {
    float noise     = hash12(gl_FragCoord.xy);
        noise   = fract(noise + (frameCounter * 7.0 * rcp(255.0)));

    return noise;
}

#ifndef NOSHADOWMAP
#ifndef DIM
float readCloudShadowmap(sampler2D shadowmap, vec3 position) {
    position    = mat3(shadowModelView) * position;
    position   /= cloudShadowmapRenderDistance;
    position.xy = position.xy * 0.5 + 0.5;

    position.xy /= max(viewSize / cloudShadowmapResolution, 1.0);

    return texture(shadowmap, position.xy).a;
}
#endif
#endif

/* ------ sample distributions ------ */

//Fermats Spiral and Spheremap functions from raspberry by rutherin https://www.patreon.com/user?u=12731730
vec2 fermatsSpiralGoldenN(float index, float total) {
	float theta = index * goldenAngle;
	return vec2(sin(theta), cos(theta)) * sqrt(index / total);
}
vec2 fermatsSpiralGoldenS(float index, float total) {
	float theta = index * goldenAngle;
	return vec2(sin(theta), cos(theta)) * sqr(index / total);
}
vec2 sincos(float x) {
    return vec2(sin(x), cos(x));
}

vec3 sphereMap(vec2 a) {
    float phi = a.y * 2.0 * pi;
    float cosTheta = 1.0 - a.x;
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);

    return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
}


#include "/lib/frag/gradnoise.glsl"

#include "/lib/light/warp.glsl"

/* ------ indirect lighting ------ */

#ifdef rsmBounce

const ivec2 shadowmapSize   = ivec2(shadowMapResolution * MC_SHADOW_QUALITY);
const vec2 shadowmapPixel   = 1.0 / vec2(shadowmapSize);

const float ditherSize  = 16.0 * 16.0;

vec3 sceneToShadowProjSpace(vec3 scenePos) {
    vec3 pos    = scenePos + vec3(0.08) * lightvec;
        pos     = viewMAD(shadowModelView, pos);
        pos     = projMAD(shadowProjection, pos);

    return pos;
}

mat2 getRotationMatrix(float angle) {
	float cosine = cos(angle);
	float sine = sin(angle);
	return mat2(cosine, -sine, sine, cosine);
}

vec3 rsmBounceResponse(vec3 shadowClipPos, vec3 shadowPos, vec3 sceneNormal, vec2 noise, uint i, float skyOcclusion) {
    float dither    = noise.x * ditherSize + 0.5;
    float dither2   = dither / ditherSize /*+ (0.5 / ditherSize)*/;

    const uint totalSamples     = rsmExtraSamples * ssrtSPP;

    const float radius  = 16.0 * rsmRange;
    const float radSqr  = radius * radius;
    const float sampleArea  = pi * radSqr / totalSamples;
    const float sampleDistAdd = sqrt((sampleArea / totalSamples) / pi);

    mat2x3 projScale        = mat2x3(shadowProjection[0].x, shadowProjection[1].y, shadowProjection[2].z / 5.0,
                        shadowProjectionInverse[0].x, shadowProjectionInverse[1].y, shadowProjectionInverse[2].z * 5.0);

    vec2 offsetScale        = radius * projScale[0].xy;

    vec3 shadowNormal   = normalize(mat3(shadowModelView) * sceneNormal);
        shadowNormal.z  = -shadowNormal.z;

    vec3 total  = vec3(0.0);

    mat2 rot = getRotationMatrix(ditherSize * goldenAngle);
    vec2 dir = sincos(dither * goldenAngle);

    for (uint j = 0; j < rsmExtraSamples; ++j) {
        uint index  = i * rsmExtraSamples + j;
        //float r     = (float(index) * dither2) / totalSamples;
        //vec2 sampleOffset   = fermatsSpiralGoldenS(float(index) * ditherSize + dither, totalSamples * ditherSize) * offsetScale * r;

        float r = (float(index) + dither2) / totalSamples;
        vec2 sampleOffset = dir * offsetScale * r;
        dir *= rot;

        if (dot(shadowNormal.xy, sampleOffset) <= 0.0) sampleOffset = -sampleOffset;

        vec3 sampleClip     = shadowClipPos;
            sampleClip.xy  += sampleOffset;

        vec2 sampleCoord    = shadowmapWarp(sampleClip.xy) * 0.5 + 0.5;
            sampleClip.z    = texelFetch(shadowtex1, ivec2(sampleCoord * shadowmapSize), 0).x * 2.0 - 1.0;
        vec3 samplePos      = projScale[1] * sampleClip + shadowProjectionInverse[3].xyz;

        float delta         = (shadowClipPos.z - sampleClip.z * 5.0) - 0.02 / 256.0;

        if (delta > 0.0) continue;

        vec3 sampleVector   = samplePos - shadowPos;
        float sampleDistSqr = selfDot3(sampleVector);

        if (sampleDistSqr > radSqr) continue;

            sampleVector    = normalize(sampleVector);
        vec4 sampleData     = texelFetch(shadowcolor1, ivec2(sampleCoord * shadowmapSize), 0);

        vec3 sampleNormal   = sampleData.xyz * 2.0 - 1.0;
            sampleNormal.xy = -sampleNormal.xy;

        float sampleOcclusion = saturate(sampleData.w * 2.0 - 1.0);

        //float diffOut       = saturate(dot(sampleNormal, -sampleVector));
        //float diffBounce    = saturate(dot(shadowNormal, sampleVector));

        float surfaceAngle  = dot(sampleNormal, -shadowNormal);

        if (surfaceAngle < -0.5) continue;

        float diffOut       = saturate(dot(sampleVector, sampleNormal));
            //diffOut         = max(diffOut, saturate(dot(sampleNormal, -sampleVector)) / pi);
        float diffBounce    = saturate(dot(shadowNormal, sampleVector));

        float brdf      = 2.0 * r * diffOut * diffBounce;

        //if (brdf <= 0.0) continue;

        //float falloff   = 1.0 / (sampleDistSqr + sampleDistAdd);

        float sampleDist    = sqrt(sampleDistSqr);

        float falloff       = rcp(sampleDistAdd + sampleDistSqr);
        float falloff1      = rcp((sampleDistAdd + sampleDistSqr) / tau);
            falloff         = mix(falloff, falloff1 / pi, saturate(sampleDist * rcp(1.0 + sampleDist)));

        vec4 colorSample    = texelFetch(shadowcolor0, ivec2(sampleCoord * shadowmapSize), 0);

        if (colorSample.a < 0.97 && colorSample.a > 0.93) continue;

        float occlusionFix  = saturate((sampleOcclusion) - sqrt(1.0 - skyOcclusion));
            occlusionFix    = mix(occlusionFix, 1.0, sqr(1 - saturate(max(sqr(sampleOcclusion), sqr(skyOcclusion)) * halfPi)));

        total += sqr(colorSample.rgb) * (brdf * falloff * sampleArea) * occlusionFix;
    }

    total /= max(rsmExtraSamples, 1);

    return total * halfPi;
}

#endif

#include "/lib/light/indirect/ssrt.glsl"
#include "/lib/light/indirect/rtao.glsl"


/* ------ temporal packing ------ */

struct temporalDataset {
    vec3 indirect;
    vec4 emission;
    float depth;
    float pixelAge;
};

vec4 packRGBM16(vec3 color) {
        color  /= 384.0;

    vec4 rgbm   = vec4(0.0);
        rgbm.a  = saturate(max(max(color.r, color.g), max(color.b, 1e-12)));
        rgbm.a  = ceil(rgbm.a * 65535.0) / 65535.0;
        rgbm.rgb = color / rgbm.a;

    return rgbm;
}

vec3 unpackRGBM16(vec4 inputData) {
    return inputData.rgb * inputData.a * 384.0;
}

vec4 packRGBM8(vec3 color) {
    color  /= 1.0;

    vec4 rgbm   = vec4(0.0);
        rgbm.a  = saturate(max(max(color.r, color.g), max(color.b, 1e-12)));
        rgbm.a  = ceil(rgbm.a * 255.0) / 255.0;
        rgbm.rgb = color / rgbm.a;
        //rgbm.rgb = sqrt(rgbm.rgb);

    return rgbm;
}

vec3 unpackRGBM8(vec4 inputData) {
    return (inputData.rgb) * inputData.a * 1.0;
}
/*
    From Spectrum by Zombye
    Hotfix solution since above's decoding just breaks for reasons I cant be bothered to deal with as this
    part will get completely replaced anyways
*/
vec4 EncodeRGBE8(vec3 rgb) {
	float exponentPart = floor(log2(max(max(rgb.r, rgb.g), max(rgb.b, exp2(-127.0))))); // can remove the clamp to above exp2(-127) if you're sure you're not going to input any values below that
	vec3  mantissaPart = clamp((128.0 / 255.0) * exp2(-exponentPart) * rgb, 0.0, 1.0);
	      exponentPart = clamp(exponentPart / 255.0 + (127.0 / 255.0), 0.0, 1.0);

	return vec4(mantissaPart, exponentPart);
}
vec3 DecodeRGBE8(vec4 rgbe) {
	const float add = log2(255.0 / 128.0) - 127.0;
	return exp2(rgbe.a * 255.0 + add) * rgbe.rgb;
}

temporalDataset mixTemporalDataset(temporalDataset x, temporalDataset y, float weight) {
    temporalDataset result;
    result.indirect     = mix(x.indirect, y.indirect, weight);
    result.emission     = mix(x.emission, y.emission, weight);
    result.depth        = mix(x.depth, y.depth, weight);
    result.pixelAge     = mix(x.pixelAge, y.pixelAge, weight);

    return result;
}

temporalDataset decodeTemporalBuffer(uvec4 inputData) {
    vec3 indirect   = DecodeRGBE8(unpack4x8(inputData.x));
    vec4 emission   = vec4(0.0);

        //indirect.xy = unpack2x16(inputData.x);
        //indirect.zw = unpack2x16(inputData.y);

    //vec2 unpackedZ  = unpack2x16(inputData.z);
        emission.rgb = DecodeRGBE8(unpack4x8(inputData.z));

    vec2 unpackedW  = unpack2x16(inputData.w);
    vec2 unpackedW2 = decode2x8(unpackedW.y);
        emission.a  = unpackedW2.x;

    return temporalDataset(indirect, emission, unpackedW.x, unpackedW2.y);
}

temporalDataset getTemporalDataInterpolated(usampler2D texture, vec2 coord) {
    vec2 pos        = coord * viewSize * indirectResScale - 0.5;
    ivec2 pixelPos  = ivec2(pos);

    vec2 weights    = fract(pos);

    temporalDataset resultX1 = mixTemporalDataset(decodeTemporalBuffer(texelFetch(texture, pixelPos, 0)), decodeTemporalBuffer(texelFetch(texture, pixelPos + ivec2(1, 0), 0)), weights.x);
    temporalDataset resultX2 = mixTemporalDataset(decodeTemporalBuffer(texelFetch(texture, pixelPos + ivec2(0, 1), 0)), decodeTemporalBuffer(texelFetch(texture, pixelPos + ivec2(1, 1), 0)), weights.x);

    return mixTemporalDataset(resultX1, resultX2, weights.y);
}

uvec4 encodeTemporalBuffer(temporalDataset inputData) {
    vec4 indirectRGBM = EncodeRGBE8(inputData.indirect);
    vec4 emissionRGBM = EncodeRGBE8(inputData.emission.rgb);

    uvec4 result    = uvec4(0);
        result.x    = pack4x8(indirectRGBM);
        //result.y    = pack2x16(indirectRGBM.zw);
        result.z    = pack4x8(emissionRGBM);
        result.w    = pack2x16(vec2(inputData.depth, encode2x8(vec2(inputData.emission.w, inputData.pixelAge))));

    return result;
}

#define maxFrames 30.0

#ifdef rtaoEnabled
    /* - */
#endif

#ifdef ssgiToggle
    /* - */
#endif

#if !(defined ssgiToggle || defined ssrtEmissionToggle || defined rtaoEnabled)
    #define noAccumulation
#endif

void main() {
    temporalData    = uvec4(0);
    indirectLight   = vec4(0.0);

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

    if (landMask(sceneDepthL) && saturate(lowresCoord) == lowresCoord) {
        vec2 coord          = saturate(lowresCoord);

        float sceneDepth    = sceneDepthL;
        float sceneDepth2   = texelFetch(depthtex2, pixelPos, 0).x;

        vec3 lightmaps      = saturate(texelFetch(colortex3, pixelPos, 0).xyz);
        vec3 sceneNormal    = texelFetch(colortex6, pixelPos, 0).xyz * 2.0 - 1.0;
        vec3 viewNormal     = mat3(gbufferModelView) * sceneNormal;
        vec3 viewPos        = screenToViewSpace(vec3(coord, sceneDepth));

        bool hand           = sceneDepth < sceneDepth2;

        vec3 scenePos       = viewToSceneSpace(viewPos);

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

        uvec4 historyIn     = texelFetch(colortex7, ivec2(reprojection.xy * viewSize * indirectResScale), 0);

        //temporalDataset history = decodeTemporalBuffer(historyIn);
        temporalDataset history = getTemporalDataInterpolated(colortex7, reprojection.xy);

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

        float currentDistance   = length(viewPos);
        float distanceDelta     = distance(history.depth * far, currentDistance) - abs(cameraMovement.z);
        //float depthRejection    = (offscreen || distanceDelta >= 1.0) ? 0.0 : exp(-max0(distanceDelta - 0.066) * 16.0);
        float depthRejection = (offscreen) ? 0.0 : exp(-max(distanceDelta - 0.2, 0.0) * 3.0);

        temporalDataset current = temporalDataset(vec3(0.0), vec4(0.0), currentDistance / far, 0.0);

        #if (defined ssgiToggle || defined ssrtEmissionToggle)

            #if (DIM == -1 || !defined ssgiToggle)
                #ifdef ssrtEmissionToggle
                    current.emission    = saturate(SSRT_Emission(viewPos, sceneNormal, ditherBluenoise2(), hand));
                #endif
            #else
                ssrtData rtData     = SSRT(viewPos, sceneNormal, ditherHashNoise(), hand, lightmaps.y);

                current.indirect   += rtData.bounce * directLightColor * pi;

            #ifndef DIM
                current.indirect   *= readCloudShadowmap(colortex0, scenePos);
            #endif

                current.emission    = rtData.emission;
            #endif

        #endif

        #ifdef rtaoEnabled
            current.indirect   += getRTAO(viewPos, viewNormal, ditherHashNoise(), hand, lightmaps.y);
        #else
            #ifndef DIM
                current.indirect += cube(lightmaps.y) * indirectLightCol * lightmaps.z;
            #else
                current.indirect += indirectLightCol * lightmaps.z;
            #endif
        #endif

        temporalDataset accumulated = current;

        float accumulationR0        = mix(0.1, 0.01 * euler, sqrt(history.pixelAge));
        float accumulationWeight    = depthRejection;
            accumulationWeight      = saturate(1.0 - accumulationWeight);

        #ifndef noAccumulation
            if (!(offscreen || hand)) {
                float weight    = max(1.0 - depthRejection, mix(0.4, 0.1, history.pixelAge));
                accumulated.indirect    = mix(history.indirect, current.indirect, max(accumulationWeight, accumulationR0));
                accumulated.emission    = mix(history.emission, current.emission, max(accumulationWeight, accumulationR0));
                accumulated.pixelAge    = mix(0.0, saturate(history.pixelAge + rcp(maxFrames)), float(distanceDelta < 0.2));
            }

            temporalData = encodeTemporalBuffer(accumulated);
        #endif

        bool emitter    = texelFetch(colortex2, ivec2(coord * viewSize), 0).z > 0.1;

        vec3 blocklight = getBlocklightMap((emitter ? vec3(v3avg(blocklightColor) / pi) : blocklightColor), lightmaps.x) * blocklightIllum;

        #if (defined ssgiToggle || defined ssrtEmissionToggle)
            blocklight  = blocklight / euler;
            blocklight *= sqr(lightmaps.z);
            blocklight += accumulated.emission.rgb * max3(blocklightColor) * euler;
        #else
            blocklight  = blocklight;
            blocklight *= sqr(lightmaps.z);
        #endif

        #ifdef DIM
            blocklight *= 0.5;
        #endif

        vec3 total      = accumulated.indirect + blocklight;
        indirectLight.rgb   = total + vec3(0.5, 0.7, 1.0) * (0.07 * minLightLuma) * lightmaps.z;
        indirectLight.a     = getLuma(indirectLight.rgb);

        //indirectLight.rgb   = current.emission.rgb * 320.0;
    }
}