/********************************************************
    © 2020 Continuum Graphics LLC. All Rights Reserved
 ********************************************************/

#include "/../ContinuumLib/Syntax.glsl"

#define Blur_glsl 38
#define DynamicShaderStage Blur_glsl


varying vec2 texcoord;
varying float centerDepth;

/***********************************************************************/
#if defined vsh

uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;

uniform sampler2D depthtex0;

uniform float fov;

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

void main() {
    texcoord = gl_Vertex.xy;
    centerDepth = texture(depthtex0, vec2(0.5)).r;

    gl_Position = vec4(gl_Vertex.xy * 2.0 - 1.0, 0.0, 1.0);
}

#endif
/***********************************************************************/



/***********************************************************************/
#if defined fsh

#include "/../ContinuumLib/Debug.glsl"

uniform sampler2D colortex0;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D depthtex0;
uniform sampler2D depthtex1;
uniform sampler2D depthtex2;

uniform mat4 gbufferProjectionInverse;

uniform vec2 viewDimensions;
uniform vec2 pixelSize;

uniform float aspectRatio;
uniform float fov;

/* DRAWBUFFERS:0123 */
layout (location = 0) out vec4 Buffer0;
layout (location = 1) out vec4 Buffer1;
layout (location = 2) out vec4 Buffer2;
#define LAYOUT_0 Buffer0
#include "/../ContinuumLib/Exit.glsl"

const bool colortex3MipmapEnabled = true;

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

float ScreenToViewSpaceDepth(float depth) {
    depth = depth * 2.0 - 1.0;
    return -1.0 / (depth * gbufferProjectionInverse[2][3] + gbufferProjectionInverse[3][3]);
}

float CalculateFocus(float depth) {
    const float focalLength = CAMERA_FOCAL_LENGTH / 1000.0;
    const float aperture    = (CAMERA_FOCAL_LENGTH / CAMERA_APERTURE) / 1000.0;

    #if CAMERA_FOCUS_MODE == 0
        float focus = ScreenToViewSpaceDepth(centerDepth) - CAMERA_AUTO_FOCAL_OFFSET;
    #else
        float focus = -CAMERA_FOCAL_POINT;
    #endif

    return aperture * (focalLength * (focus - depth)) / (focus * (depth - focalLength));
}

vec2 CalculateDistOffset(const vec2 prep, const float angle, const vec2 offset) {
    return offset * angle + prep * dot(prep, offset) * (1.0 - angle);
}

vec3 DepthOfField() { //OPTIMISATION: Add circular option for lower end hardware. TODO: Look over for accuracy and speed.
    #ifndef DOF
        return DecodeColor(textureLod(colortex3, texcoord, 0).rgb);
    #endif

    if (texture(depthtex2, texcoord).x > texture(depthtex1, texcoord).x) return DecodeColor(textureLod(colortex3, texcoord, 0).rgb);

    vec3 dof = vec3(0.0);
    vec3 weight = vec3(0.0);

    float r = 1.0;
    const mat2 rot = mat2(
        cos(goldenAngle), -sin(goldenAngle),
        sin(goldenAngle),  cos(goldenAngle)
    );

    // Lens specifications referenced from Sigma 32mm F1.4 art.
    // Focal length of 32mm (assuming lens does not zoom), with a diaphram size of 25mm at F1.4.
    // For more accuracy to lens settings, set blades to 9.
    const float aperture  = (CAMERA_FOCAL_LENGTH / CAMERA_APERTURE);

    float depth = ScreenToViewSpaceDepth(texture(depthtex0, texcoord).x);
    float pcoc = CalculateFocus(depth);

    //vec2 maxPos = CalculateDistOffset(prep, angle, (r - 1.0) * sampleAngle * pcoc) * distOffsetScale;

    //float depth = ScreenToViewSpaceDepth(texture(depthtex0, texcoord + maxPos).x);
    //float pcoc = CalculateFocus(depth);

    vec2 sampleAngle = vec2(0.0, 1.0);

    const float sizeCorrect   = 1.0 / (sqrt(DOF_SAMPLES) * 1.35914091423);
    const float apertureScale = sizeCorrect * aperture;

    float lod = log2(abs(pcoc) * max(viewDimensions.x, viewDimensions.y) * apertureScale * aspectRatio);

    vec2 distOffsetScale = apertureScale * vec2(1.0, aspectRatio);

    vec2 toCenter = texcoord.xy - 0.5;
    vec2 prep = normalize(vec2(toCenter.y, -toCenter.x));
    float lToCenter = length(toCenter);
    float angle = cos(lToCenter * DISTORTION_BARREL);

    for(int i = 0; i < DOF_SAMPLES; ++i) {
        r += 1.0 / r;
        sampleAngle = rot * sampleAngle;

        vec2 rSample = (r - 1.0) * sampleAngle;

        vec2 pos = CalculateDistOffset(prep, 1.0, rSample) * sizeCorrect;
        vec3 bokeh = texture2D(colortex0, pos * -0.25 + vec2(0.5, 0.5) ).rgb;

        vec2 maxPos = CalculateDistOffset(prep, angle, rSample * pcoc) * distOffsetScale;

        dof += DecodeColor(textureLod(colortex3, texcoord + maxPos, lod).rgb) * bokeh;
        weight += bokeh;
    }

    return dof / weight;
}

vec3 CalculateBloomTile(const float lod, vec2 pixelSize, vec2 offset) {
    #ifndef BLOOM
        return vec3(0.0);
    #endif

    vec2 coord = (texcoord - offset) * exp2(lod);
    vec2 scale = pixelSize * exp2(lod);

    if (any(greaterThanEqual(abs(coord - 0.5), scale + 0.5)))
        return vec3(0.0);

    vec3 bloom = vec3(0.0);
    float totalWeight = 0.0;

    const int bloomSamples = BLOOM_SAMPLES - 1;
    const float invSamples = 1.0 / float(bloomSamples + 1.0);

    for (int y = -bloomSamples; y <= bloomSamples; ++y) { // 5
        for (int x = -bloomSamples; x <= bloomSamples; ++x) { // 5
            float sampleLength = 1.0 - length(vec2(x, y)) * invSamples; //div by sqrt of total samples
            float sampleWeight = clamp01(pow(sampleLength, BLOOM_CURVE));

            bloom += textureLod(colortex3, coord + vec2(x, y) * scale, lod).rgb * sampleWeight;

            totalWeight += sampleWeight;
        }
    }

    return bloom / totalWeight;
}

vec3 CalculateBloomTiles() {
    #ifndef BLOOM
        return vec3(0.0);
    #endif

    vec3 bloom  = vec3(0.0);
        bloom += CalculateBloomTile(2.0, pixelSize, vec2(0.0, 0.0));
        bloom += CalculateBloomTile(3.0, pixelSize, vec2(0.0, 0.25 + pixelSize.y * 2.0));
        bloom += CalculateBloomTile(4.0, pixelSize, vec2(0.125 + pixelSize.x * 2.0, 0.25 + pixelSize.y * 2.0));
        bloom += CalculateBloomTile(5.0, pixelSize, vec2(0.1875 + pixelSize.x * 4.0, 0.25 + pixelSize.y * 2.0));
        bloom += CalculateBloomTile(6.0, pixelSize, vec2(0.125 + pixelSize.x * 2.0, 0.3125 + pixelSize.y * 4.0));
        bloom += CalculateBloomTile(7.0, pixelSize, vec2(0.140625 + pixelSize.x * 4.0, 0.3125 + pixelSize.y * 4.0));

    return DecodeColor(max0(bloom));
}

void main() {
    vec3 color = DepthOfField();

    #ifdef BOKEH_DEBUG
        if(all(lessThanEqual(texcoord / vec2(1.0, aspectRatio), vec2(0.25))))
            color  = texture(colortex0, (texcoord * 4.0) / vec2(1.0, aspectRatio)).rgb;
    #endif

    //Calculate DoF
    Buffer0 = EncodeRGBE8(color);

    //Calculate Bloom Tiles
    Buffer1 = EncodeRGBE8(CalculateBloomTiles());

    vec3 buffer2Passthrough = texture(colortex2, texcoord).rgb;
    float EV = texture(colortex3, texcoord).a;
    
    Buffer2 = vec4(buffer2Passthrough, EV * 0.1);
    
	exit();
}

#endif
/***********************************************************************/
