/*
# License Terms for Academy Color Encoding System Components #

Academy Color Encoding System (ACES) software and tools are provided by the
Academy under the following terms and conditions: A worldwide, royalty-free,
non-exclusive right to copy, modify, create derivatives, and use, in source and
binary forms, is hereby granted, subject to acceptance of this license.

Copyright © 2015 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.).
Portions contributed by others as indicated. All rights reserved.

Performance of any of the aforementioned acts indicates acceptance to be bound
by the following terms and conditions:

* Copies of source code, in whole or in part, must retain the above copyright
notice, this list of conditions and the Disclaimer of Warranty.

* Use in binary form must retain the above copyright notice, this list of
conditions and the Disclaimer of Warranty in the documentation and/or other
materials provided with the distribution.

* Nothing in this license shall be deemed to grant any rights to trademarks,
copyrights, patents, trade secrets or any other intellectual property of
A.M.P.A.S. or any contributors, except as expressly stated herein.

* Neither the name "A.M.P.A.S." nor the name of any other contributors to this
software may be used to endorse or promote products derivative of or based on
this software without express prior written permission of A.M.P.A.S. or the
contributors, as appropriate.

This license shall be construed pursuant to the laws of the State of
California, and any disputes related thereto shall be subject to the
jurisdiction of the courts therein.

Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY
CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY
DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR
OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR
APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR
UNDISCLOSED.
*/

float sigmoid_shaper(float x) { // Sigmoid function in the range 0 to 1 spanning -2 to +2.
	float t = max(1.0 - abs(0.5 * x), 0.0);
	float y = 1.0 + sign(x) * (1.0 - t * t);
	
	return 0.5 * y;
}

float cubic_basis_shaper(float x, float w) {

    const vec4 M[4] = vec4[4](
        vec4( -1.0 / 6.0,  3.0 / 6.0, -3.0 / 6.0,  1.0 / 6.0),
        vec4(  3.0 / 6.0, -6.0 / 6.0,  3.0 / 6.0,  0.0 / 6.0), 
        vec4( -3.0 / 6.0,  0.0 / 6.0,  3.0 / 6.0,  0.0 / 6.0), 
        vec4(  1.0 / 6.0,  4.0 / 6.0,  1.0 / 6.0,  0.0 / 6.0)
    );

    float knot[5] = float[5](
        -0.5 * w,
        -0.25 * w,
        0.0, 
        0.25 * w,
        0.5 * w
    );

    float y     = 0.0;

    if ((x > knot[0]) && (x < knot[4])) {
        float knot_coord    = (x - knot[0]) * 4.0 / w;

        int j   = int(knot_coord);
        float t = knot_coord - j;

        float monomials[4]  = float[4](
            t*t*t,
            t*t,
            t,
            1.0
        );

        /* this section can be simplified */
        if (j == 3) {
            y   = monomials[0] * M[0].x + monomials[1] * M[1].x
                + monomials[2] * M[2].x + monomials[3] * M[3].x;
        } else if (j == 2) {
            y   = monomials[0] * M[0].y + monomials[1] * M[1].y
                + monomials[2] * M[2].y + monomials[3] * M[3].y;
        } else if (j == 1) {
            y   = monomials[0] * M[0].z + monomials[1] * M[1].z
                + monomials[2] * M[2].z + monomials[3] * M[3].z;
        } else if (j == 0) {
            y   = monomials[0] * M[0].w + monomials[1] * M[1].w
                + monomials[2] * M[2].w + monomials[3] * M[3].w;
        } else {
            y   = 0.0;
        }
    }

    return y * 1.5;
}

struct SegmentedSplineParams_c5 {
	float coefsLow[6];    // coefs for B-spline between minPoint and midPoint (units of log luminance)
	float coefsHigh[6];   // coefs for B-spline between midPoint and maxPoint (units of log luminance)
	vec2 minPoint; // {luminance, luminance} linear extension below this
	vec2 midPoint; // {luminance, luminance} 
	vec2 maxPoint; // {luminance, luminance} linear extension above this
	float slopeLow;       // log-log slope of low linear extension
	float slopeHigh;      // log-log slope of high linear extension
};

const mat3 M = mat3(
	 0.5, -1.0,  0.5,
	-1.0,  1.0,  0.5,
	 0.5,  0.0,  0.0
);

float segmented_spline_c5_fwd(float x) {
	const SegmentedSplineParams_c5 C = SegmentedSplineParams_c5(
		float[6] ( -4.0000000000, -4.0000000000, -3.1573765773, -0.4852499958, 1.8477324706, 1.8477324706 ),
		float[6] ( -0.7185482425, 2.0810307172, 3.6681241237, 4.0000000000, 4.0000000000, 4.0000000000 ),
		vec2(0.18*exp2(-15.0), 0.0001),
		vec2(0.18,              4.8),
		vec2(0.18*exp2(18.0),  10000.),
		0.0,
		0.0
	);

	const int N_KNOTS_LOW = 4;
	const int N_KNOTS_HIGH = 4;

	// Check for negatives or zero before taking the log. If negative or zero,
	// set to ACESMIN.1
	float xCheck = x <= 0 ? exp2(-14.0) : x;

	float logx = log10( xCheck);
	float logy;

	if (logx <= log10(C.minPoint.x)) {
		logy = logx * C.slopeLow + (log10(C.minPoint.y) - C.slopeLow * log10(C.minPoint.x));
	} else if ((logx > log10(C.minPoint.x)) && (logx < log10(C.midPoint.x))) {
		float knot_coord = (N_KNOTS_LOW-1) * (logx-log10(C.minPoint.x))/(log10(C.midPoint.x)-log10(C.minPoint.x));
		int j = int(knot_coord);
		float t = knot_coord - float(j);

		vec3 cf = vec3( C.coefsLow[ j], C.coefsLow[ j + 1], C.coefsLow[ j + 2]);
    
		vec3 monomials = vec3(t * t, t, 1.0);
		logy = dot( monomials, M * cf);
	} else if ((logx >= log10(C.midPoint.x)) && (logx < log10(C.maxPoint.x))) {
		float knot_coord = (N_KNOTS_HIGH - 1) * (logx - log10(C.midPoint.x)) / (log10(C.maxPoint.x) - log10(C.midPoint.x));
		int j = int(knot_coord);
		float t = knot_coord - float(j);

		vec3 cf = vec3(C.coefsHigh[j], C.coefsHigh[j + 1], C.coefsHigh[j + 2]);
		vec3 monomials = vec3(t * t, t, 1.0);
		
		logy = dot(monomials, M * cf);
	} else {
		logy = logx * C.slopeHigh + (log10(C.maxPoint.y) - C.slopeHigh * log10(C.maxPoint.x));
	}

	return pow(10.0, logy);
}


struct SegmentedSplineParams_c9 {
    float coeff_low[10];
    float coeff_high[10];
    vec2 min_point;
    vec2 mid_point;
    vec2 max_point;
    float slope_low;
    float slope_high;
};

float segmented_spline_c9_fwd(float x, const SegmentedSplineParams_c9 c) {
    const int N_KNOTS_LOW   = 8;
    const int N_KNOTS_HIGH  = 8;

    float x_check = x <= 0.0 ? 1e-4 : x;

    float logx  = log10(x_check);
    float logy;

    if (logx <= log10(c.min_point.x)) {
        logy    = logx * c.slope_low + (log10(c.min_point.y) - c.slope_low * log10(c.min_point.x));
    } else if ((logx > log10(c.min_point.x)) && (logx < log10(c.mid_point.x))) {
        float knot_coord = (N_KNOTS_LOW - 1) * (logx - log10(c.min_point.x)) / (log10(c.mid_point.x) - log10(c.min_point.x));
        int j   = int(knot_coord);
        float t = knot_coord - j;

        vec3 cf = vec3(c.coeff_low[j], c.coeff_low[j+1], c.coeff_low[j+2]);

        vec3 monomials = vec3(t*t, t, 1.0);

        logy    = dot(monomials, M * cf);
    } else if ((logx >= log10(c.mid_point.x)) && (logx < log10(c.max_point.x))) {
        float knot_coord = (N_KNOTS_HIGH - 1) * (logx - log10(c.mid_point.x)) / (log10(c.max_point.x) - log10(c.mid_point.x));
        int j   = int(knot_coord);
        float t = knot_coord - j;

        vec3 cf = vec3(c.coeff_high[j], c.coeff_high[j+1], c.coeff_high[j+2]);

        vec3 monomials = vec3(t*t, t, 1.0);

        logy    = dot(monomials, M * cf);
    } else {
        logy    = logx * c.slope_high + (log10(c.max_point.y) - c.slope_high * log10(c.max_point.x));
    }
    return pow(10.0, logy);
}