#version 330 core
/*
 * Advanced Atmospheric Dehazing Shader
 * ------------------------------------
 * This shader simulates the behavior of Adobe Lightroom/Photoshop's Dehaze feature using:
 * 
 * 1. Dark Channel Prior (DCP) technique for estimating atmospheric light and transmission
 *    - In photography, haze appears as a low-contrast, brightened, desaturated overlay 
 *    - DCP assumes that haze-free regions have at least one RGB channel with very low intensity
 *    - By detecting regions where all RGB channels are high, we can identify hazy areas
 * 
 * 2. Multi-scale contrast enhancement with perceptual considerations
 *    - Detail recovery is based on local contrast improvement
 *    - Preserves color relationships similar to Lightroom by boosting both contrast and saturation
 * 
 * 3. Adaptive atmospheric light estimation
 *    - Uses a simplified version of sky/atmospheric light detection
 *    - Approximates the global atmospheric light color (typically grayish-blue)
 * 
 * 4. Tone-aware processing
 *    - Protects highlights from clipping, similar to Lightroom's implementation
 *    - Preserves natural shadows while improving visibility
 *
 * 5. Implements a non-linear response curve that matches the perceptual effect of Lightroom's
 *    dehaze slider within the -100 to +100 range
 */

out vec4 FragColor;
in vec2 TexCoord;

uniform sampler2D InputTexture;
uniform float dehazeValue; // -100 to 100 (negative values add haze)

// Perceptual luminance weights (Rec. 709)
const vec3 luminanceWeight = vec3(0.2126, 0.7152, 0.0722);

float luminance(vec3 color) {
    return dot(color, luminanceWeight);
}

// Improved box blur with edge-aware weighting
vec3 boxBlur(sampler2D tex, vec2 uv, vec2 texelSize, int radius) {
    vec3 blurred = vec3(0.0);
    float weightSum = 0.0;
    float r = float(radius);
    vec3 centerColor = texture(tex, uv).rgb;
    
    for (int x = -radius; x <= radius; ++x) {
        for (int y = -radius; y <= radius; ++y) {
            vec2 offset = vec2(x, y) * texelSize;
            vec3 sampleColor = texture(tex, uv + offset).rgb;
            
            // Edge-aware weight: lower weight for pixels that are very different
            float colorDist = distance(centerColor, sampleColor);
            float edgeWeight = exp(-colorDist * 10.0);
            
            // Spatial weight: Gaussian
            float spatialWeight = exp(-(float(x*x + y*y)) / (2.0 * r*r));
            
            float weight = spatialWeight * edgeWeight;
            blurred += sampleColor * weight;
            weightSum += weight;
        }
    }
    return blurred / max(weightSum, 0.0001);
}

// Calculates dark channel (minimum of RGB channels) for a local region
float getDarkChannel(sampler2D tex, vec2 uv, vec2 texelSize, int radius) {
    float darkChannel = 1.0;
    
    for (int x = -radius; x <= radius; ++x) {
        for (int y = -radius; y <= radius; ++y) {
            vec2 offset = vec2(x, y) * texelSize;
            vec3 sampleColor = texture(tex, uv + offset).rgb;
            float minChannel = min(min(sampleColor.r, sampleColor.g), sampleColor.b);
            darkChannel = min(darkChannel, minChannel);
        }
    }
    
    return darkChannel;
}

// Estimates atmospheric light (typically the brightest pixel in hazy regions)
vec3 estimateAtmosphericLight(sampler2D tex, vec2 uv, vec2 texelSize) {
    // Default slightly bluish-gray haze color, similar to most atmospheric conditions
    vec3 defaultAtmosphericLight = vec3(0.85, 0.9, 1.0);
    
    // Find brightest area in a larger region 
    vec3 brightest = vec3(0.0);
    float maxLum = 0.0;
    
    int searchRadius = 10;
    for (int x = -searchRadius; x <= searchRadius; x += 2) {
        for (int y = -searchRadius; y <= searchRadius; y += 2) {
            vec2 offset = vec2(x, y) * texelSize;
            vec3 sampleColor = texture(tex, uv + offset).rgb;
            float lum = luminance(sampleColor);
            
            if (lum > maxLum) {
                maxLum = lum;
                brightest = sampleColor;
            }
        }
    }
    
    // Blend between default and detected atmospheric light
    return mix(defaultAtmosphericLight, brightest, 0.7);
}

// Tone protection function to preserve highlights and shadows
vec3 protectTones(vec3 color, vec3 enhanced, float amount) {
    float lum = luminance(color);
    
    // Highlight and shadow protection factors
    float highlightProtection = 1.0 - smoothstep(0.75, 0.98, lum);
    float shadowProtection = smoothstep(0.0, 0.2, lum);
    
    float protection = mix(1.0, highlightProtection * shadowProtection, min(abs(amount) * 2.0, 1.0));
    
    return mix(color, enhanced, protection);
}

// Apply advanced dehazing algorithm
vec3 applyDehaze(vec3 originalColor, vec2 uv, float dehaze) {
    // Convert dehaze from -100...100 to -1...1
    float amount = dehaze / 100.0;
    
    if (abs(amount) < 0.01) return originalColor;
    
    vec2 texelSize = 1.0 / textureSize(InputTexture, 0);
    
    // --- Atmospheric Scattering Model: I = J × t + A × (1-t) ---
    // I is the observed hazy image, J is the dehazed image, 
    // A is atmospheric light, t is transmission
    
    // Calculate dark channel (key to estimating haze)
    int darkChannelRadius = 3;
    float darkChannel = getDarkChannel(InputTexture, uv, texelSize, darkChannelRadius);
    
    // Estimate atmospheric light
    vec3 atmosphericLight = estimateAtmosphericLight(InputTexture, uv, texelSize);
    
    // Calculate transmission map using dark channel prior
    float omega = 0.95; // Preserves a small amount of haze for realism
    float transmission = 1.0 - omega * darkChannel;
    
    vec3 result;
    if (amount > 0.0) {
        // Remove haze (positive dehaze)
        float minTransmission = 0.1;
        float adjustedTransmission = max(transmission, minTransmission);
        adjustedTransmission = mix(1.0, adjustedTransmission, amount);
        
        // Apply dehaze formula: J = (I - A) / t + A
        result = (originalColor - atmosphericLight) / adjustedTransmission + atmosphericLight;
        
        // Additional local contrast enhancement
        vec3 localAverage = boxBlur(InputTexture, uv, texelSize, 3);
        vec3 localDetail = originalColor - localAverage;
        result = result + localDetail * (amount * 0.5);
        
        // Saturation boost (typical of dehaze)
        float satBoost = 1.0 + amount * 0.3;
        float resultLum = luminance(result);
        result = mix(vec3(resultLum), result, satBoost);
    } 
    else {
        // Add haze (negative dehaze)
        float hazeAmount = -amount;
        
        // Blend with atmospheric light
        result = mix(originalColor, atmosphericLight, hazeAmount * 0.5);
        
        // Reduce contrast to simulate haze
        vec3 localAverage = boxBlur(InputTexture, uv, texelSize, 5);
        result = mix(result, localAverage, hazeAmount * 0.3);
        
        // Reduce saturation
        float resultLum = luminance(result);
        result = mix(result, vec3(resultLum), hazeAmount * 0.4);
    }
    
    // Protect highlights and shadows
    result = protectTones(originalColor, result, amount);
    
    // Non-linear response curve similar to Lightroom
    float blendFactor = 1.0 / (1.0 + exp(-abs(amount) * 3.0));
    result = mix(originalColor, result, blendFactor);
    
    return result;
}

void main() {
    vec4 color = texture(InputTexture, TexCoord);
    color.rgb = applyDehaze(color.rgb, TexCoord, dehazeValue);
    FragColor = vec4(max(color.rgb, vec3(0.0)), color.a);
}