

#define SIMPLEFOG
#define WATERSTAGE
#include "/lib/taa/res_params.glsl"
#include "/lib/uniforms.glsl"
varying vec4 color;
varying float iswater;
varying vec4 lmtexcoord;
varying vec3 torch;
varying vec3 viewVector;
uniform int isEyeInWater;
varying vec3 normal0;
varying vec3 normal1;

uniform sampler2D texture;

uniform sampler2D gaux2;
uniform sampler2D gaux3;
uniform sampler2D gaux4;
uniform sampler2D gaux1;
uniform float darknessLightFactor;
uniform float far;

#include "/lib/color_transforms.glsl"
#include "/lib/projections.glsl"
#include "/lib/sky_gradient.glsl"
#include "/lib/light.glsl"

#define viewMAD(m, v) (mat3(m) * (v) + (m)[3].xyz)

const vec2 shadowOffsets[6] = vec2[6](vec2(0.5303, 0.5303), vec2(-0.6250, -0.0000), vec2(0.3536, -0.3536),
                                      vec2(-0.0000, 0.3750), vec2(-0.1768, -0.1768), vec2(0.1250, 0.0000));
#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)
#define projMAD(m, v) (diagonal3(m) * (v) + (m)[3].xyz)

float speculars(vec3 n, vec3 rd, vec3 l, float km, float f0)
{
    vec3 h = normalize(l - rd);
    float ndh = dot(n, h);
    float g = ndh * ndh * (km * km - 1.0) + 1.0;
    float ggx = km * km / (PI * g * g);

    float fre = 1.0 + dot(rd, n);

    float kr = f0 + (1.0 - f0) * (1. - km) * (1.0 - km) * pow(fre, 5.0);

    return kr * ggx;
}

vec3 specularWater(vec3 n, vec3 rd)
{
    vec3 f0 = vec3(0.2); // Typical value for non-metallic surfaces
    float km = 0.5;      // Example roughness squared
    vec3 light = toScreenSpaceDH(vec3(0, -1, 0));

    vec3 h = normalize(light - rd);
    float ndh = dot(n, h);
    float g = ndh * ndh * (km * km - 1.0) + 1.0;
    float ggx = km * km / (PI * g * g);

    float fre = 1.0 + dot(rd, n);

    vec3 kr = f0 + (1.0 - f0) * (1. - km) * (1.0 - km) * pow(fre, 5.0);

    return kr * ggx;
}

#define Time (frameTimeCounter)

const vec3 ambientMinLight = vec3(0.007, 0.01, 0.0125) * 0.4;

float fresnel0(vec3 l, vec3 n, float eta1, float eta2)
{
    float cosThetaI = abs(dot(l, n)); // cos(theta_i) - angle of incidence
    float eta = eta2 / eta1;          // relative index of refraction
    float sin2ThetaI = 1.0 - cosThetaI * cosThetaI;
    float sin2ThetaT = eta * eta * sin2ThetaI; // sin^2(theta_t) using Snell's law

    float cosThetaT = sqrt(max(0.0, 1.0 - sin2ThetaT)); // cos(theta_t)

    // Reflectance for s-polarized light
    float Rs = (eta1 * cosThetaI - eta2 * cosThetaT) / (eta1 * cosThetaI + eta2 * cosThetaT);
    Rs = Rs * Rs;

    // Reflectance for p-polarized light
    float Rp = (eta2 * cosThetaI - eta1 * cosThetaT) / (eta2 * cosThetaI + eta1 * cosThetaT);
    Rp = Rp * Rp;

    // Average of the s and p polarization reflectances
    return 0.5 * (Rs + Rp);
}

vec3 rayTrace(vec3 dir, vec3 position, float dither)
{
    const float quality = SSR_STEPS;

    vec3 clipPosition = toClipSpace3DH(position);
    vec3 direction = normalize(toClipSpace3DH(position + dir) - clipPosition);
    vec3 maxLengths = (step(0.0, direction) - clipPosition) / direction;
    float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = (direction * minLength) / quality;

    float tolerance = max(abs(stepv.z) * 4.0, 0.02 / (position.z * position.z));

    for (int i = 0; i <= int(quality); ++i)
    {

        vec3 spos = clipPosition + stepv * (i + dither);

        float depthSample = texelFetch2D(dhDepthTex0, ivec2(spos.xy / texelSize), 0).r;
        if (depthSample < spos.z && abs(spos.z - depthSample) < tolerance)
        {
            return vec3(spos.xy, depthSample);
        }
    }

    return vec3(1.1); // Indicates no intersection found
}

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

    return prevScreenPos;
}
vec3 reflections(vec3 direct, float fresnel, float sunSpec, float skyLight, vec3 normal, float watermult, vec3 fragpos)
{
    vec3 reflectedVector = reflect(normalize(fragpos), normal);
    vec3 reflection = vec3(0.0);
    vec4 tempreflection = vec4(0.0);

    if (skyLight > 0.0)
    {
        vec3 wrefl = mat3(gbufferModelViewInverse) * reflectedVector;

        wrefl.y *= watermult;

        vec4 sky = skyFromTex2((wrefl), gaux3);
        reflection.rgb = sky.rgb + (sky.a * direct * 0.2);
    }

    vec3 rtPos = rayTrace(reflectedVector, fragpos.xyz, R2_dither(gl_FragCoord.xy));

    vec3 previousPosition =
        reprojectDH(mat3(gbufferModelViewInverse) * toScreenSpaceDH(rtPos) + gbufferModelViewInverse[3].xyz, false);
    if (all(greaterThanEqual(previousPosition.xy, vec2(0.0))) && rtPos.z < 1.0)
    {
        tempreflection = vec4(texture(gaux1, previousPosition.xy).rgb, 1);
    }

    if (isEyeInWater == 1)
    {
        reflection = mix(
            mix(min(specularWater((normal), normalize(fragpos)), 16.0) * vec3(0.1, 0.4, 0.5) * 1, vec3(0), 1 - fresnel),
            tempreflection.rgb, tempreflection.a);
    }
    else
    {
        reflection = mix(reflection.rgb, tempreflection.rgb, tempreflection.a) * fresnel;
    }
    reflection = (reflection.rgb) + sunSpec * direct;

    return reflection;
}

float fakeFres(in float incidentDotNormal)
{
    float x = 1.0 + incidentDotNormal;
    return clamp((pow(x, 4)), 0, 1);
    ;
}

vec3 waterNormal(vec2 coord, vec3 normFragpos)
{
    coord = mod(coord / 64.0, 1.0);
    float strength = 1.0;

    float lod = fakeFres(dot(normFragpos, normal0));

    float deltaPos = 0.0012;
    deltaPos = mix(deltaPos, 0.0, lod * 1.1);
    strength = mix(strength, 0.0, lod * 0.9);

    vec2 offsets[4] = vec2[](vec2(deltaPos, 0.0), vec2(-deltaPos, 0.0), vec2(0.0, deltaPos), vec2(0.0, -deltaPos));

    float heights[4];
    for (int i = 0; i < 4; i++)
    {
        heights[i] = mix(texture(gaux4, coord + offsets[i]).x, 0.5, 0.0);
    }

    float dx = (heights[1] - heights[0]) * strength;
    float dy = (heights[3] - heights[2]) * strength;
    vec3 full = normalize(vec3(dx, dy, 0.5)) + 0.5;

    return full;
}

mat3 cotangent(vec3 N, vec3 p, vec2 uv)
{

    vec3 dp1 = dFdx(p);
    vec3 dp2 = dFdy(p);
    vec2 duv1 = dFdx(uv);
    vec2 duv2 = dFdy(uv);

    vec3 dp2perp = cross(dp2, N);
    vec3 dp1perp = cross(N, dp1);
    vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

    float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
    return mat3(T * invmax, B * invmax, N);
}
mat3 tbnpom(vec3 N, vec3 p, vec2 uv)
{

    vec3 binormal = vec3(0.0);
    vec3 tangent = vec3(0.0);

    vec3 pos_dx = dFdx(p);
    vec3 pos_dy = dFdy(p);
    float tcoord_dx = dFdx(uv.y);
    float tcoord_dy = dFdy(uv.y);

    // Fix issues when the texture coordinate is wrong, this happens when
    // two adjacent vertices have the same texture coordinate, as the gradient
    // is 0 then. We just assume some hard-coded tangent and binormal then
    if (abs(tcoord_dx) < 1e-24 && abs(tcoord_dy) < 1e-24)
    {
        vec3 base = abs(N.z) < 0.999 ? vec3(0, 0, 1) : vec3(0, 1, 0);
        tangent = normalize(cross(N, base));
    }
    else
    {
        tangent = normalize(pos_dx * tcoord_dy - pos_dy * tcoord_dx);
    }

    binormal = normalize(cross(tangent, N));

    return mat3(tangent.x, binormal.x, N.x, tangent.y, binormal.y, N.y, tangent.z, binormal.z, N.z);
}

uniform sampler2DShadow shadow;

uniform mat4 shadowModelView;
uniform mat4 shadowProjection;
uniform float nightblendShadows;
vec2 tapLocation(int sampleNumber, int nb, float nbRot, float jitter, float distort)
{
    float alpha = (sampleNumber + jitter) / nb;
    float angle = jitter * 6.28 + alpha * nbRot * 6.28;

    float sin_v, cos_v;

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

    return vec2(cos_v, sin_v) * sqrt(alpha);
}

#include "/lib/shadow_param.glsl"

/* DRAWBUFFERS:5*/
void main()
{
    vec4 outcolor = texture2D(texture, lmtexcoord.xy);
    outcolor.rgb *= color.rgb;
    float alpha0 = 1;
    vec3 normal = normal1;
    if (iswater > 0.5)
    {
        vec3 wcolor = vec3(0.1, 0.4, 0.5);
        outcolor.rgb = mix(wcolor, color.rgb, 0.25) * 0.5;
    }

    float diffuseSun = mix(1.0, max(dot(normal, lightPos), 0.0), clamp(outcolor.a + 0.05, 0.0, 1.0)) * lmtexcoord.w;
    vec3 direct = sunLight;
#ifdef WATER_SHADOWS
    float shading = 1;
    if (diffuseSun > 0.001)
    {
        float noise = R2_dither(gl_FragCoord.xy);
        vec3 p3_shadows = (mat3(gbufferModelViewInverse) * viewVector) + gbufferModelViewInverse[3].xyz;

        vec3 ProjShadowPos = mat3(shadowModelView) * p3_shadows + shadowModelView[3].xyz;
        ProjShadowPos = diagonal3(shadowProjection) * ProjShadowPos + shadowProjection[3].xyz;

        float distortFactor = calcDistort(ProjShadowPos.xy);
        ProjShadowPos.xy *= distortFactor;

        if (all(lessThan(abs(ProjShadowPos.xy), vec2(1.0) - 1.5 / shadowMapResolution)) && abs(ProjShadowPos.z) < 6)
        {
            float distortThresh = (sqrt(1.0 - diffuseSun * diffuseSun) / diffuseSun + 0.7) / distortFactor;
            float diffthresh = distortThresh / 6000.0 * max(2048.0 / shadowMapResolution * shaddist / 128.0, 0.95);

            ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.5 / 6.0) + 0.5;

            shading = 0.0;
            float rdMul = 4.0 / shadowMapResolution;
            for (int i = 0; i < 9; i++)
            {
                vec2 offsetS = tapLocation(i, 9, 2.0, noise, 0.0);
                float weight = 1.0 + (i + noise) * rdMul / 9.0 * shadowMapResolution;
                shading += shadow2D(shadow, ProjShadowPos + vec3(rdMul * offsetS, -diffthresh * weight)).x / 9.0;
            }
            diffuseSun *= shading;
            direct *= shading;
        }
    }

#endif

    vec3 ambient = calcAmbientLight(normal, lmtexcoord.zw, atmosphereUp, diffuseSun, torch);

    outcolor.rgb = ambient * toLinear(outcolor.rgb);

    if (iswater > 0.5)
    {
        vec3 normFragpos = normalize(viewVector);
        alpha0 = 0.1;
        vec3 p3 = mat3(gbufferModelViewInverse) * viewVector + gbufferModelViewInverse[3].xyz;
        vec3 posxz = p3 + cameraPosition;
        vec3 normalTex = waterNormal(posxz.xz - posxz.y, normFragpos) * 2 - 1;
        /*     mat3 TBNUP = mat3(vec3(1, 0, 0),  // Tangent
                               vec3(0, 0, 1),  // Bitangent
                               vec3(0, 1, 0)); // Normal
                               */
        mat3 TBNUP = tbnpom(normal0, -(viewVector), lmtexcoord.xy);
        normal = (normalize(TBNUP * normalTex));

        float fresnel =
            fresnel0(normFragpos, normal, mix(1.333, 1, float(isEyeInWater)), mix(1, 1.333, float(isEyeInWater)));

        float sunSpec = min(speculars(normal, normFragpos, sunVec, 0.003, 0.02), 16.0) * lmtexcoord.w * diffuseSun * (1.0 - rainStrength);

        vec3 reflected =
            reflections(sunLight, fresnel, sunSpec, lmtexcoord.w, normal, mix(1, -1, float(isEyeInWater)), viewVector);

        float effectiveAlpha = 0.75 * (1.0 - fresnel) + fresnel;
        vec3 baseColorCorrected = outcolor.rgb * alpha0 * (1.0 - fresnel);
        const vec3 totEpsilon = vec3(0.69, 0.19, 0.14);
        float depth = texelFetch(dhDepthTex1, ivec2(gl_FragCoord.xy), 0).r;
        vec3 fragpos2 = toScreenSpaceDH((vec3(gl_FragCoord.xy, depth) * vec3(texelSize, 1.0)));
        float estimatedDepth = distance(viewVector, fragpos2);
        vec3 absorbance_w = min(exp(-totEpsilon * estimatedDepth), 1);
        outcolor = vec4((baseColorCorrected + reflected) / effectiveAlpha, effectiveAlpha);
        outcolor.rgb = mix(texture2D(gaux2, gl_FragCoord.xy * texelSize).rgb * absorbance_w, outcolor.rgb, outcolor.a);
        // outcolor.rgb = normalTex;
        outcolor.a = 1.0;
    }

    gl_FragData[0] = outcolor;
}
