precision highp float;

varying vec2 vUv;

uniform sampler2D inputTex;
uniform sampler2D adjustmentCurveTex;


// Line data
uniform vec2 lineStart;
uniform vec2 lineEnd;

vec2 uv; // original uv
float illuminance;
float lineProjLength; // length of projection on line
vec2 lineVector; // original uv - nearest uv on line

const float CIE_E = 216.0 / 24389.0;
const vec3 LAB_REF_WHITE = vec3(95.047, 100, 108.883) / 100.0;

vec3 rgb2xyz(vec3 c) {
    vec3 tmp;
    tmp.x = ( c.r > 0.04045 ) ? pow( ( c.r + 0.055 ) / 1.055, 2.4 ) : c.r / 12.92;
    tmp.y = ( c.g > 0.04045 ) ? pow( ( c.g + 0.055 ) / 1.055, 2.4 ) : c.g / 12.92,
    tmp.z = ( c.b > 0.04045 ) ? pow( ( c.b + 0.055 ) / 1.055, 2.4 ) : c.b / 12.92;
    return tmp * mat3(
        0.4124, 0.3576, 0.1805,
        0.2126, 0.7152, 0.0722,
        0.0193, 0.1192, 0.9505
    );
}

vec3 xyz2lab(vec3 c) {
    vec3 n = c / LAB_REF_WHITE;
    vec3 v;
    v.x = ( n.x > CIE_E ) ? pow( n.x, 1.0 / 3.0 ) : ( 7.787 * n.x ) + ( 16.0 / 116.0 );
    v.y = ( n.y > CIE_E ) ? pow( n.y, 1.0 / 3.0 ) : ( 7.787 * n.y ) + ( 16.0 / 116.0 );
    v.z = ( n.z > CIE_E ) ? pow( n.z, 1.0 / 3.0 ) : ( 7.787 * n.z ) + ( 16.0 / 116.0 );
    return vec3(
        116.0 * v.y - 16.0,
        500.0 * ( v.x - v.y ),
        200.0 * ( v.y - v.z )
    );
}

vec3 rgb2lab(vec3 c) {
    vec3 lab = xyz2lab( rgb2xyz( c ) );
    return vec3( lab.x / 100.0, 0.5 + 0.5 * ( lab.y / 127.0 ), 0.5 + 0.5 * ( lab.z / 127.0 ));
}

vec3 lab2xyz(vec3 c) {
    float fy = ( c.x + 16.0 ) / 116.0;
    float fx = c.y / 500.0 + fy;
    float fz = fy - c.z / 200.0;

    float fx3 = pow(fx, 3.0);
    float fy3 = pow(fy, 3.0);
    float fz3 = pow(fz, 3.0);

    return LAB_REF_WHITE * vec3(
        ( fx3 > CIE_E ) ? fx3 : ( fx - 16.0 / 116.0 ) / 7.787,
        ( fy3 > CIE_E ) ? fy3 : ( fy - 16.0 / 116.0 ) / 7.787,
        ( fz3 > CIE_E ) ? fz3 : ( fz - 16.0 / 116.0 ) / 7.787
    );
}

vec3 xyz2rgb(vec3 c) {
    vec3 v =  c * mat3(
        3.2406, -1.5372, -0.4986,
        -0.9689, 1.8758, 0.0415,
        0.0557, -0.2040, 1.0570
    );
    vec3 r;
    r.x = ( v.r > 0.0031308 ) ? (( 1.055 * pow( v.r, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.r;
    r.y = ( v.g > 0.0031308 ) ? (( 1.055 * pow( v.g, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.g;
    r.z = ( v.b > 0.0031308 ) ? (( 1.055 * pow( v.b, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.b;
    return r;
}

vec3 lab2rgb(vec3 c) {
    return xyz2rgb( lab2xyz( vec3(100.0 * c.x, 2.0 * 127.0 * (c.y - 0.5), 2.0 * 127.0 * (c.z - 0.5)) ) );
}

void calcLineTemp() {
    vec2 a = lineEnd - lineStart;
    vec2 b = uv - lineStart;

    // compute b's projection on a
    vec2 proj = clamp(dot(b, a) / dot(a, a), 0.0, 1.0) * a;

    vec2 uvOnLine = proj + lineStart;
    lineProjLength = proj.x / a.x;
    lineVector = uv - uvOnLine;
}

float getShift() {
    float adjuetmentAverage = illuminance;
    vec2 coor = vec2(adjuetmentAverage, 0.0);
    float splineVal = texture2D(adjustmentCurveTex, coor).x;
    return splineVal;
}

vec2 shiftLine(float shift) {
    float t = lineProjLength + shift;
    t = clamp(t, 0.0, 1.0);
    vec2 uvInterpolated = lineStart * (1.0 - t) + lineEnd * t;
    vec2 uvNew = uvInterpolated + lineVector;
    return uvNew;
}

vec3 projectToGamut(vec3 rgb, float illuminance){
    vec3 labfixed = rgb2lab(rgb);
    labfixed.x = illuminance;
    return clamp(lab2rgb(labfixed), vec3(0.0,0.0,0.0), vec3(1.0,1.0,1.0));
}

void main() {
    vec4 color = texture2D(inputTex, vUv);

    // Clamp to avoid artifact in uv calculation
    color = clamp(color, vec4(0.0), vec4(1.0));

    // Check if the spline is reset, i.e. no adjustment needed
    if (texture2D(adjustmentCurveTex, vUv).y < -10.0 ) {
        gl_FragColor = color;
        return;
    }

    float alpha = color.a;
    vec3 lab = rgb2lab(color.rgb);
    illuminance = lab.x;
    uv = lab.yz;

    calcLineTemp();
    float shiftDist = getShift();
    vec2 uvNewLine = shiftLine(shiftDist);

    vec3 rgb = clamp(lab2rgb(vec3(illuminance, uvNewLine)), vec3(0.0), vec3(1.0));
    for(int i=0;i<25;i++){
        rgb = projectToGamut(rgb, illuminance);
    }

    gl_FragColor = vec4(rgb, alpha);
}
