#extension GL_ARB_gpu_shader5 : enable

#include "/lib/projections.glsl"
#include "/lib/taa/res_params.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 wetness;
uniform float near;
uniform float far;

uniform float frameTimeCounter;
uniform float darknessLightFactor;

uniform sampler2D depthtex0;
uniform sampler2D depthtex2;

uniform sampler2D colortex8;
uniform sampler2D colortex11;
uniform sampler2D colortex12;
uniform sampler2D colortex15;
uniform sampler2D colortex0;

#include "/lib/color_transforms.glsl"
#include "/lib/light.glsl"
#include "/lib/shadow_param.glsl"

uniform mat4 shadowProjectionInverse;
uniform float nightblendShadows;

#if SHADOW_FILTERING == 2
#include "/lib/shadows/shadowfunc_advanced.glsl"

#else
#include "/lib/shadows/shadowfunc_simple.glsl"
#endif
#if SSAO == 1
#include "/lib/ssao.glsl"
#endif

vec3 CookTorranceBRDF(vec3 normal, vec3 viewDir, vec3 lightDir, float perceptualSmoothness, vec3 F0, float rainMult)
{
    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);
    roughness = mix(roughness, 0.01, rainMult);

    // 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 clamp(specular, 0, 1.0);
}

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;
}
#if SSAO == 2
#include "/lib/rtao_lib.glsl"
#endif

mat3 CoordBase0(vec3 n)
{
    vec3 x, y;

    if (n.z < -0.999999)
    {
        x = vec3(0., -1, 0);
        y = vec3(-1, 0, 0);
    }
    else
    {
        float a = 1. / (1. + n.z);
        float b = -n.x * n.y * a;
        x = vec3(1. - n.x * n.x * a, b, -n.x);
        y = vec3(b, 1. - n.y * n.y * a, -n.y);
    }

    return mat3(x, y, n);
}

float s_rtao(vec3 normal, vec3 fragpos)
{
    // Pre-compute invariant values
    mat3 basis = CoordBase0(normal);
    vec4 bnoise = blueNoise4(gl_FragCoord.xy);
    vec3 clipPosition = toClipSpace3(fragpos);
    int fc4 = frameCounter * 4;
    float occlusion = 0.0;
    float fragposSq = fragpos.z * fragpos.z;
    for (int i = 0; i < 4; i++) // Unroll loop since samples is fixed at 4
    {
        int seed = fc4 + i;
        vec2 ij = fract(fract(seed * vec2(0.7, 0.5)) + bnoise.rg);

        // Sample directions over the hemisphere oriented around the normal
        float r = sqrt(ij.x);
        float theta = 6.283185 * ij.y;
        vec3 H = vec3(r * cos(theta), r * sin(theta), sqrt(1.0 - ij.x));
        vec3 L = basis * H;

        // Perform ray tracing directly within the loop
        vec3 direction = normalize(toClipSpace3(fragpos + mat3(gbufferModelView) * L) - clipPosition);
        vec3 maxLengths = ((step(0.0, direction) - clipPosition) / direction);
        float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
        vec3 stepv = direction * (minLength * ij.yxy) * 0.0625; // 1/16 = 0.0625

        float tolerance = max(abs(stepv.z) * 10.0, 0.02 / fragposSq);

        vec3 spos = clipPosition + stepv * ij.xyx;
        float depthSample = texelFetch(depthtex0, ivec2(spos.xy / texelSize), 0).r;

        if (!(depthSample < spos.z && abs(spos.z - depthSample) < tolerance))
        {
            occlusion += 0.25;
        }
    }

    return occlusion; // Use multiplication by inverse to avoid division
}

void main()
{
    /* DRAWBUFFERS:0*/
    vec3 color;
    float z = texture(depthtex0, texcoord).x;

    if (z >= 1.0)
    {
        gl_FragData[0].rgb = texture(colortex8, texcoord).rgb;
        return;
    }

    ivec2 texCoords = ivec2(gl_FragCoord.xy);
    vec3 albedo = texelFetch(colortex0, texCoords, 0).rgb;
    vec4 normalTex = texelFetch(colortex11, texCoords, 0);
    vec4 materials = texture(colortex12, texcoord);
    vec3 normal = decodeNormal(normalTex.rg);
    vec2 lightmap = decodeVec2(normalTex.b);
    lightmap.x = pow(lightmap.x, 3.0);

    vec3 sAlbedo = vec3(0.6) * ambientA * lightmap.y;
    bool hand = texelFetch(depthtex2, texCoords, 0).x > z;
    float emissive = materials.y;
    float sss = materials.x;
    float ao = normalTex.a;
#ifdef OVERWORLD
    float diffuseSun = max(dot(normal, lightPos), 0.0);
    diffuseSun = mix(0.1, diffuseSun, shadowblend);

#else
    float diffuseSun = max(dot(normal, vec3(0, 1, 0)), 0.0);

#endif
    vec3 specular = vec3(0.0);

#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 p3 = mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz;

    if (!hand)
    {
        float noise = noise_standard(gl_FragCoord.xy);
        sss = (sss * 255.0 > 64.5) ? sss * 0.75 : 0.0;

#if SSAO == 1
        // ao *= dbao(noise, z);
        ao *= s_rtao(normal, fragpos);

#elif SSAO == 2
        ao *= rtao(lightmap.y, normal, fragpos);
#endif

        if (lightmap.y > 0.001)
        {

#ifdef OVERWORLD
#if SHADOW_FILTERING == 2
            diffuseSun = shadowFunc(diffuseSun, sss, p3, noise, lightmap, fragpos, sAlbedo, albedo, normal);
#else
            diffuseSun = shadowFunc(diffuseSun, sss, p3, noise, lightmap, normal);
#endif
#endif
        }
        else
        {
#ifdef OVERWORLD
            diffuseSun = 0.0;
#endif
        }

#ifdef PBR
        if (materials.z > 0.0)
        {
            float porosity = materials.x;
            porosity = ((porosity > 64.5 / 255.0) ? 0.0 : porosity) * 255.0 / 64.0;
            float rainMult = sqrt(lightmap.y) * wetness * (1.0 - (porosity * porosity));
            materials.z = mix(materials.z, 0.02, rainMult);

#ifdef OVERWORLD
            specular = CookTorranceBRDF(normal, -p3, lightPos, materials.w, vec3(materials.z), rainMult) * diffuseSun * sunLight;
#else
            specular = CookTorranceBRDF(normal, -p3, vec3(0, 1, 0), materials.w, vec3(materials.z), rainMult) * diffuseSun * vec3(0.01);
#endif
        }

#endif
    }
    else
    {
        float shading = 1.0;
#ifdef HAND_SHADOW

        vec3 ProjShadowPos = (mat3(shadowModelView) * p3) + shadowModelView[3].xyz;

        ProjShadowPos = vec3(ProjShadowPos.x * shadowProjection[0].x + shadowProjection[3].x,
                             ProjShadowPos.y * shadowProjection[1].y + shadowProjection[3].y,
                             ProjShadowPos.z * shadowProjection[2].z + shadowProjection[3].z);

        const vec2 bounds = vec2(1.0, 1.0) - 1.5 / 2;
        if (all(lessThan(abs(ProjShadowPos.xy), bounds)) && abs(ProjShadowPos.z) < 1.0)
        {

            ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.0833333) + vec3(0.5);
            ProjShadowPos.z -= 0.000167;

            shading = texture(shadow, ProjShadowPos);
        }
#endif
        shading = mix(0.0, shading, min(eyeBrightnessSmooth.y * recip * lightmap.y, 1.0));
        diffuseSun *= shading;
    }

    vec3 torch = vec3(3.000, 1.382, 0.408);
    float lightmapX4 = lightmap.x * lightmap.x * lightmap.x * lightmap.x;
    torch *= lightmapX4 + lightmap.x * 0.025;
    torch += emissive * 8.0;

    vec3 ambient = calcAmbientLight(normal, lightmap.xy, ambientUp, ambientDown, ambientRight, ambientLeft, ambientB, ambientF, sAlbedo, diffuseSun, clamp(torch, 0.0, 4.0) * 2.0);
    ////

    ///

#ifdef SSPT
    vec3 sspt = texelFetch(colortex15, texCoords, 0).rgb;
    ambient += sspt;
#endif

    ambient *= ao;

#ifdef PBR
    color = ambient * albedo + specular;

#else
    color = ambient * albedo;
#endif
    // color = vec3(sss);
    gl_FragData[0].rgb = color;
}
