/*
 * =======================================================================================
 *
 * MAIN ATMOSPHERIC RENDERING SYSTEM
 * Lunara Shader - Core Atmosphere Module
 *
 * =======================================================================================
 *
 * Copyright (c) 2025 Lunara Development
 * All rights reserved. Unauthorized reproduction or distribution is prohibited.
 *
 * This is the main atmospheric rendering module that coordinates volumetric atmospheric
 * effects, shadow calculations, and lighting interactions for the Lunara Shader.
 *
 * =======================================================================================
 */

//=======================================================================================
// DEPENDENCIES
//=======================================================================================

#include "/lib/colors/core.glsl"

#ifdef MOON_PHASE_INF_ATMOSPHERE
    #include "/lib/colors/effects.glsl"
#endif

#include "/lib/sky/volumetric.glsl"

//=======================================================================================
// UTILITY FUNCTIONS
//=======================================================================================

/**
 * Generates interleaved gradient noise for atmospheric dithering
 * @return: Noise value [0.0, 1.0] for temporal anti-aliasing
 */
float lu_GenerateAtmosphericNoise()
{
    float noiseBase = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);

    #ifdef LU_TAA
        // Use temporal variation for smoother sampling
        return fract(noiseBase + goldenRatio * mod(float(frameCounter), 3600.0));
    #else
        // Static noise for consistent sampling
        return fract(noiseBase);
    #endif
}

//=======================================================================================
// LU_SHADOW SYSTEM FUNCTIONS
//=======================================================================================

#if LU_SHADOW_QUALITY > -1

/**
 * Transforms atmospheric position to shadow map coordinates
 * @param tracePos: Position in atmospheric space
 * @param cameraPos: Current camera position
 * @return: Shadow map coordinates [0.0, 1.0]
 */
vec3 lu_TransformToShadowSpace(vec3 tracePos, vec3 cameraPos)
{
    vec3 worldPosition = lu_PlayerToShadow(tracePos - cameraPos);

    // Calculate distance-based distortion
    float horizontalDistance = sqrt(worldPosition.x * worldPosition.x + worldPosition.y * worldPosition.y);
    float distortionFactor = 1.0 - shadowMapBias + horizontalDistance * shadowMapBias;

    // Apply shadow transformation
    vec3 shadowPosition = vec3(
        vec2(worldPosition.xy / distortionFactor),
        worldPosition.z * 0.2
    );

    return shadowPosition * 0.5 + 0.5;
}

/**
 * Performs shadow testing for atmospheric elements
 * @param tracePos: Position to test for shadows
 * @param cameraPos: Current camera position
 * @param altitudeLevel: Atmospheric altitude level
 * @param lowerBound: Lower altitude boundary
 * @param upperBound: Upper altitude boundary
 * @return: true if position is in shadow, false otherwise
 */
bool lu_TestAtmosphericShadow(vec3 tracePos, vec3 cameraPos, int altitudeLevel,
                          float lowerBound, float upperBound)
{
    const float shadowOffset = 0.5;

    vec3 shadowCoordinates = lu_TransformToShadowSpace(tracePos, cameraPos);

    // Check if coordinates are within shadow map bounds
    if (length(shadowCoordinates.xy * 2.0 - 1.0) < 1.0) {
        float shadowSample = shadow2D(shadowtex0, shadowCoordinates).z;

        if (shadowSample == 0.0) return true;
    }

    return false;
}

#endif

//=======================================================================================
// ATMOSPHERIC MODULE INCLUDES
//=======================================================================================

#ifdef LU_CLOUDS_REIMAGINED
    // weather.glsl already includes volumetric.glsl internally
    #include "/lib/sky/scatter/weather.glsl"
#endif

#ifdef LU_CLOUDS_UNBOUND
    #include "/lib/sky/scatter/weather.glsl"
#endif

//=======================================================================================
// MAIN ATMOSPHERIC RENDERING FUNCTION
//=======================================================================================

/**
 * Main atmospheric rendering pipeline
 * @param atmosphericDepth: Output depth information for atmospheric elements
 * @param skyFade: Sky fade factor for blending
 * @param cameraPos: Current camera position
 * @param playerPos: Player view direction
 * @param viewDistance: Current view distance
 * @param sunDotProduct: Dot product with sun direction
 * @param upDotProduct: Dot product with up vector
 * @param ditherNoise: Dithering noise for sampling
 * @param auroraContribution: Aurora borealis color contribution
 * @param nebulaContribution: Night nebula color contribution
 * @return: Final atmospheric color and alpha
 */
vec4 lu_RenderAtmosphericEffects(inout float atmosphericDepth, float skyFade, vec3 cameraPos,
                             vec3 playerPos, float viewDistance, float sunDotProduct,
                             float upDotProduct, float ditherNoise, vec3 auroraContribution,
                             vec3 nebulaContribution)
{
    vec4 atmosphericResult = vec4(0.0);

    // Normalize player position for calculations
    vec3 normalizedPlayerPos = normalize(playerPos);

    // Calculate effective view distance with safety bounds
    float effectiveViewDistance = viewDistance < renderDistance * 1.5 ? viewDistance - 1.0 : 1000000000.0;

    // Calculate sky fade multiplier for blending
    float skyFadeMultiplier = lu_pow2(skyFade * 3.333333 - 2.333333);

    // Calculate view threshold based on up vector orientation
    float thresholdMixFactor = lu_pow2(lu_clamp01(upDotProduct * 5.0));
    float viewThreshold = mix(far, 1000.0, thresholdMixFactor * 0.5 + 0.5);

    #ifdef DISTANT_HORIZONS
        viewThreshold = max(viewThreshold, renderDistance);
    #endif

    // Apply weather-based color adjustments
    #ifdef LU_CLOUDS_REIMAGINED
        cloudAmbientColor *= 1.0 - 0.25 * rainFactor;
    #endif

    // Apply custom color multipliers
    vec3 atmosphericColorMultiplier = vec3(1.0);
    #if LU_CLOUD_R != 100 || LU_CLOUD_G != 100 || LU_CLOUD_B != 100
        atmosphericColorMultiplier *= vec3(LU_CLOUD_R, LU_CLOUD_G, LU_CLOUD_B) * 0.01;
    #endif

    cloudAmbientColor *= atmosphericColorMultiplier;
    cloudLightColor *= atmosphericColorMultiplier;

    // Render atmospheric layers based on configuration
    #if !defined DOUBLE_REIM_CLOUDS || defined LU_CLOUDS_UNBOUND
        // Single layer rendering
        atmosphericResult = lu_GetVolumetricClouds(
            cloudAlt1i, viewThreshold, atmosphericDepth, skyFade, skyFadeMultiplier,
            cameraPos, normalizedPlayerPos, effectiveViewDistance, sunDotProduct,
            upDotProduct, ditherNoise
        );
    #else
        // Dual layer rendering with altitude-based prioritization
        int primaryAltitude = max(cloudAlt1i, cloudAlt2i);
        int secondaryAltitude = min(cloudAlt1i, cloudAlt2i);

        if (abs(cameraPos.y - secondaryAltitude) < abs(cameraPos.y - primaryAltitude)) {
            // Render closer layer first
            atmosphericResult = lu_GetVolumetricClouds(
                secondaryAltitude, viewThreshold, atmosphericDepth, skyFade, skyFadeMultiplier,
                cameraPos, normalizedPlayerPos, effectiveViewDistance, sunDotProduct,
                upDotProduct, ditherNoise
            );

            // Fallback to farther layer if no contribution
            if (atmosphericResult.a == 0.0) {
                atmosphericResult = lu_GetVolumetricClouds(
                    primaryAltitude, viewThreshold, atmosphericDepth, skyFade, skyFadeMultiplier,
                    cameraPos, normalizedPlayerPos, effectiveViewDistance, sunDotProduct,
                    upDotProduct, ditherNoise
                );
            }
        } else {
            // Render farther layer first
            atmosphericResult = lu_GetVolumetricClouds(
                primaryAltitude, viewThreshold, atmosphericDepth, skyFade, skyFadeMultiplier,
                cameraPos, normalizedPlayerPos, effectiveViewDistance, sunDotProduct,
                upDotProduct, ditherNoise
            );

            // Fallback to closer layer if no contribution
            if (atmosphericResult.a == 0.0) {
                atmosphericResult = lu_GetVolumetricClouds(
                    secondaryAltitude, viewThreshold, atmosphericDepth, skyFade, skyFadeMultiplier,
                    cameraPos, normalizedPlayerPos, effectiveViewDistance, sunDotProduct,
                    upDotProduct, ditherNoise
                );
            }
        }
    #endif

    // Apply atmospheric color modifications
    #ifdef ATM_COLOR_MULTS
        atmosphericResult.rgb *= sqrtAtmColorMult;
    #endif

    #ifdef MOON_PHASE_INF_ATMOSPHERE
        atmosphericResult.rgb *= moonPhaseInfluence;
    #endif

    // Add special atmospheric effects
    #if LU_AURORA_STYLE > 0
        atmosphericResult.rgb += auroraContribution * 0.1;
    #endif

    #ifdef NIGHT_NEBULA
        atmosphericResult.rgb += nebulaContribution * 0.2;
    #endif

    return atmosphericResult;
}

//=======================================================================================
// LEGACY COMPATIBILITY ALIASES
//=======================================================================================

// Maintain compatibility with existing shader code
#define LU_InterleavedGradientNoiseForClouds lu_GenerateAtmosphericNoise
#define LU_GetShadowOnCloudPosition lu_TransformToShadowSpace
#define LU_GetShadowOnCloud lu_TestAtmosphericShadow
#define LU_GetClouds lu_RenderAtmosphericEffects
