

varying vec4 color;
varying float iswater;
varying vec4 lmtexcoord;
varying vec3 torch;
varying vec3 viewVector;
varying vec3 normal0;
varying vec3 normal1;

#include "/lib/sky_function.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)
{
    fresnel = pow(fresnel, 0.75);
    vec3 reflectedVector = reflect(normalize(fragpos), normal);
    vec3 reflection = vec3(0.0);
    vec4 tempreflection = vec4(0.0);
    vec4 sky = vec4(1.0);
    if (pow(skyLight, 3) > 0.0)
    {
        vec3 wrefl = mat3(gbufferModelViewInverse) * reflectedVector;

        wrefl.y *= watermult;

        sky = skyFromTex2((wrefl), gaux3);
        reflection.rgb = (sky.rgb + (sky.a * direct * 0.2)) * pow(skyLight, 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.1)
    {

        tempreflection = vec4(texture(gaux1, previousPosition.xy).rgb, 1);
    }

    if (isEyeInWater == 1)
    {

        reflection = mix(
            mix(min(specularWater((normal), normalize(fragpos)), 16.0) * reflection * 8.0, vec3(0), 1 - fresnel),
            tempreflection.rgb, tempreflection.a);
    }
    else
    {
        reflection = mix(reflection.rgb, tempreflection.rgb, tempreflection.a) * fresnel;
    }

    reflection = (reflection.rgb) + sunSpec * direct;

    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 / 128.0, 1.0);
    coord = mod(coord / 84.0, 1.0);

    float strength = mix(2.0, 3.0, float(isEyeInWater == 1));

    float lod = clamp((pow(1.0 + dot(normFragpos, normal0), 4)), 0, 1);

    float deltaPos = 0.0015;
    deltaPos = mix(deltaPos, 0.0, lod * 1.0);
    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] = texture(gaux4, coord + offsets[i]).x;
    }

    float dx = (heights[1] - heights[0]) * strength;
    float dy = (heights[3] - heights[2]) * strength;
    vec3 full = normalize(vec3(dx, dy, 0.5)) + 0.5;
    // full = vec3(0.5, 0.5, 1.0);
    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);
}
mat3 tbnpom_up()
{
    // Hardcoded vectors for a TBN pointing up
    vec3 N = vec3(0.0, 1.0, 0.0);  // Normal pointing up (Y-axis)
    vec3 T = vec3(1.0, 0.0, 0.0);  // Tangent pointing right (X-axis)
    vec3 B = vec3(0.0, 0.0, -1.0); // Bitangent pointing forward (negative Z-axis)

    // Construct and return the TBN matrix
    return mat3(T.x, B.x, N.x,
                T.y, B.y, N.y,
                T.z, B.z, N.z);
}

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);
}
float GetBorderFogMixFactorCylinder(in vec3 eyePlayerPos, in float far, bool isWater)
{

    float mixval = 0.75;

    if (isWater)
        mixval = 0.5;
    float eyeDist = length(eyePlayerPos.xz);
    float borderFogFactor = smoothstep(far * mixval, far, eyeDist);

    return borderFogFactor;
}

mat3 tbn_from_world_normal(vec3 N)
{
    // Normalize the input normal just to be safe
    N = normalize(N);

    // Choose an arbitrary vector that is not parallel to the normal
    vec3 arbitrary = abs(N.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);

    // Compute the tangent vector as a vector orthogonal to the normal
    vec3 T = normalize(cross(arbitrary, N));

    // Compute the bitangent as the cross product of the normal and the tangent
    vec3 B = cross(N, T); // No need to normalize, it is already orthogonal

    // Construct and return the TBN matrix
    return mat3(T, B, N);
}

/* RENDERTARGETS: 1,5 */
void main()
{
    float viewDist = length(viewVector);

    if (viewDist < 0.75 * far)
    {
        //    discard;
        //   return;
    }
    vec2 uv = gl_FragCoord.xy * texelSize;

    vec4 outcolor = texture2D(texture, lmtexcoord.xy);
    outcolor.rgb *= color.rgb;

    if (iswater < 0.5)
    {
        outcolor.a = 1;
    }

    float alpha0 = 1;
    vec3 normal = normal1;

    if (iswater > 0.5)
    {
        vec3 wcolor = vec3(0.1, 0.4, 0.5);
#ifndef VANILLA_WATER

        outcolor.rgb = mix(wcolor, color.rgb, 0.25) * 0.5;

#endif
    }

    float diffuseSun = max(dot(normal, lightPos), 0.0) * lmtexcoord.w;
    vec3 direct = sunLight;
    float shading = 1;

#ifdef OVERWORLD
#ifdef WATER_SHADOWS
    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
#endif
    direct *= (1 - rainStrength * 0.9);
    diffuseSun *= (1 - rainStrength * 0.9);
    vec3 ambient = calcAmbientLight(normal, lmtexcoord.zw, atmosphereUp, diffuseSun, torch);
    vec3 p3 = mat3(gbufferModelViewInverse) * viewVector + gbufferModelViewInverse[3].xyz;
    vec3 posxz = p3 + cameraPosition;
    // viewToWorld(normal0)
    outcolor.rgb = ambient * toLinear(outcolor.rgb);
    vec3 normFragpos = normalize(viewVector);
    // mat3 TBNUP = tbnpom(normal0, -(viewVector), lmtexcoord.xy);
    mat3 TBNUP = tbn_from_world_normal(viewToWorld(normal0));
    vec3 normalTex = vec3(0.5, 0.5, 1.0) * 2 - 1;
    if (iswater > 0.5)
    {
        alpha0 = 0.1;
        normalTex = waterNormal(posxz.xz - posxz.y, normFragpos) * 2 - 1;
    }

    normal = (normalize(TBNUP * normalTex));
    vec3 normalWorld = worldToView(normal);

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

    float roughness = 0.01;
    float f0 = 0.002;

    float sunSpec = min(speculars(normalWorld, normFragpos, sunVec, roughness, f0), 16.0) * lmtexcoord.w * (1.0 - rainStrength) * shading;

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

    float effectiveAlpha = outcolor.a * (1.0 - fresnel) + fresnel;
    vec3 baseColorCorrected = outcolor.rgb * alpha0 * (1.0 - fresnel);
    outcolor = vec4((baseColorCorrected + reflected) / effectiveAlpha, effectiveAlpha);

    if (iswater < 0.5)
    {
        outcolor.a = 1;
    }

    ivec2 texCoords = ivec2(gl_FragCoord.xy);

    float z = texelFetch(depthtex1, texCoords, 0).x;
    vec3 fragpos = toScreenSpace(vec3(gl_FragCoord.xy * texelSize, z));

    vec3 p3_org = mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz;

    float mixfactor = GetBorderFogMixFactorCylinder(p3_org, far, iswater >= 0.9);

    gl_FragData[1] = vec4(1, 0, 0, float(iswater > 0.99) * mixfactor);

    outcolor.a = mix(0, outcolor.a, mixfactor);
    //  outcolor = vec4(vec3(sunSpec), 1.0);

    gl_FragData[0] = clamp(outcolor, 0, 1000);
}
