#extension GL_ARB_gpu_shader5 : enable
// data1

#include "/lib/projections.glsl"
#include "/lib/taa/res_params.glsl"
#include "/lib/shadow_param.glsl"
#include "/lib/uniforms.glsl"

flat varying vec3 ambientUp;
flat varying vec3 ambientDown;
flat varying vec3 ambientLeft;
flat varying vec3 ambientRight;
flat varying vec3 ambientB;
flat varying vec3 ambientF;
flat varying float ambientA;
varying vec2 texcoord;
uniform float near;
uniform float far;
uniform float nightblendShadows;
uniform mat4 shadowProjectionInverse;

uniform float frameTimeCounter;

uniform float darknessLightFactor;
uniform sampler2D depthtex0;
uniform sampler2D depthtex2;

uniform sampler2D colortex2; // data0
uniform sampler2D colortex8; // data0
uniform sampler2D colortex9; // data0

uniform sampler2D colortex10; // data0
uniform sampler2D colortex11; // data0
uniform sampler2D colortex12; // data0

uniform sampler2D colortex0; // albedo
#include "/lib/color_transforms.glsl"
#include "/lib/light.glsl"


#ifdef SSAO
#include "/lib/ssao.glsl"
#endif

vec3 CookTorranceBRDF(vec3 normal, vec3 viewDir, vec3 lightDir, float perceptualSmoothness, vec3 F0)
{
    normal = normalize(normal);
    viewDir = normalize(viewDir);
    lightDir = normalize(lightDir);

    vec3 halfVec = normalize(lightDir + viewDir);

    float NoL = max(dot(normal, lightDir), 0.0);
    float NoV = max(dot(normal, viewDir), 0.0);
    float NoH = max(dot(normal, halfVec), 0.0);
    float VoH = max(dot(viewDir, halfVec), 0.0);

    // Handle cases where NoV is very small
    if (NoV <= 0.0)
    {
        return vec3(0.0); // No reflection if the view direction is below the surface
    }

    // Convert perceptual smoothness to roughness
    float roughness = clamp(pow(1.0 - perceptualSmoothness, 2.0), 0.0001, 0.9999);

    // Distribution function (GGX)
    // float alpha = roughness * roughness;
    float alpha = roughness;
    float alpha2 = alpha * alpha;
    float denom = NoH * NoH * (alpha2 - 1.0) + 1.0;
    float D = alpha2 / (PI * denom * denom);

    // Fresnel function (Schlick's approximation)
    vec3 F = F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);

    // Geometry function (Smith's method with GGX)
    float k = alpha / 2.0;
    float G1 = NoL / (NoL * (1.0 - k) + k);
    float G2 = NoV / (NoV * (1.0 - k) + k);
    float G = G1 * G2;

    // Cook-Torrance BRDF
    vec3 specular = (D * F * G) / (4.0 * NoL * NoV + 0.0001);

    return specular;
}
vec3 drawSun(float cosY, float sunInt, vec3 nsunlight, vec3 inColor)
{
    return inColor +
           nsunlight / 0.001 * pow(smoothstep(cos(0.0093084168595 * 3.2), cos(0.0093084168595 * 1.8), cosY), 3.) * 0.62;
}

vec2 tapLocation(int sampleNumber, float spinAngle, int nb, float nbRot)
{
    float alpha = float(sampleNumber + spinAngle / 6.28) / nb;
    float angle = alpha * (nbRot * 6.28) + spinAngle;

    float ssR = alpha * alpha;
    float sin_v, cos_v;

    sin_v = sin(angle);
    cos_v = cos(angle);

    return vec2(cos_v, sin_v) * ssR;
}
#ifdef PBR_REFLECTIONS

#include "/lib/reflection_lib.glsl"
#endif
vec3 Reproject(vec3 position)
{
    bool isHand = position.z < 0.6;
    if (position.z < 1.0 && !isHand)
    {
        position = ViewSpaceFromScreenSpace(position, gbufferProjectionInverse);
        position = mat3(gbufferModelViewInverse) * position + gbufferModelViewInverse[3].xyz;
        position += (cameraPosition - previousCameraPosition);
        position = mat3(gbufferPreviousModelView) * position + gbufferPreviousModelView[3].xyz;
        position = ScreenSpaceFromViewSpace(position, gbufferPreviousProjection);
    }
    else
    {
        position = ViewSpaceFromScreenSpace(position, gbufferProjectionInverse);
        position = mat3(gbufferModelViewInverse) * position + gbufferModelViewInverse[3].xyz;
        position = mat3(gbufferPreviousModelView) * position + gbufferPreviousModelView[3].xyz;
        position = ScreenSpaceFromViewSpace(position, gbufferPreviousProjection);
    }

    return position;
}
float MinDepth3x3(vec2 uv)
{
    ivec2 fragCoord = ivec2(gl_FragCoord.xy);
    float depth = texelFetch(depthtex0, fragCoord, 0).x;

    for (int x = -1; x <= 1; ++x)
    {
        for (int y = -1; y <= 1; ++y)
        {
            if (x != 0 || y != 0)
            {
                depth = min(depth, texelFetch(depthtex0, fragCoord + ivec2(x, y), 0).x);
            }
        }
    }

    return depth;
}
vec4 fetchHistoryPx(ivec2 pix)
{
    return vec4(texelFetch(colortex9, pix, 0).xyz, 1);
}

vec3 CubicHermite(vec3 A, vec3 B, vec3 C, vec3 D, float t)
{
    float t2 = t * t;
    float t3 = t * t * t;
    vec3 a = -A / 2.0 + (3.0 * B) / 2.0 - (3.0 * C) / 2.0 + D / 2.0;
    vec3 b = A - (5.0 * B) / 2.0 + 2.0 * C - D / 2.0;
    vec3 c = -A / 2.0 + C / 2.0;
    vec3 d = B;

    return a * t3 + b * t2 + c * t + d;
}

vec3 BicubicHermiteTextureSample(vec2 P)
{
    vec2 pixel = P * viewResolution.xy + 0.5;

    vec2 frac = fract(pixel);
    // pixel = floor(pixel) / outputTex_size.xy - vec2(c_onePixel/2.0);
    ivec2 ipixel = ivec2(pixel) - 1;

    vec3 C00 = fetchHistoryPx(ipixel + ivec2(-1, -1)).rgb;
    vec3 C10 = fetchHistoryPx(ipixel + ivec2(0, -1)).rgb;
    vec3 C20 = fetchHistoryPx(ipixel + ivec2(1, -1)).rgb;
    vec3 C30 = fetchHistoryPx(ipixel + ivec2(2, -1)).rgb;

    vec3 C01 = fetchHistoryPx(ipixel + ivec2(-1, 0)).rgb;
    vec3 C11 = fetchHistoryPx(ipixel + ivec2(0, 0)).rgb;
    vec3 C21 = fetchHistoryPx(ipixel + ivec2(1, 0)).rgb;
    vec3 C31 = fetchHistoryPx(ipixel + ivec2(2, 0)).rgb;

    vec3 C02 = fetchHistoryPx(ipixel + ivec2(-1, 1)).rgb;
    vec3 C12 = fetchHistoryPx(ipixel + ivec2(0, 1)).rgb;
    vec3 C22 = fetchHistoryPx(ipixel + ivec2(1, 1)).rgb;
    vec3 C32 = fetchHistoryPx(ipixel + ivec2(2, 1)).rgb;

    vec3 C03 = fetchHistoryPx(ipixel + ivec2(-1, 2)).rgb;
    vec3 C13 = fetchHistoryPx(ipixel + ivec2(0, 2)).rgb;
    vec3 C23 = fetchHistoryPx(ipixel + ivec2(1, 2)).rgb;
    vec3 C33 = fetchHistoryPx(ipixel + ivec2(2, 2)).rgb;

    vec3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x);
    vec3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x);
    vec3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x);
    vec3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x);

    return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y);
}

#define SAMPLES 80                 // HIGHER = NICER = SLOWER
#define DISTRIBUTION_BIAS 0.6      // between 0. and 1.
#define PIXEL_MULTIPLIER 1.5       // between 1. and 3. (keep low)
#define INVERSE_HUE_TOLERANCE 20.0 // (2. - 30.)

#define GOLDEN_ANGLE 2.3999632 // 3PI-sqrt(5)PI

#define pow(a, b) pow(max(a, 0.), b) // @morimea

void main()
{
    /* DRAWBUFFERS:1*/

    float z = texture(depthtex0, texcoord).x;
    vec4 materials = texture(colortex12, texcoord);

    if (z >= 1)
    {
        gl_FragData[0].rgb = vec3(0);
        return;
    }

    vec3 reflections = vec3(0);

    vec3 normalTex = texelFetch(colortex11, ivec2(gl_FragCoord.xy), 0).rgb;
    vec3 normal = (decodeNormal(normalTex.rg));
    vec2 lightmap = decodeVec2(normalTex.b);

#ifdef TAA
    vec2 tempOffset = offsets[framemod8] * texelSize * 0.5;
    vec3 fragpos = toScreenSpace(vec3(texcoord - tempOffset, z));
#else
    vec3 fragpos = toScreenSpace(vec3(texcoord, z));
#endif

    vec3 f0 = vec3(materials.z);
    reflections = computeReflections(lightmap.y, materials.w, f0, normal, fragpos, materials.x);

    gl_FragData[0].rgb = reflections;
}
