#ifndef _UTILITIES_FXH_
#define _UTILITIES_FXH_

//******************************************************************
//  color conversions
//******************************************************************
 
float3 RGB_to_HSV(float3 color)
{
    float3 HSV   = 0.0;
    float  M     = max(color.r, max(color.g, color.b));
    float  C     = M - min(color.r, min(color.g, color.b));
           HSV.z = M;
    if (C != 0.0) {
        float3 Delta  = (color.brg - color.rgb) / C + float3(2.0, 4.0, 6.0);
               Delta *= step(M, color.gbr); //if max = rgb
        HSV.x = frac(max(Delta.r, max(Delta.g, Delta.b)) / 6.0);
        HSV.y = C / M;
    }
    return HSV;
}

float3 RGB_to_HSL(float3 color)
{
    float3 HSL   = 0.0;
    float  M     = max(color.r, max(color.g, color.b));
    float  C     = M - min(color.r, min(color.g, color.b));
           HSL.z = M - 0.5 * C;
    if (C != 0.0) {
        float3 Delta = (color.brg - color.rgb) / C + float3(2.0, 4.0, 6.0);
               Delta*= step(M, color.gbr); //if max = rgb
        HSL.x = frac(max(Delta.r, max(Delta.g, Delta.b)) / 6.0);
        HSL.y = (HSL.z == 1.0)? 0.0: C/ (1.0 - abs( 2.0 * HSL.z - 1.0));
    }
    return HSL;
}

float3 Hue_to_RGB( float h) {
    return saturate(float3( abs(h * 6.0f - 3.0f) - 1.0f, 
                            2.0f - abs(h * 6.0f - 2.0f),
                            2.0f - abs(h * 6.0f - 4.0f)));
}

float3 HSL_to_RGB( float3 HSL ) {
    return (Hue_to_RGB(HSL.x) - 0.5) * (1.0 - abs(2.0 * HSL.z - 1)) * HSL.y + HSL.z;
}

float3 HSV_to_RGB(float3 HSV) {
    return HSV.z * (HSV.y * Hue_to_RGB(HSV.x) + 1.0 - HSV.y);
}

//******************************************************************
//  curve conversions
//******************************************************************
// ( 300 * log10( LinearColor * (1 - .0108) + .0108 ) + 685 ) / 1023
// ( pow( 10, ( 1023 * LogColor - 685 ) / 300) - .0108 ) / (1 - .0108);

//https://github.com/mymei/UE4/blob/d049f041989d62f6d6d4840d58e084a52f0558a9/Engine/Shaders/PostProcessTonemap.usf
//cineon [0.0108, 13.51] -> [0, 1]
float3 Lin2Log(float3 Lin) {
    return saturate((300.0 * log10( Lin * (1.0 - 0.0108) + 0.0108 ) + 685.0) / 1023.0);
}

//cineon [0, 1] -> [0.0108, 13.51]
float3 Log2Lin(float3 Log) {
    return max((pow( 10.0, ( 1023.0 * Log - 685.0 ) / 300.0) - 0.0108 ) / (1.0 - 0.0108), 0.0);
}

//******************************************************************
//  TODIE/DNIE conversions
//******************************************************************

 
//******************************************************************
//  Misc
//******************************************************************
 
float LoC( float L0, float L1, float angle)
{
    return sqrt(L0*L0+L1*L1-2.0*L0*L1*cos(angle));
}

float2 Rotate(float2 uv, float rad)
{
    float2x2 rot;
    sincos(rad, rot._m00, rot._m10);
    rot._m11 = rot._m00;
    rot._m01 =-rot._m10;
    return mul(rot, uv);
}

//no inverse func for this
float2 Distort( float2 coord, float curve, float scale)
{
    float  r = length(coord);
    return coord + pow( 2.0*r, curve) * scale * coord/r;
}

//monotonic Cubic hermite interpolation (Fritsch-Carlson)
//      https://math.stackexchange.com/questions/4082/equation-of-a-curve-given-3-points-and-additional-constant-requirement
//      https://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
// u == [0, 1]
float CubicLerp(float2 c0, float2 p0, float2 p1, float2 c1, float u)
{
    float ma = (p0.y - c0.y) / (p0.x - c0.x); //d(i-1)
    float mb = (p1.y - p0.y) / (p1.x - p0.x); //d(i)
    float mc = (c1.y - p1.y) / (c1.x - p1.x); //d(i+1)

    //Fritsch-Carlson
    float d0 = p1.x - c0.x;
    float d1 = c1.x - p0.x;

    float m0 = (ma*mb > 0.0) ? 3.0 * d0 / ((d0 + (p1.x - p0.x)) / ma + (d0 + (p0.x - c0.x)) / mb) : 0.0;
    float m1 = (mb*mc > 0.0) ? 3.0 * d1 / ((d1 + (c1.x - p1.x)) / mb + (d1 + (p1.x - p0.x)) / mc) : 0.0;

    m0 = p0.x == 0.0 ? m1 : m0;
    m1 = p1.x == 1.0 ? m0 : m1;
    
    //cubic Hermite interpolant
    float h = p1.x - p0.x;
    float b = m0 * h;
    float c = (3.0 * mb - 2.0 * m0 - m1) * h;
    float d = (m0 + m1 - 2.0 * mb) * h;
    
    return p0.y + b*u + c*u*u + d*u*u*u;
}

//only handles if D != 0 && A != 0
float3 CubicSolve( float A, float B, float C, float D, out float3 Im)
{
    if (A == 0 || D == 0) return 0.0;
    float3 Re;
    
    B /= A;
    C /= A;
    D /= A;    
   
    float q = (3.0*C - (B*B))/9.0;
    float r = (-(27.0*D) + B*(9.0*C - 2.0*(B*B))) / 54.0;
    float d = q*q*q + r*r;
    float t0 = (B/3.0);
    float s, t, r13;
    
    Im = 0.0; //The first root is always real.
    
    if (d > 0) { // one root real, two are complex
        s = r + sqrt(d);
        s = sign(s) * pow(abs(s), 1.0/3.0);
        t = r - sqrt(d);
        t = sign(t) * pow(abs(t), 1.0/3.0);
        Re.x  = -t0 + s + t;
        Re.yz = -t0 - (s + t)/2.0;
        Im.y = sqrt(3.0)*(-t + s)/2.0;
        Im.z = -Im.y;
        return Re;
    } // End if (d > 0)
    
    // The remaining options are all real
    if (d == 0){ // All roots real, at least two are equal.
        r13 = sign(r) * pow(abs(r), 1.0/3.0);
        Re.x  = -t0 + 2.0*r13;
        Re.yz = -(r13 + t0);
        return Re;
    } // End if (d == 0)
    
    // Only option left is that all roots are real and unequal (to get here, q < 0)
    q = abs(q);
    r13 = 2.0 * sqrt(q);
    q = acos(r/sqrt(q*q*q));
    
    Re.x = -t0 + r13*cos( q             /3.0);
    Re.y = -t0 + r13*cos((q + 2.0*XM_PI)/3.0);
    Re.z = -t0 + r13*cos((q + 4.0*XM_PI)/3.0);
    return Re;
}

//******************************************************************
//  Blending
//******************************************************************

//blending operation macros
//a over b
//***only works in [0, 1] range
//http://stackoverflow.com/questions/5919663/how-does-photoshop-blend-two-images-together
//WIP
#define Blend_Subtract(a, b)    max(a + b - 1, 0)
#define Blend_Difference(a, b)  abs(a - b)
#define Blend_Negation(a, b)    (1 - abs(1 - a - b))
#define Blend_Screen(a, b)      (1 - (1 - a) * (1 - b))
#define Blend_Exclusion(a, b)   (a + b - 2 * a * b)
#define Blend_Overlay(a, b)     ((b < 0.5)? 2 * a * b: 1 - 2 * (1 - a) * (1 - b))
#define Blend_SoftLight(a, b)   ((b < 0.5)? 2 * (a * 0.5 + 0.25) * b: 1 - 2 * (0.75 - a * 0.5) * ( 1 - b))   //not consistant to wiki blend_mode
#define Blend_HardLight(a, b)   Blend_Overlay(b, a)
#define Blend_ColorDodge(a, b)
#define Blend_Lighten(a, b)     max(a, b)
#define Blend_Darken(a, b)      min(a, b)
#define Blend_Multiply(a, b)    (a * b)
#define Blend_Average(a, b)     ((a + b) * 0.5)
#define Blend_Add(a, b)         (a + b)
//note: add Dissolve

//******************************************************************
//  Noise
//******************************************************************
#include "PerlinNoise.fxh"





#endif //_UTILITIES_FXH_