#define LQ_CLOUD
#define LQ_SKY

#include "/lib/colors/lightAndAmbientColors.glsl"
#include "/lib/atmospherics/sky.glsl"

float GetDepth(float depth) {
    return 2.0 * near * far / (far + near - (2.0 * depth - 1.0) * (far - near));
}

float GetDistX(float dist) {
    return (far * (dist - near)) / (dist * (far - near));
}

vec4 DistortShadow(vec4 shadowpos, float distortFactor) {
    shadowpos.xy *= 1.0 / distortFactor;
    shadowpos.z = shadowpos.z * 0.2;
    shadowpos = shadowpos * 0.5 + 0.5;
    return shadowpos;
}

#ifndef END
#ifdef CLOUD_SHADOWS

#include "/lib/colors/skyColors.glsl"
#include "/lib/atmospherics/clouds/mainClouds.glsl"

#define PI 3.141592653589793
const float sunAngularSize = 0.533333;
const float SUN_ANGULAR_RADIUS_RAD = (sunAngularSize * 0.5) * (PI / 180.0);
const float INV_SHADOW_DISTANCE = 1.0 / max(shadowDistance, 1.0);
const float EPSILON = 1e-4;

int CloudShadowStepsLOD(float camDist) {
    float t = clamp(camDist * INV_SHADOW_DISTANCE, 0.0, 1.0);
    return 3 + int(step(t, 0.66));
}

bool RaySlabIntersectY(float yCenter, float halfThickness,
                       vec3 ro, vec3 rd, out float tEnter, out float tExit)
{
    float yMin = yCenter - halfThickness;
    float yMax = yCenter + halfThickness;
    if (abs(rd.y) < EPSILON) {
        tEnter = 0.0;
        tExit  = 0.0;
        return (ro.y >= yMin && ro.y <= yMax);
    }
    float invRy = 1.0 / rd.y;
    float t0 = (yMin - ro.y) * invRy;
    float t1 = (yMax - ro.y) * invRy;
    tEnter = min(t0, t1);
    tExit  = max(t0, t1);
    return (tExit > 0.0 && tExit > max(tEnter, 0.0));
}

float LowerLayerDensityFast(vec3 p, int steps, vec3 cameraPos)
{
    float lTracePosXZ   = length((p - cameraPos).xz);
    float cloudPlayerY  = p.y - float(cumulusLayerAlt);
    float d = GetCumulusCloud(p, steps, cumulusLayerAlt, lTracePosXZ, cloudPlayerY, CUMULUS_CLOUD_GRANULARITY, 1.0, (200.0 * 0.01));
    return max(d, 0.0);
}

float CloudVLTransmittanceAt(vec3 worldPos, vec3 sunDir_ws, vec3 cameraPos, float jitter)
{
    float yCenter = float(cumulusLayerAlt);
    float halfH   = cumulusLayerStretch * 0.15;
    float tEnter, tExit;
    if (!RaySlabIntersectY(yCenter, halfH, worldPos, sunDir_ws, tEnter, tExit)) return 1.0;
    float t0 = max(tEnter, 0.0);
    float t1 = max(tExit,  0.0);
    float lenInside = t1 - t0;
    if (lenInside <= EPSILON) return 1.0;
    float camDist = distance(cameraPos, worldPos);
    float distThreshold = float(CLOUD_RENDER_DISTANCE) * 0.6;
    int maxS = 2 + int(step(camDist, distThreshold));
    maxS = min(maxS, 3);
    float baseStep = (lenInside * 0.2) * mix(0.9, 1.1, fract(jitter * 43758.5453));
    float stepLen  = max(baseStep, lenInside / float(maxS));
    vec3 stepVec = sunDir_ws * stepLen;
    vec3 p = worldPos + sunDir_ws * (t0 + stepLen * 0.5);
    float dens = LowerLayerDensityFast(p, maxS, cameraPos);
    float tau = dens * stepLen;
    p += stepVec;
    float T = exp(-1.6 * tau);
    T = pow(T, 0.105); 
    return clamp(T, 0.0, 1.0);
}
#endif
#endif

float GetMiePhase(float cosTheta, float g) {
    float g2 = g * g;
    float denom = 1.0 + g2 - 2.0 * g * cosTheta;
    return (1.0 - g2) / (4.0 * 3.14159265 * pow(max(denom, 0.001), 1.5));
}

vec4 GetVolumetricLight(inout vec3 color, inout float vlFactor, vec3 translucentMult, float lViewPos0, float lViewPos1, vec3 nViewPos, float VdotL, float VdotU, float VdotS, vec2 texCoord, float z0, float z1, float dither) {
    vec4 volumetricLight = vec4(0.0);
    float vlMult = 10.0 - maxBlindnessDarkness;

    #if SHADOW_QUALITY > -1
        vec2 shadowMapResolutionM = textureSize(shadowtex0, 0);
    #endif

    #ifdef OVERWORLD
        vec3 vlColor = lightColor * 0.5 + GetSky(VdotU, VdotS, dither, true, true) * 0.5; 

        
        float vlSceneIntensity = isEyeInWater != 1 ? vlFactor : 1.0;

        #ifdef SPECIAL_BIOME_WEATHER
            vlSceneIntensity = mix(vlSceneIntensity, 1.0, inDry * rainFactor);
        #endif

        if (sunVisibility < 0.5) {
            vlSceneIntensity = 0.0;
            float vlMultNightModifier = 0.6 + 0.4 * max0(far - lViewPos1) / far;
            #ifdef SPECIAL_PALE_GARDEN_LIGHTSHAFTS
                vlMultNightModifier = mix(vlMultNightModifier, 1.0, inPaleGarden);
            #endif
            vlMult *= vlMultNightModifier;

            vlColor = mix(vlColor, vec3(length(vlColor)), 0.5) * vec3(0.1, 0.15, 0.25);
            vlColor *= 0.0766 + 0.0766 * vsBrightness;
        } 

        #ifdef SPECIAL_PALE_GARDEN_LIGHTSHAFTS
            vlSceneIntensity = mix(vlSceneIntensity, 1.0, inPaleGarden);
            vlMult *= 1.0 + (3.0 * inPaleGarden) * (1.0 - sunVisibility);
        #endif

        float currentG = mix(0.75, 0.4, noonFactor); 

        float phase = GetMiePhase(VdotL, currentG);

        float scattering = phase + (0.15 * noonFactor); 
        float rainyNight = (1.0 - sunVisibility) * rainFactor;
        scattering = mix(scattering, 0.5, rainyNight * 0.5);
        vlMult *= scattering * vlTime;
        vlMult *= mix(invNoonFactor2 * 3.0 + 4.0, 1.0, max(vlSceneIntensity, rainFactor2));
        
        vlMult *= 0.08; 

        #if LIGHTSHAFT_QUALI == 4
            int sampleCount = vlSceneIntensity < 0.5 ? 30 : 50;
        #elif LIGHTSHAFT_QUALI == 3
            int sampleCount = vlSceneIntensity < 0.5 ? 15 : 30;
        #elif LIGHTSHAFT_QUALI == 2
            int sampleCount = vlSceneIntensity < 0.5 ? 10 : 20;
        #elif LIGHTSHAFT_QUALI == 1
            int sampleCount = vlSceneIntensity < 0.5 ? 6 : 12;
        #endif

        #ifdef LIGHTSHAFT_SMOKE
            float totalSmoke = 0.0;
        #endif
    #else
        translucentMult = sqrt(translucentMult); 
        float vlSceneIntensity = 0.0;
        #ifndef LOW_QUALITY_ENDER_NEBULA
            int sampleCount = 16;
        #else
            int sampleCount = 10;
        #endif
    #endif

    float addition = 1.0;
    float maxDist = 0.0;

    float depth0 = GetDepth(z0);
    float depth1 = GetDepth(z1);
    
    maxDist = mix(max(far, 96.0) * 0.55, 80.0, vlSceneIntensity);

    #if WATER_FOG_MULT != 100
        if (isEyeInWater == 1) {
            #define WATER_FOG_MULT_M WATER_FOG_MULT * 0.001;
            maxDist /= WATER_FOG_MULT_M;
        }
    #endif

    float sampleMultIntense = isEyeInWater != 1 ? 1.0 : 0.85;
    float distMult = maxDist / (sampleCount + 1.0);
    float viewFactor = 1.0 - 0.7 * pow2(dot(nViewPos.xy, nViewPos.xy));

    #ifdef END
        if (z0 == 1.0) depth0 = 1000.0;
        if (z1 == 1.0) depth1 = 1000.0;
    #endif

    maxDist *= viewFactor;
    distMult *= viewFactor;
    
    float horizonBoost = clamp(1.0 - abs(VdotU), 0.0, 1.0);
    maxDist += mix(0.0, 2.0, horizonBoost);

    float sampleMult = 1.0 / float(sampleCount);

    #ifdef OVERWORLD
        float maxCurrentDist = min(depth1, maxDist);
    #else
        float maxCurrentDist = min(depth1, far);
    #endif

    for (int i = 0; i < sampleCount; i++) {
        float currentDist = (i + dither) * distMult + addition;

        if (currentDist > maxCurrentDist) break;

        vec4 viewPos = gbufferProjectionInverse * (vec4(texCoord, GetDistX(currentDist), 1.0) * 2.0 - 1.0);
        viewPos /= viewPos.w;
        vec4 wpos = gbufferModelViewInverse * viewPos;
        vec3 playerPos = wpos.xyz / wpos.w;
        #ifdef END
            #ifdef DISTANT_HORIZONS
                playerPos *= sqrt(renderDistance / far);
            #endif
           vec4 enderBeamSample = vec4(0.0);
        #endif

        float shadowSample = 1.0;
        vec3 vlSample = vec3(1.0);

        #if SHADOW_QUALITY > -1
            wpos = shadowModelView * wpos;
            wpos = shadowProjection * wpos;
            wpos /= wpos.w;
            float distb = sqrt(wpos.x * wpos.x + wpos.y * wpos.y);
            float distortFactor = 1.0 - shadowMapBias + distb * shadowMapBias;
            vec4 shadowPosition = DistortShadow(wpos,distortFactor);

            #ifdef OVERWORLD
                float percentComplete = currentDist / maxDist;
                float densityFalloff = exp(-0.02 * currentDist); 
                
                float loopSampleMult = mix(percentComplete * 3.0, sampleMultIntense, max(rainFactor, vlSceneIntensity));
                loopSampleMult *= densityFalloff; 
                
                if (currentDist < 5.0) loopSampleMult *= smoothstep1(clamp(currentDist / 5.0, 0.0, 1.0));
                loopSampleMult /= sampleCount;
            #endif

            if (length(shadowPosition.xy * 2.0 - 1.0) < 1.0) {
                shadowSample = texelFetch(shadowtex0, ivec2(shadowPosition.xy * shadowMapResolutionM), 0).x;
                shadowSample = clamp((shadowSample-shadowPosition.z)*65536.0,0.0,1.0);

                vlSample = vec3(shadowSample);

                #if SHADOW_QUALITY >= 1
                    if (shadowSample == 0.0) {
                        float testsample = shadow2D(shadowtex1, shadowPosition.xyz).z;
                        if (testsample == 1.0) {
                            vec3 colsample = texture2D(shadowcolor1, shadowPosition.xy).rgb * 4.0;
                            colsample *= colsample; 
                            vlSample = colsample;
                            shadowSample = 1.0;
                        }
                    } else {
                        #ifdef OVERWORLD
                            if (translucentMult != vec3(1.0) && currentDist > depth0) {
                                vec3 tinter = vec3(1.0);
                                if (isEyeInWater == 1) {
                                    vec3 translucentMultM = translucentMult * 2.8;
                                    tinter = pow(translucentMultM, vec3(sunVisibility * 3.0 * clamp01(playerPos.y * 0.03)));
                                } else {
                                    tinter = 0.1 + 0.9 * pow2(pow2(translucentMult * 1.7));
                                }
                                vlSample *= mix(vec3(1.0), tinter, clamp01(oceanAltitude - cameraPosition.y));
                            }
                        #endif
                        if (isEyeInWater == 1 && translucentMult == vec3(1.0)) vlSample = vec3(0.0);
                    }
                #endif
            }
        #endif

        if (currentDist > depth0) vlSample *= translucentMult;
        
        #ifdef CLOUD_SHADOWS
            if (isEyeInWater != 1) {
                vec3 worldPos  = playerPos + cameraPosition;
                vec3 sunDir_ws = normalize(mat3(gbufferModelViewInverse) * lightVec);
                float cloudT   = CloudVLTransmittanceAt(worldPos, sunDir_ws, cameraPosition, dither);
                vlSample     *= cloudT;
            }
        #endif

        #ifdef OVERWORLD
            #ifdef LIGHTSHAFT_SMOKE
                vec3 smokePos  = 0.00055 * (playerPos + cameraPosition);
                vec3 smokeWind = frameTimeCounter * vec3(0.002, 0.001, 0.0) * 0.1;
                float smoke = 0.65 * Noise3D(smokePos + smokeWind)
                            + 0.25 * Noise3D((smokePos - smokeWind) * 3.0)
                            + 0.10 * Noise3D((smokePos + smokeWind) * 9.0);
                smoke = smoothstep1(smoothstep1(smoothstep1(smoke)));
                float smokeMask = max(smoke - 0.0, 0.0);
                smokeMask = pow(smokeMask, 3.0);
                vlSample     *= smokeMask;
                shadowSample *= smokeMask;
                volumetricLight += vec4(vlSample, shadowSample) * loopSampleMult * smokeMask;
            #else
                volumetricLight += vec4(vlSample, shadowSample) * loopSampleMult;
            #endif
        #else
            volumetricLight += vec4(vlSample, shadowSample) * enderBeamSample;
        #endif
    }

    #ifdef LIGHTSHAFT_SMOKE
        float smokeVisibility = 10.0;
        volumetricLight += pow(totalSmoke / volumetricLight.a, min(smokeVisibility - volumetricLight.a, smokeVisibility));
        volumetricLight.rgb /= pow(0.5, 1.0 - volumetricLight.a);
    #endif

    // SALS Logic (Scene Aware Light Shafts)
    #if defined OVERWORLD && LIGHTSHAFT_BEHAVIOUR == 1 && SHADOW_QUALITY >= 1
        if (viewWidth + viewHeight - gl_FragCoord.x - gl_FragCoord.y < 1.5) {
            if (frameCounter % int(0.06666 / frameTimeSmooth + 0.5) == 0) {
                int salsX = 5; int salsY = 5; float heightThreshold = 6.0;
                vec2 viewM = 1.0 / vec2(salsX, salsY);
                float salsSampleSum = 0.0; int salsSampleCount = 0;
                for (float i = 0.25; i < salsX; i++) {
                    for (float h = 0.45; h < salsY; h++) {
                        vec2 coord = 0.3 + 0.4 * viewM * vec2(i, h);
                        ivec2 icoord = ivec2(coord * shadowMapResolutionM);
                        float salsSample = texelFetch(shadowtex0, icoord, 0).x;
                        if (salsSample < 0.55) {
                            float sampledHeight = texture2D(shadowcolor1, coord).a;
                            if (sampledHeight > 0.0) {
                                sampledHeight = max0(sampledHeight - 0.25) / 0.05;
                                salsSampleSum += sampledHeight;
                                salsSampleCount++;
                            }
                        }
                    }
                }
                float salsCheck = salsSampleSum / salsSampleCount;
                int reduceAmount = 2;
                int skyCheck = 0;
                for (float i = 0.1; i < 1.0; i += 0.2) {
                    skyCheck += int(texelFetch(depthtex0, ivec2(view.x * i, view.y * 0.9), 0).x == 1.0);
                }
                if (skyCheck >= 4) { salsCheck = 0.0; reduceAmount = 3; }
                if (salsCheck > heightThreshold) { vlFactor = min(vlFactor + OSIEBCA, 0.25); } 
                else { vlFactor = max(vlFactor - OSIEBCA * reduceAmount, 0.0); }
            }
        } else vlFactor = 0.0;
    #endif

    #ifdef OVERWORLD
        // Apply Colors
        #if LIGHTSHAFT_DAY_I != 100 || LIGHTSHAFT_NIGHT_I != 100 || LIGHTSHAFT_RAIN_I != 100
            #define LIGHTSHAFT_DAY_IM LIGHTSHAFT_DAY_I * 0.01
            #define LIGHTSHAFT_NIGHT_IM LIGHTSHAFT_NIGHT_I * 0.01
            #define LIGHTSHAFT_RAIN_IM LIGHTSHAFT_RAIN_I * 0.01

            if (isEyeInWater == 0) {
                #if LIGHTSHAFT_DAY_I != 100 || LIGHTSHAFT_NIGHT_I != 100
                    vlColor.rgb *= mix(LIGHTSHAFT_NIGHT_IM, LIGHTSHAFT_DAY_IM, sunVisibility);
                #endif
                #if LIGHTSHAFT_RAIN_I != 100
                    vlColor.rgb *= mix(1.0, LIGHTSHAFT_RAIN_IM, rainFactor);
                #endif
            }
        #endif

        volumetricLight.rgb *= vlColor;
    #endif

    volumetricLight.rgb *= vlMult;
    volumetricLight = max(volumetricLight, vec4(0.0));

    #ifdef DISTANT_HORIZONS
        if (isEyeInWater == 0) {
            #ifdef OVERWORLD
                float lViewPosM = lViewPos0;
                if (z0 >= 1.0) {
                    float z0DH = texelFetch(dhDepthTex, texelCoord, 0).r;
                    vec4 screenPosDH = vec4(texCoord, z0DH, 1.0);
                    vec4 viewPosDH = dhProjectionInverse * (screenPosDH * 2.0 - 1.0);
                    viewPosDH /= viewPosDH.w;
                    lViewPosM = length(viewPosDH.xyz);
                }
                lViewPosM = min(lViewPosM, renderDistance * 0.1);
                float dhVlStillIntense = max(max(vlSceneIntensity, rainFactor), nightFactor * 0.5);
                volumetricLight *= mix(0.0003 * lViewPosM, 1.0, dhVlStillIntense) * 3.0;
            #else
                volumetricLight *= min1(lViewPos1 * 3.0 / renderDistance);
            #endif
        }
    #endif

    #ifndef DISTANT_HORIZONS
        volumetricLight *= 0.5;
    #else
        volumetricLight *= 2.0;
    #endif

    return volumetricLight * 2.0;
}