#version 330 core

out vec4 FragColor;
in vec2 TexCoord;

uniform sampler2D InputTexture;
uniform float temperature; // Target Correlated Color Temperature (Kelvin, e.g., 1000-20000)
uniform float tint;        // Green-Magenta shift (-100 to +100)

// --- Constants ---

// sRGB Primaries to XYZ (D65)
const mat3 M_RGB_2_XYZ_D65 = mat3(
    0.4124564, 0.3575761, 0.1804375,
    0.2126729, 0.7151522, 0.0721750,
    0.0193339, 0.1191920, 0.9503041
);

// XYZ (D65) to sRGB Primaries
const mat3 M_XYZ_2_RGB_D65 = mat3(
     3.2404542, -1.5371385, -0.4985314,
    -0.9692660,  1.8760108,  0.0415560,
     0.0556434, -0.2040259,  1.0572252
);

// Standard Illuminant D65 XYZ (Normalized Y=1) - Our Target White
const vec3 WHITEPOINT_D65 = vec3(0.95047, 1.00000, 1.08883);

// Bradford CAT Matrices
const mat3 M_BRADFORD = mat3(
    0.8951,  0.2664, -0.1614,
   -0.7502,  1.7135,  0.0367,
    0.0389, -0.0685,  1.0296
);
const mat3 M_BRADFORD_INV = mat3(
    0.9869929, -0.1470543, 0.1599627,
    0.4323053,  0.5183603, 0.0492912,
   -0.0085287,  0.0400428, 0.9684867
);

// --- Helper Functions ---

// Approximate Kelvin Temperature to XYZ Chromaticity (xy) -> XYZ coordinates
// Uses simplified polynomial fits for different temperature ranges.
// Note: More accurate methods often use look-up tables or spectral calculations.
vec3 kelvinToXYZ(float kelvin) {
    float temp = clamp(kelvin, 1000.0, 20000.0);
    float x, y;

    // Calculate xy chromaticity coordinates based on temperature
    // Formulas from: http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html (with slight adaptations)
    float t = temp;
    float t2 = t * t;
    float t3 = t2 * t;

    // Calculate x coordinate
    if (t >= 1000.0 && t <= 4000.0) {
        x = -0.2661239e9 / t3 - 0.2343589e6 / t2 + 0.8776956e3 / t + 0.179910;
    } else { // t > 4000.0 && t <= 25000.0
        x = -3.0258469e9 / t3 + 2.1070379e6 / t2 + 0.2226347e3 / t + 0.240390;
    }

    // Calculate y coordinate based on x
    float x2 = x * x;
    float x3 = x2 * x;
    if (t >= 1000.0 && t <= 2222.0) {
         y = -1.1063814 * x3 - 1.34811020 * x2 + 2.18555832 * x - 0.18709;
    } else if (t > 2222.0 && t <= 4000.0) {
         y = -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867;
    } else { // t > 4000.0 && t <= 25000.0
         y = 3.0817580 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;
    }

    // Convert xyY (Y=1) to XYZ
    if (y < 1e-6) return vec3(0.0); // Avoid division by zero
    float Y = 1.0;
    float X = (x / y) * Y;
    float Z = ((1.0 - x - y) / y) * Y;

    return vec3(X, Y, Z);
}

// Apply Bradford Chromatic Adaptation Transform
vec3 adaptXYZ(vec3 xyzColor, vec3 sourceWhiteXYZ, vec3 destWhiteXYZ) {
    vec3 sourceCone = M_BRADFORD * sourceWhiteXYZ;
    vec3 destCone = M_BRADFORD * destWhiteXYZ;

    // Avoid division by zero if source cone response is zero
    // (shouldn't happen with typical white points but good practice)
    if (sourceCone.r < 1e-6 || sourceCone.g < 1e-6 || sourceCone.b < 1e-6) {
        return xyzColor; // Return original color if adaptation is impossible
    }

    vec3 ratio = destCone / sourceCone;

    mat3 adaptationMatrix = M_BRADFORD_INV * mat3(ratio.x, 0, 0, 0, ratio.y, 0, 0, 0, ratio.z) * M_BRADFORD;

    return adaptationMatrix * xyzColor;
}

// --- Main White Balance Function ---
vec3 applyWhiteBalance(vec3 linearSRGBColor, float tempK, float tintVal) {

    // 1. Convert input Linear sRGB (D65) to XYZ D65
    vec3 inputXYZ = M_RGB_2_XYZ_D65 * linearSRGBColor;

    // 2. Calculate the XYZ white point of the source illuminant (from Kelvin temp)
    // This is the white we want to adapt *FROM*
    vec3 sourceWhiteXYZ = kelvinToXYZ(tempK);

    // 3. Apply Bradford CAT to adapt from sourceWhiteXYZ to D65
    vec3 adaptedXYZ = adaptXYZ(inputXYZ, sourceWhiteXYZ, WHITEPOINT_D65);

    // 4. Convert adapted XYZ (now relative to D65) back to Linear sRGB
    vec3 adaptedLinearSRGB = M_XYZ_2_RGB_D65 * adaptedXYZ;

    // 5. Apply Tint adjustment (Post-CAT approximation)
    // This shifts color balance along a Green<->Magenta axis.
    // We scale RGB components slightly based on the tint value.
    // A common method is to affect Green opposite to Red/Blue.
    if (abs(tintVal) > 0.01) {
        // Map tint -100..100 to a scaling factor, e.g., -0.1..0.1
        float tintFactor = tintVal / 1000.0; // Smaller scale factor for subtle tint

        // Apply scaling: Increase G for negative tint, Decrease G for positive tint
        // Compensate R and B slightly in the opposite direction.
        // Coefficients below are heuristic and may need tuning for perceptual feel.
        float rScale = 1.0 + tintFactor * 0.5;
        float gScale = 1.0 - tintFactor * 1.0;
        float bScale = 1.0 + tintFactor * 0.5;

        vec3 tintScaleVec = vec3(rScale, gScale, bScale);

        // Optional: Normalize scale vector to preserve luminance (roughly)
        // Calculate luminance of the scale vector itself
        float scaleLum = dot(tintScaleVec, vec3(0.2126, 0.7152, 0.0722));
        if (scaleLum > 1e-5) {
           tintScaleVec /= scaleLum; // Normalize
        }

        adaptedLinearSRGB *= tintScaleVec;
    }

    return adaptedLinearSRGB;
}

void main() {
    vec4 texColor = texture(InputTexture, TexCoord);
    vec3 linearInputColor = texColor.rgb; // Assuming input texture is already linear sRGB

    // Calculate white balanced color
    vec3 whiteBalancedColor = applyWhiteBalance(linearInputColor, temperature, tint);

    // Ensure output is non-negative
    whiteBalancedColor = max(whiteBalancedColor, vec3(0.0));

    FragColor = vec4(whiteBalancedColor, texColor.a);
}