#include "/lib/config.glsl"
const bool colortex1MipmapEnabled = true;

/* Color utils */

#ifdef THE_END
    #include "/lib/color_utils_end.glsl"
#elif defined NETHER
    #include "/lib/color_utils_nether.glsl"
#else
    #include "/lib/color_utils.glsl"
#endif

/* Uniforms */

uniform sampler2D colortex1;
uniform float far;
uniform float near;
uniform float blindness;
uniform float rainStrength;
uniform sampler2D depthtex0;
uniform int isEyeInWater;
uniform ivec2 eyeBrightnessSmooth;
uniform int frameCounter;
uniform float frameTimeCounter;
uniform sampler2D noisetex;

#if !(VOL_LIGHT == 1 && !defined NETHER) && !(VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER)
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferModelViewInverse;
#endif

#if MC_VERSION >= 11900
    uniform float darknessFactor;
#endif

#if VOL_LIGHT == 1 && !defined NETHER
    uniform sampler2D depthtex1;
    uniform vec3 sunPosition;
    uniform vec3 moonPosition;
    uniform float dayNightMix;
    uniform mat4 gbufferProjectionInverse;
    uniform mat4 gbufferModelViewInverse;
    uniform mat4 gbufferModelView;
    uniform float volumetricDayMixer;
#endif

#if VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER
    uniform float dayNightMix;
    uniform mat4 gbufferProjectionInverse;
    uniform mat4 gbufferModelViewInverse;
    uniform mat4 gbufferModelView;
    uniform float volumetricDayMixer;
    uniform vec3 shadowLightPosition;
    uniform mat4 shadowModelView;
    uniform mat4 shadowProjection;
    uniform sampler2DShadow shadowtex1;

    #if defined COLORED_SHADOW
        uniform sampler2DShadow shadowtex0;
        uniform sampler2D shadowcolor0;
    #endif
#endif

/* Ins / Outs */

varying vec2 texcoord;
varying vec3 directLightColor;
varying float exposure;

#if VOL_LIGHT == 1 && !defined NETHER
    varying vec3 volumetricLightColor;
    varying vec2 lightpos;
    varying vec3 astroLightPos;
#endif

#if VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER
    varying vec3 volumetricLightColor;
#endif

#if (VOL_LIGHT == 1 && !defined NETHER) || (VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER)
    varying mat4 modeli_times_projectioni;
#endif

/* Utility functions */

#include "/lib/basic_utils.glsl"
#include "/lib/depth.glsl"

#ifdef BLOOM
    #include "/lib/luma.glsl"
#endif

#if VOL_LIGHT == 1 && !defined NETHER
    #include "/lib/dither.glsl"
    #include "/lib/volumetric_light.glsl"
#endif

#if VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER
    #include "/lib/dither.glsl"
    #include "/lib/volumetric_light.glsl"
#endif

float hash21(vec2 p) {
    p = fract(p * vec2(123.34, 345.45));
    p += dot(p, p + 34.345);
    return fract(p.x * p.y);
}

vec3 prismaReconstructViewPos(vec2 uv, float depth) {
    vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
    vec4 view = gbufferProjectionInverse * clip;
    return view.xyz / max(view.w, 1e-6);
}

vec3 prismaReconstructWorldPos(vec2 uv, float depth) {
    vec3 viewPos = prismaReconstructViewPos(uv, depth);
    vec4 world = gbufferModelViewInverse * vec4(viewPos, 1.0);
    return world.xyz;
}

float prismaLuma(vec3 c) {
    return dot(c, vec3(0.2126, 0.7152, 0.0722));
}

float prismaWaterCaustics(vec3 worldPos, vec3 sunDir) {
    float sunY = max(abs(sunDir.y), 0.18);
    vec2 pos = (worldPos.xz - (sunDir.xz / sunY) * worldPos.y) * 0.018;
    float movement = frameTimeCounter * 0.028;

    mat2 rot = mat2(vec2(0.7374, -0.6755), vec2(0.6755, 0.7374));
    vec2 uv = pos;
    float large = max(texture2D(noisetex, uv * 0.18).b, 0.1);
    float h = 0.0;

    h += pow(abs(abs(texture2D(noisetex, uv / vec2(1.40, 0.42) + vec2(movement,  movement * 0.7) + large * 0.5).b * 2.0 - 1.0) * 2.0 - 1.0), 2.0);
    uv = rot * uv;
    h += pow(abs(abs(texture2D(noisetex, uv / vec2(0.46, 1.55) + vec2(-movement * 0.8, movement * 0.55) + large * 0.4).b * 2.0 - 1.0) * 2.0 - 1.0), 2.0);
    uv = rot * uv;
    h += pow(abs(abs(texture2D(noisetex, uv / vec2(0.95, 0.95) + vec2(movement * 0.55, -movement * 0.75) + large * 0.35).b * 2.0 - 1.0) * 2.0 - 1.0), 2.0);

    float c = exp((1.2 + 4.0 * sqrt(large)) * (h / 3.0 - 0.52));
    return clamp((c - 0.55) * 0.55, 0.0, 1.4);
}

// MAIN FUNCTION ------------------

void main() {
    vec4 blockColor = texture2DLod(colortex1, texcoord, 0);
    float d = texture2DLod(depthtex0, texcoord, 0).r;
    float linearDepth = ld(d);

    vec2 eyeBrightSmoothFloat = vec2(eyeBrightnessSmooth);

    // Depth to distance
    float screen_distance = linearDepth * far * 0.5;

    // Underwater fog / density / particulate matter
    if(isEyeInWater == 1) {
        float dayFactorUW = smoothstep(0.18, 0.62, dayNightMix);
        float waterBrightness = clamp((eyeBrightSmoothFloat.y * 0.62 + 22.0) * 0.0032, 0.16, 0.86);
        waterBrightness *= mix(0.58, 1.0, dayFactorUW);
        float waterFog = 1.0 - exp(-screen_distance * (0.044 + WATER_ABSORPTION * 0.135));
        float deepFog = 1.0 - exp(-screen_distance * 0.108);

        vec3 viewPosUW = prismaReconstructViewPos(texcoord, d);
        vec3 worldPosUW = prismaReconstructWorldPos(texcoord, d);

        // Bliss-like underwater absorption: less "glass", more actual medium.
        vec3 absorbColor = exp(-screen_distance * vec3(0.31, 0.135, 0.060) * (0.58 + WATER_ABSORPTION * 1.18));
        vec3 shallowTint = mix(vec3(0.026, 0.215, 0.305), WATER_COLOR * 1.34 + vec3(0.00, 0.030, 0.064), 0.34);
        vec3 deepTint = mix(vec3(0.010, 0.082, 0.205), WATER_COLOR * 1.10 + vec3(0.00, 0.018, 0.068), 0.18);
        vec3 scatterTint = mix(shallowTint, deepTint, clamp(screen_distance * 0.018, 0.0, 1.0));

        vec3 absorbedScene = blockColor.rgb * absorbColor;
        vec3 foggedScene = mix(absorbedScene, scatterTint * waterBrightness, waterFog * 0.80);

        // World-space suspended particles so they don't stick to the camera.
        vec2 particleCellA = floor(worldPosUW.xz * 2.2 + worldPosUW.y * 0.45 + vec2(frameCounter) * vec2(0.008, -0.006));
        vec2 particleCellB = floor(worldPosUW.xz * 3.8 - worldPosUW.y * 0.65 + vec2(frameCounter) * vec2(-0.005, 0.007));
        float particlesA = hash21(particleCellA);
        float particlesB = hash21(particleCellB + 13.17);
        float particles = smoothstep(0.9925, 0.9994, max(particlesA, particlesB));
        particles *= waterFog * clamp(1.0 - linearDepth * 0.72, 0.18, 1.0) * 0.58;

        blockColor.rgb = foggedScene + vec3(0.05, 0.10, 0.12) * particles * waterBrightness * 0.12;

        // Looking upwards should feel brighter and more aquatic instead of fully flat.
        float upScatter = clamp(1.0 - linearDepth * 0.95, 0.0, 1.0);
        blockColor.rgb += scatterTint * (0.052 + 0.105 * waterBrightness) * upScatter * (1.0 - rainStrength) * (0.34 + 0.52 * deepFog);
        // Small ambient floor so depth and nighttime do not collapse into total blackness.
        blockColor.rgb += vec3(0.012, 0.032, 0.046) * (0.24 + 0.62 * waterBrightness) * (0.22 + 0.60 * deepFog);

        // World-space underwater light response: no screen-space rectangles, no camera-following worms.
        #if !defined NETHER
            vec3 sunDirUW;
            #if VOL_LIGHT == 1
                sunDirUW = normalize((gbufferModelViewInverse * vec4(astroLightPos, 0.0)).xyz);
            #elif VOL_LIGHT == 2 && defined SHADOW_CASTING
                sunDirUW = normalize((gbufferModelViewInverse * vec4(shadowLightPosition, 0.0)).xyz);
            #else
                sunDirUW = normalize(vec3(0.25, 0.85, 0.35));
            #endif

            vec3 viewDirWorld = normalize((gbufferModelViewInverse * vec4(normalize(viewPosUW), 0.0)).xyz);
            float towardSun = clamp(dot(viewDirWorld, sunDirUW), 0.0, 1.0);
            float towardUp = clamp(viewDirWorld.y * 0.5 + 0.5, 0.0, 1.0);
            float upperWater = clamp(1.0 - linearDepth * 1.15, 0.0, 1.0);
            float depthFade = exp(-screen_distance * 0.042);
            float sunReach = clamp(sunDirUW.y * 0.9 + 0.1, 0.0, 1.0) * (1.0 - rainStrength);

            // Soft forward scattering only; remove caustic wall-crawling artifacts.
            float forwardScatter = pow(towardSun, 4.8) * waterFog * depthFade * upperWater * sunReach * (0.14 + 0.26 * towardUp);
            blockColor.rgb += vec3(0.024, 0.078, 0.104) * forwardScatter * (0.58 + 0.42 * waterBrightness);
        #endif

    } else if(isEyeInWater == 2) {

        blockColor = mix(blockColor, vec4(1.0, .1, 0.0, 1.0), clamp(sqrt(linearDepth * far * 0.125), 0.0, 1.0));
    }

    #if MC_VERSION >= 11900
        if((blindness > .01 || darknessFactor > .01) && linearDepth > 0.999) {
            blockColor.rgb = vec3(0.0);
        }
    #else
        if(blindness > .01 && linearDepth > 0.999) {
            blockColor.rgb = vec3(0.0);
        }
    #endif

    #if (VOL_LIGHT == 1 && !defined NETHER) || (VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER)
        #if AA_TYPE > 0
           float dither = shiftedDither17(gl_FragCoord.xy);
        #else
            float dither = rDither(gl_FragCoord.xy);
        #endif
    #endif

    #if VOL_LIGHT == 1 && !defined NETHER
        #if defined THE_END
            float volumetricLight = 0.1;
            if(d > 0.9999) {
                volumetricLight = 0.5;
            }
        #else
            float volumetricLight = ssGodrays(dither);
        #endif

        vec4 centerFarPlanePos = modeli_times_projectioni * (vec4(0.5, 0.5, 1.0, 1.0) * 2.0 - 1.0);
        vec3 centerEyeDirection = normalize(centerFarPlanePos.xyz);

        vec4 farPlaneClipPos = modeli_times_projectioni * (vec4(texcoord, 1.0, 1.0) * 2.0 - 1.0);
        vec3 eyeDirection = normalize(farPlaneClipPos.xyz);

        #if defined THE_END
            // Fixed light source position in sky for intensity calculation
            vec3 auxVector =
                normalize((gbufferModelViewInverse * gbufferModelView * vec4(0.0, 0.89442719, 0.4472136, 0.0)).xyz);
            float volumetricIntensity =
                clamp(dot(centerEyeDirection, auxVector), 0.0, 1.0);

            volumetricIntensity *= clamp(dot(eyeDirection, auxVector), 0.0, 1.0);

            volumetricIntensity *= 0.666;

            blockColor.rgb += vec3(0.0);
        #else
            // Light source position for depth based godrays intensity calculation
            vec3 auxVector =
                normalize((gbufferModelViewInverse * vec4(astroLightPos, 0.0)).xyz);
            float volumetricIntensity =
                clamp(dot(centerEyeDirection, auxVector), 0.0, 1.0);
            volumetricIntensity *= dot(eyeDirection, auxVector);
            volumetricIntensity =
                pow(clamp(volumetricIntensity, 0.0, 1.0), volumetricDayMixer) * 0.5 * abs(dayNightMix * 2.0 - 1.0);

            if(isEyeInWater == 1) {
                volumetricLight *= 1.18;
                volumetricIntensity *= 1.38;
                blockColor.rgb =
                    mix(blockColor.rgb, volumetricLightColor * vec3(0.40, 0.72, 1.00) * volumetricLight, volumetricIntensity * (volumetricLight * 0.55 + 0.25) * (1.0 - rainStrength));
            } else {
                blockColor.rgb =
                    mix(blockColor.rgb, volumetricLightColor * volumetricLight, volumetricIntensity * (volumetricLight * 0.5 + 0.5) * (1.0 - rainStrength));
            }
        #endif
    #endif

    #if VOL_LIGHT == 2 && defined SHADOW_CASTING && !defined NETHER
        #if defined COLORED_SHADOW
            vec3 volumetricLight = get_volumetric_color_light(dither, screen_distance, modeli_times_projectioni);
        #else
            float volumetricLight = get_volumetric_light(dither, screen_distance, modeli_times_projectioni);
        #endif

        // Volumetric intensity adjustments

        vec4 farPlaneClipPos = modeli_times_projectioni * (vec4(texcoord, 1.0, 1.0) * 2.0 - 1.0);
        vec3 eyeDirection = normalize(farPlaneClipPos.xyz);

        #if defined THE_END
            // Fixed light source position in sky for volumetrics intensity calculation (The End)
            float volumetricIntensity = dot(eyeDirection, normalize((gbufferModelViewInverse * gbufferModelView * vec4(0.0, 0.89442719, 0.4472136, 0.0)).xyz));
        #else
            // Light source position for volumetrics intensity calculation
            float volumetricIntensity = dot(eyeDirection, normalize((gbufferModelViewInverse * vec4(shadowLightPosition, 0.0)).xyz));
        #endif

        #if defined THE_END
            volumetricIntensity =
                ((squarePow(clamp((volumetricIntensity + .666667) * 0.6, 0.0, 1.0)) * 0.5));
            blockColor.rgb += vec3(0.0);
        #else
            volumetricIntensity =
                pow(clamp((volumetricIntensity + 0.5) * 0.666666666666666, 0.0, 1.0), volumetricDayMixer) * 0.6 * abs(dayNightMix * 2.0 - 1.0);

            if(isEyeInWater == 1) {
                volumetricLight *= 1.16;
                volumetricIntensity *= 1.32;
                blockColor.rgb =
                    mix(blockColor.rgb, volumetricLightColor * vec3(0.40, 0.72, 1.00) * volumetricLight, volumetricIntensity * (volumetricLight * 0.55 + 0.25) * (1.0 - rainStrength));
            } else {
                blockColor.rgb =
                    mix(blockColor.rgb, volumetricLightColor * volumetricLight, volumetricIntensity * (volumetricLight * 0.5 + 0.5) * (1.0 - rainStrength));
            }
        #endif
    #endif

    // Dentro de la nieve
    #ifdef BLOOM
        if(isEyeInWater == 3) {
            blockColor.rgb =
                mix(blockColor.rgb, vec3(0.7, 0.8, 1.0) / exposure, clamp(screen_distance, 0.0, 1.0));
        }
    #else
        if(isEyeInWater == 3) {
            blockColor.rgb =
                mix(blockColor.rgb, vec3(0.85, 0.9, 0.6), clamp(screen_distance, 0.0, 1.0));
        }
    #endif

    #ifdef BLOOM
        // Bloom source
        float bloom_luma = smoothstep(0.85, 1.0, luma(blockColor.rgb * exposure)) * 0.5;

        blockColor = clamp(blockColor, vec4(0.0), vec4(vec3(50.0), 1.0));     
        /* DRAWBUFFERS:146 */
        gl_FragData[0] = blockColor;
        gl_FragData[1] = blockColor * bloom_luma;
        gl_FragData[2] = vec4(exposure, 0.0, 0.0, 0.0);
    #else
        blockColor = clamp(blockColor, vec4(0.0), vec4(vec3(50.0), 1.0));
        /* DRAWBUFFERS:16 */
        gl_FragData[0] = blockColor;
        gl_FragData[1] = vec4(exposure, 0.0, 0.0, 0.0);
    #endif
}