uniform sampler2D color_lut;
uniform sampler1D color_grad;
uniform sampler2D distort_lut;
uniform sampler2D noise_lut;

uniform float lut_fade;
uniform vec3 distortParams; // freq, time, amount
uniform vec2 gammaParams; // exposure, gamma
uniform vec3 noiseParams; // offset.xy, amount
uniform vec2 invViewport;


vec3 desaturate(in vec3 color, in float desat)
{
	float lum = dot(color, vec3(0.299, 0.587, 0.114));
	return mix(color, vec3(lum), desat);
}


#if 0
vec3 RGBToHSL(in vec3 color)
{
	vec3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part)

	float fmin = min(min(color.r, color.g), color.b);    //Min. value of RGB
	float fmax = max(max(color.r, color.g), color.b);    //Max. value of RGB
	float delta = fmax - fmin;             //Delta RGB value

	hsl.z = (fmax + fmin) / 2.0; // Luminance

	if ( delta == 0.0 ) // This is a gray, no chroma
	{
		hsl.x = 0.0;
		hsl.y = 0.0;
	}
	else // Chromatic data
	{
		if (hsl.z < 0.5)
			hsl.y = delta / (fmax + fmin); // Saturation
		else
			hsl.y = delta / (2.0 - fmax - fmin); // Saturation

		float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
		float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
		float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;

		if (color.r == fmax )
			hsl.x = deltaB - deltaG; // Hue
		else if (color.g == fmax)
			hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue
		else if (color.b == fmax)
			hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue

		if (hsl.x < 0.0)
			hsl.x += 1.0; // Hue
		else if (hsl.x > 1.0)
			hsl.x -= 1.0; // Hue
	}

	return hsl;
}

float HueToRGB(in float f1, in float f2, in float hue)
{
	if (hue < 0.0)
		hue += 1.0;
	else if (hue > 1.0)
		hue -= 1.0;
	float res;
	if ((6.0 * hue) < 1.0)
		res = f1 + (f2 - f1) * 6.0 * hue;
	else if ((2.0 * hue) < 1.0)
		res = f2;
	else if ((3.0 * hue) < 2.0)
		res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
	else
		res = f1;
	return res;
}

vec3 HSLToRGB(in vec3 hsl)
{
	vec3 rgb;

	if (hsl.y == 0.0)
	{
		rgb = vec3(hsl.z); // Luminance
	}
	else
	{
		float f2;

		if (hsl.z < 0.5)
			f2 = hsl.z * (1.0 + hsl.y);
		else
			f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);

		float f1 = 2.0 * hsl.z - f2;

		rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
		rgb.g = HueToRGB(f1, f2, hsl.x);
		rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
	}

	return rgb;
}

vec3 ContrastSaturationBrightness(in vec3 color, in float brt, in float sat, in float con)
{
	// Increase or decrease theese values to adjust r, g and b color channels seperately
	const float AvgLumR = 0.5;
	const float AvgLumG = 0.5;
	const float AvgLumB = 0.5;

	const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721);

	vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB);
	vec3 brtColor = color * brt;
	vec3 intensity = vec3(dot(brtColor, LumCoeff));
	vec3 satColor = mix(intensity, brtColor, sat);
	vec3 conColor = mix(AvgLumin, satColor, con);
	return conColor;
}
#endif

#if 0
// usage:
// vec2 uv = from_hammer_aitoff( inputUv*2.0 - vec2(1.0) );
vec2 from_hammer_aitoff(in vec2 pos)
{
	const float pi = 3.1415927;
	const float two_pi = pi * 2.0;
	float z = 1.0 - pos.x*pos.x/2.0 - pos.y*pos.y/2.0;
	float longitude = 2.0 * atan( sqrt( 2.0 ) * pos.x * z / ( 2.0 * z * z - 1.0 ) );
	float latitude = asin( sqrt( 2.0 ) * pos.y * z );

	// convert to 0..1 range
	return (vec2( latitude/two_pi , longitude/pi ) + vec2(1.0)) * vec2(0.5);
}
#endif

// usage:
// const float pi = 3.1415927;
// const float two_pi = pi * 2.0;
// vec2 pos = (inputUV * vec2(pi, two_pi) ) - vec2(pi/2.0, pi);
// vec2 uv = to_hammer_aitoff( pos );

vec2 to_hammer_aitoff(in vec2 pos)
{
	float z = sqrt( 1.0 + cos( pos.x ) * cos( pos.y / 2.0 ) );

	vec2 ret;
	ret.x = sin( pos.x ) / z;
	ret.y = cos( pos.x ) * sin( pos.y / 2.0 ) / z;

	return (ret + vec2(1.0)) * vec2(0.5);
}


vec2 distort_uv(in vec2 uv)
{
#if 0
	vec2 offs;

	offs.x = textureLod(distort_lut, vec2(uv.y*distortParams.x + distortParams.y, 0.0), 0.0).x*distortParams.z * 0.01;
	offs.x *= (sin(uv.y*3.14*distortParams.x + 3.14*distortParams.y)*2.0 - 1.0)*distortParams.z;
	offs.y = (sin(uv.x*3.14*distortParams.x + 3.14*distortParams.y)*2.0 - 1.0)*distortParams.z*0.01;
	float falloff = smoothstep(0.75, 1.0, distance(uv, vec2(0.5))*2.0);
	return uv + offs*falloff;
#else
	//vec2 offs = from_hammer_aitoff(uv*2.0 - vec2(1.0));
	const float pi = 3.1415927;
	const float two_pi = pi * 2.0;
	vec2 pos = (uv * vec2(pi, two_pi) ) - vec2(pi/2.0, pi);

	vec2 offs = to_hammer_aitoff(pos);
	return mix(uv, offs, distortParams.z);
#endif
}

#if 0
vec3 filmic_tonemap(in vec3 x)
{
	const float A = 0.15;
	const float B = 0.50;
	const float C = 0.10;
	const float D = 0.20;
	const float E = 0.02;
	const float F = 0.30;
	const float W = 11.2;
	return ( (x * (x*A + vec3(C*B)) + vec3(D*E)) / (x*(x*A + vec3(B)) + vec3(D*F))) - vec3(E/F);
}
#endif

// used by color_lookup()
vec4 color_correction(in vec4 color)
{
#if 1
	color *= gammaParams.x;
	//color.rgb = pow(color.rgb, vec3(1.0/gammaParams.y));
#else
	color.rgb = filmic_tonemap(color.rgb*16.0*gammaParams.x);
	color.rgb *= 1.0 / filmic_tonemap(vec3(11.2)).r;
	color.rgb = pow(color.rgb, gammaParams.yyy);
#endif
	return color;
}

vec4 noise_lookup()
{
	vec2 uv = gl_FragCoord.xy * invViewport;
	float n = (texture(noise_lut, uv + noiseParams.xy).x*2.0 - 1.0)*noiseParams.z;
	return vec4(n, n, n, 1.0);
}

vec4 color_lum(in vec4 color)
{
	float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));
	return texture(color_lut, vec2(lum, 1.0));
}

vec4 color_gradient(in vec4 color)
{
	vec4 c;
	// TODO - bake this in a 3d lookup texture
	c.r = texture(color_grad, color.r).r;
	c.g = texture(color_grad, color.g).g;
	c.b = texture(color_grad, color.b).b;
	c.a = color.a;
	return c;
}


vec4 color_lookup(in vec4 color, in vec2 uv)
{
	//float falloff = smoothstep(0.4, 1.0, distance(uv, vec2(0.5))*1.4);

	vec4 cl = color_lum(color);
	vec4 cg = color_gradient(color);
	vec4 c = mix(cg, cl, lut_fade);
	return color_correction(c) + noise_lookup();
}
