layout(location = 0) out vec3 colortex7Out;
layout(location = 1) out vec4 reflectionData;
layout(location = 2) out vec4 colortex3Out;

/* DRAWBUFFERS:723 */

#include "/lib/Head/Common.inc"
#include "/lib/Head/Uniforms.inc"

in vec2 lightmap;
in vec3 minecraftPos;
in vec4 tint;
in vec4 viewPos;

flat in mat3 tbnMatrix;
flat in uint materialIDs;

#include "/lib/Atmosphere/Atmosphere.glsl"
#include "/lib/Water/WaterWave.glsl"

vec2 GetWaterParallaxCoord(in vec3 position, in vec3 tangentViewVector) {
    vec3 stepSize = tangentViewVector * vec3(vec2(0.1 * WATER_WAVE_HEIGHT), 1.0);
    stepSize *= 0.02 / abs(stepSize.z);

    vec3 samplePos = vec3(position.xz - position.y, 1.0) + stepSize;
    float sampleHeight = WaterHeight(samplePos.xy);

    for (uint i = 0u; sampleHeight < samplePos.z && i < 60u; ++i) {
        samplePos += stepSize;
        sampleHeight = WaterHeight(samplePos.xy);
    }

    return samplePos.xy;
}

#include "/lib/Surface/RainEffect.glsl"
#include "/lib/Head/Functions.inc"
#include "/lib/Head/Material.inc"
#include "/lib/Surface/ScreenSpaceReflections.glsl"

vec3 UnderwaterReflectionBlur(vec3 centerReflection, vec2 screenCoord) {
    float centerLuma = GetLuminance(centerReflection);
    
    vec2 toEdge = min(screenCoord, screenSize - screenCoord - vec2(1.0));
    int safeRadius = int(min(min(toEdge.x, toEdge.y), 2.0));
    
    if (safeRadius < 1) {
        return centerReflection;
    }
    
    vec3 weightedSum = vec3(0.0);
    float totalWeight = 0.0;
    float maxLuma = centerLuma;
    vec3 brightestColor = centerReflection;
    
    for (int x = -safeRadius; x <= safeRadius; x++) {
        for (int y = -safeRadius; y <= safeRadius; y++) {
            if (x == 0 && y == 0) continue;
            
            ivec2 sampleCoord = ivec2(screenCoord) + ivec2(x, y);
            vec3 neighborColor = texelFetch(colortex4, sampleCoord, 0).rgb;
            float neighborLuma = GetLuminance(neighborColor);
            
            if (neighborLuma > maxLuma) {
                maxLuma = neighborLuma;
                brightestColor = neighborColor;
            }
            
            float dist = sqrt(float(x*x + y*y));
            float distWeight = 1.0 / (1.0 + dist * 0.6);
            float brightnessWeight = neighborLuma * neighborLuma * 3.0 + 0.2;
            if (neighborLuma < 0.12) brightnessWeight *= 0.3;
            
            float combinedWeight = distWeight * brightnessWeight;
            weightedSum += neighborColor * combinedWeight;
            totalWeight += combinedWeight;
        }
    }
    
    if (totalWeight < 0.001) {
        return brightestColor;
    }
    
    vec3 brightDominantColor = weightedSum / totalWeight;
    vec3 dilatedColor = max(centerReflection, brightestColor);
    float darkFactor = 1.0 - smoothstep(0.0, 0.25, centerLuma);
    vec3 targetColor = mix(dilatedColor, brightDominantColor, darkFactor);
    
    return targetColor;
}

vec4 CalculateSpecularReflections(in vec3 normal, in float skylight, in vec3 viewPos) {
    skylight = smoothstep(0.3, 0.8, skylight);
    vec3 viewDir = normalize(viewPos);
    
    bool isUnderwater = (isEyeInWater == 1);
    
    vec3 rayDir;
    if (isUnderwater) {
        rayDir = CalculateUnderwaterReflectDir(viewDir, normal);
    } else {
        rayDir = reflect(viewDir, normal);
    }
    
    float NdotL = dot(normal, rayDir);
    if (NdotL < 1e-6) return vec4(0.0);
    
    float dither = InterleavedGradientNoiseTemporal(gl_FragCoord.xy);
    vec3 screenPos = vec3(gl_FragCoord.xy * screenPixelSize, gl_FragCoord.z);
    
    float NdotV = max(1e-6, dot(normal, -viewDir));
    
    bool hit = ScreenSpaceRayTraceDH(viewPos, rayDir, dither, RAYTRACE_SAMPLES, screenPos, isUnderwater);
    
    vec3 reflection;
    float reflectionConfidence = 0.0;
    
    if (materialIDs == 17u) {
        #ifdef REAL_SKY_REFLECTION
            if (!isUnderwater) {
                if (skylight > 1e-3) {
                    vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                    float NdotU = saturate((dot(normal, gbufferModelView[1].xyz) + 0.7) * 2.0) * 0.75 + 0.25;
                    vec4 skyboxData = textureBicubic(colortex5, ProjectSky(rayDirWorld) + vec2(0.0, skyCaptureRes.y * screenPixelSize.y));
                    
                    vec3 sunmoon = RenderSunReflection(rayDirWorld, worldSunVector);
                    sunmoon += RenderMoonReflection(rayDirWorld, worldSunVector);
                    
                    reflection = hit ? texelFetch(colortex4, ivec2(screenPos.xy), 0).rgb : skyboxData.rgb * skylight * NdotU;
                    reflection += sunmoon * skyboxData.a * skylight * AtmosphereAbsorption(rayDirWorld, AtmosphereExtent);
                    reflectionConfidence = hit ? 1.0 : 0.3;
                }
            } else {
                if (hit) {
                    reflection = texelFetch(colortex4, ivec2(screenPos.xy), 0).rgb;
                    reflectionConfidence = 1.0;
                } else {
                    vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                    float upFactor = saturate(rayDirWorld.y * 0.5 + 0.5);
                    reflection = vec3(0.05, 0.4, 0.8) * 0.4 * (1.0 + upFactor * 2.0);
                    reflectionConfidence = 0.2;
                }
            }
        #else
            if (hit) {
                reflection = texelFetch(colortex4, ivec2(screenPos.xy), 0).rgb;
                reflectionConfidence = 1.0;
            }
            #if defined IS_OVERWORLD
                else if (skylight > 1e-3) {
                    if (!isUnderwater) {
                        vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                        float NdotU = saturate((dot(normal, gbufferModelView[1].xyz) + 0.7) * 2.0) * 0.75 + 0.25;
                        vec4 skyboxData = textureBicubic(colortex5, ProjectSky(rayDirWorld) + vec2(0.0, skyCaptureRes.y * screenPixelSize.y));
                        reflection = skyboxData.rgb * skylight * NdotU;
                        reflectionConfidence = 0.3;
                    } else {
                        vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                        float upFactor = saturate(rayDirWorld.y * 0.5 + 0.5);
                        reflection = vec3(0.05, 0.4, 0.8) * 0.3 * (1.0 + upFactor) * (timeNoon + timeMidnight * NIGHT_BRIGHTNESS);
                        reflectionConfidence = 0.15;
                    }
                }
            #elif defined IS_END
                else {
                    if (!isUnderwater) {
                        vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                        float NdotU = saturate((dot(normal, gbufferModelView[1].xyz) + 0.7) * 2.0) * 0.75 + 0.25;
                        #ifdef DARK_END
                            bool darkEnd = bossBattle == 2 || bossBattle == 3;
                        #else
                            const bool darkEnd = false;
                        #endif
                        vec3 sunDisc = RenderSun(rayDirWorld, worldSunVector);
                        sunDisc *= vec3(0.99, 0.93, 0.65) * 0.1;
                        reflection = mix(vec3(0.396, 0.352, 0.108), vec3(0.04, 0.02, 0.05), float(darkEnd) * 0.9) * 2.0 * exp2(-max0(rayDirWorld.y) * 1.5);
                        if (!darkEnd) reflection += sunDisc;
                        reflection *= NdotU;
                        reflectionConfidence = 0.3;
                    } else {
                        reflection = vec3(0.05, 0.3, 0.6) * 0.2;
                        reflectionConfidence = 0.1;
                    }
                }
            #endif
        #endif
    } else {
        if (hit) {
            reflection = texelFetch(colortex4, ivec2(screenPos.xy), 0).rgb;
            reflectionConfidence = 1.0;
        }
        #if defined IS_OVERWORLD
            else if (skylight > 1e-3) {
                vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                float NdotU = saturate((dot(normal, gbufferModelView[1].xyz) + 0.7) * 2.0) * 0.75 + 0.25;
                vec4 skyboxData = textureBicubic(colortex5, ProjectSky(rayDirWorld) + vec2(0.0, skyCaptureRes.y * screenPixelSize.y));
                reflection = skyboxData.rgb * skylight * NdotU;
                reflectionConfidence = 0.3;
            }
        #elif defined IS_END
            else {
                vec3 rayDirWorld = mat3(gbufferModelViewInverse) * rayDir;
                float NdotU = saturate((dot(normal, gbufferModelView[1].xyz) + 0.7) * 2.0) * 0.75 + 0.25;
                #ifdef DARK_END
                    bool darkEnd = bossBattle == 2 || bossBattle == 3;
                #else
                    const bool darkEnd = false;
                #endif
                vec3 sunDisc = RenderSun(rayDirWorld, worldSunVector);
                sunDisc *= vec3(0.99, 0.93, 0.65) * 0.1;
                reflection = mix(vec3(0.396, 0.352, 0.108), vec3(0.04, 0.02, 0.05), float(darkEnd) * 0.9) * 2.0 * exp2(-max0(rayDirWorld.y) * 1.5);
                if (!darkEnd) reflection += sunDisc;
                reflection *= NdotU;
                reflectionConfidence = 0.3;
            }
        #endif
    }
    
    float specular;
    if (isUnderwater) {
        specular = FresnelDielectricN(NdotV, 1.0 / WATER_REFRACT_IOR);
    } else {
        specular = FresnelDielectricN(NdotV, materialIDs == 17u ? WATER_REFRACT_IOR : GLASS_REFRACT_IOR);
    }
    
    if (isUnderwater) {
        reflection *= 0.85;
        reflection = UnderwaterReflectionBlur(reflection, screenPos.xy);
    }
    
    return clamp16F(vec4(reflection * specular, 1.0 - specular));
}

void main() {
    if (length(minecraftPos - cameraPosition) < 0.8 * far) { discard; return; }

    if (texelFetch(depthtex0, ivec2(gl_FragCoord.xy), 0).x < 1.0) { discard; return; }

    vec4 albedo = tint;

    vec3 normalData = tbnMatrix[2];

    if (materialIDs == 17u) {
        #ifdef WATER_PARALLAX
            vec2 position = GetWaterParallaxCoord(minecraftPos, normalize(viewPos.xyz * tbnMatrix));
            normalData = GetWavesNormal(position);
        #else
            normalData = GetWavesNormal(minecraftPos.xz - minecraftPos.y);
        #endif

        normalData = normalize(tbnMatrix * normalData);
    }

    colortex7Out.xy = lightmap + (bayer4(gl_FragCoord.xy) - 0.5) * rcp(255.0);
    colortex7Out.z = float(materialIDs + 0.1) * rcp(255.0);

    colortex3Out.xy = EncodeNormal(normalData);
    colortex3Out.z = PackUnorm2x8(albedo.rg);
    colortex3Out.w = PackUnorm2x8(albedo.ba);

    reflectionData = CalculateSpecularReflections(normalData, cube(lightmap.g), viewPos.xyz);
}