// Luma coefficients according to ITU-R Recommendation BT.709 (http://en.wikipedia.org/wiki/Rec._709)
const vec3 qt_yCoeff_709 = vec3(0.2126, 0.7152, 0.0722);

float qt_luminance(in vec3 v )
{
    return dot(v, qt_yCoeff_709);
}

vec3 RGBToYPbPr( in vec3 v )
{
    vec3 ypp;
    ypp.x = qt_luminance( v );
    ypp.y = 0.5 * (v.b - ypp.x) / (1.0 - qt_yCoeff_709.b);
    ypp.z = 0.5 * (v.r - ypp.x) / (1.0 - qt_yCoeff_709.r);

    return ypp;
}

vec3 YPbPrToRGB( in vec3 v )
{
    vec3 outRGB;
    outRGB.x = dot(vec3(1.0, 0.0, 1.575), v);
    outRGB.y = dot(vec3(1.0, -0.187, -0.468), v);
    outRGB.z = dot(vec3(1.0, 1.856, 0.0), v);

    return outRGB;
}

float remapLum( float inLum, float whitePt )
{
    return clamp( inLum / whitePt, 0.0, 1.0 );
}

float exposeLum( float inLum, float exposure )
{
    return 1.0 - exp2( -inLum / exposure );
}

vec3 gammaCorrect( vec3 inColor, float gammaExp )
{
    return pow( inColor, vec3( 1.0 / gammaExp ) );
}

vec3 adjSaturation( vec3 inRGB, float satFactor )
{
    // Must be done in linear space (before gamma correction)
    float P = sqrt( qt_luminance( inRGB * inRGB ) );
    vec3 outCol;
    outCol = (inRGB - vec3(P)) * satFactor;
    outCol += vec3(P);
    return outCol;
}

float curveCompute( float inLum, float slope0, float slope1 )
{
    float a1 = slope0;
    float a2 = 3.0 - 2.0 * slope0 - slope1;
    float a3 = slope1 + slope0 - 2.0;

    // Cubic curve fit.  This results in a curve that is 0 where inColor is 0
    // equals 1 when inColor is 1, and the derivative at 0 is slope0 and the
    // derivative at 1 is slope1
    return ((((a3 * inLum) + a2)*inLum) + a1)*inLum;
}

float toeEmphasize( float inParam )
{
    float a1 = 1.0 - toeEmphasis;
    float a2 = 2.0 * toeEmphasis;
    float a3 = -toeEmphasis;

    return ((((a3 * inParam) + a2) * inParam) + a1) * inParam;
}

float shoulderEmphasize( float inParam )
{
    float a1 = 1.0;
    float a2 = shoulderEmphasis;
    float a3 = -shoulderEmphasis;

    return ((((a3 * inParam) + a2) * inParam) + a1) * inParam;
}

void MAIN()
{
    // k = shadow slope, m = midtone slope, n = highlight slope
    float k = toeSlope;
    float m = 1.0 + contrastBoost;
    float n = shoulderSlope;

    vec4 sourceColor = texture(INPUT, INPUT_UV);
    vec3 sourceSep = RGBToYPbPr(sourceColor.rgb);

    float lum = sourceSep.r;

    if (useExposure)
        lum = exposeLum( lum, exposureValue );
    else
        lum = remapLum( lum, whitePoint );

    float param0 = toeEmphasize( 2.0 * lum );               // Parametrization for Curve Part 1
    float param1 = shoulderEmphasize( 2.0 * lum - 1.0 );    // Parametrization for Curve Part 2

    float lum0 = 0.5 * curveCompute( param0, k, m );
    float lum1 = 0.5 * curveCompute( param1, m, n ) + 0.5;
    sourceSep.r = (lum > 0.5) ? lum1 : lum0;

    // Convert back to RGB. Clamp negative values that arise when luma is
    // compressed heavily while chroma stays fixed, pushing colours outside
    // the RGB cube. Negative values here would cause undefined behaviour in
    // pow() inside gammaCorrect, with different GPU drivers returning
    // different results.
    vec3 finalColor = max(vec3(0.0), YPbPrToRGB( sourceSep ));
    finalColor = gammaCorrect( adjSaturation( finalColor, saturationLevel ), gammaValue );
    FRAGCOLOR = vec4( finalColor, sourceColor.a );
}
