#version 150

#moj_import <fog.glsl>

const vec4 matte = vec4(0.005, 0.0, 0.07, 0.0);
const vec4 mult = vec4(1.05, 1.07, 1.0, 1.0);


/*vec2 clampToGridCutout(vec2 vec, vec2 gridCoord, vec2 gridSize)
{
    if (vec.x < gridCoord.x) vec.x = gridCoord.x;
    if (vec.x > gridCoord.x + gridSize.x) vec.x = gridCoord.x + gridSize.x;
    if (vec.y < gridCoord.y) vec.y = gridCoord.y;
    if (vec.y > gridCoord.y + gridSize.y) vec.y = gridCoord.y + gridSize.y;
    return vec;
}
vec2 clampToGrid(vec2 vec, vec2 gridCoord, vec2 gridSize, bool cutout)
{
    if (cutout) return clampToGridCutout(vec, gridCoord, gridSize);
    if (vec.x < gridCoord.x) vec.x += gridSize.x;
    if (vec.x > gridCoord.x + gridSize.x) vec.x -= gridSize.x;
    if (vec.y < gridCoord.y) vec.y += gridSize.y;
    if (vec.y > gridCoord.y + gridSize.y) vec.y -= gridSize.y;
    return vec;
}
vec4 tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i, bool cutout )
{
    vec2 tex_size = textureSize(textureSampler_i, 0);
    vec2 tex_offset = 1.0 / tex_size;
    vec2 gridSize = tex_offset * 256;
    vec2 gridCoord = floor(texCoord_i / gridSize) * gridSize;
    if (cutout) {
        gridSize -= tex_offset;
        gridCoord += tex_offset * 0.5;
    }
    texCoord_i -= tex_offset * 0.5;

    vec2 texCoord0 = clampToGrid(texCoord_i + vec2(0,            0           ), gridCoord, gridSize, cutout);
    vec2 texCoord1 = clampToGrid(texCoord_i + vec2(tex_offset.x, 0           ), gridCoord, gridSize, cutout);
    vec2 texCoord2 = clampToGrid(texCoord_i + vec2(0,            tex_offset.y), gridCoord, gridSize, cutout);
    vec2 texCoord3 = clampToGrid(texCoord_i + vec2(tex_offset.x, tex_offset.y), gridCoord, gridSize, cutout);

    vec4 p0q0 = texture(textureSampler_i, texCoord0);
    vec4 p1q0 = texture(textureSampler_i, texCoord1);

    vec4 p0q1 = texture(textureSampler_i, texCoord2);
    vec4 p1q1 = texture(textureSampler_i, texCoord3);

    float a = fract( texCoord_i.x * tex_size.x );

    vec4 pInterp_q0 = mix( p0q0, p1q0, a );
    vec4 pInterp_q1 = mix( p0q1, p1q1, a );

    float b = fract( texCoord_i.y * tex_size.y );
    return mix( pInterp_q0, pInterp_q1, b );
}
float Triangular( float f )
{
	f = f / 2.0;
	if( f < 0.0 )
	{
		return ( f + 1.0 );
	}
	else
	{
		return ( 1.0 - f );
	}
	return 0.0;
}
float CatMullRom( float x )
{
    const float B = 0.0;
    const float C = 0.5;
    float f = x;
    if( f < 0.0 )
    {
        f = -f;
    }
    if( f < 1.0 )
    {
        return ( ( 12 - 9 * B - 6 * C ) * ( f * f * f ) +
            ( -18 + 12 * B + 6 *C ) * ( f * f ) +
            ( 6 - 2 * B ) ) / 6.0;
    }
    else if( f >= 1.0 && f < 2.0 )
    {
        return ( ( -B - 6 * C ) * ( f * f * f )
            + ( 6 * B + 30 * C ) * ( f *f ) +
            ( - ( 12 * B ) - 48 * C  ) * f +
            8 * B + 24 * C)/ 6.0;
    }
    else
    {
        return 0.0;
    }
}
float BSpline( float x )
{
	float f = x;
	if( f < 0.0 )
	{
		f = -f;
	}

	if( f >= 0.0 && f <= 1.0 )
	{
		return ( 2.0 / 3.0 ) + ( 0.5 ) * ( f* f * f ) - (f*f);
	}
	else if( f > 1.0 && f <= 2.0 )
	{
		return 1.0 / 6.0 * pow( ( 2.0 - f  ), 3.0 );
	}
	return 1.0;
}  
vec4 tex2DBiCubic( sampler2D textureSampler_i, vec2 texCoord_i, bool cutout )
{
    vec2 tex_size = textureSize(textureSampler_i, 0);
    vec2 tex_offset = 1.0 / tex_size;
    vec2 gridSize = tex_offset * 256;
    vec2 gridCoord = floor(texCoord_i / gridSize) * gridSize;
    if (cutout) {
        gridSize -= tex_offset;
        gridCoord += tex_offset * 0.5;
    }
    texCoord_i -= tex_offset * 0.5;

    vec4 nSum = vec4( 0.0, 0.0, 0.0, 0.0 );
    vec4 nDenom = vec4( 0.0, 0.0, 0.0, 0.0 );
    float a = fract( texCoord_i.x * tex_size.x );
    float b = fract( texCoord_i.y * tex_size.y );
    for( int m = -1; m <=2; m++ )
    {
        for( int n =-1; n<= 2; n++)
        {
			vec4 vecData = texture(textureSampler_i, clampToGrid(texCoord_i + vec2(tex_offset.x * float( m ), tex_offset.y * float( n )), gridCoord, gridSize, cutout));
			float f  = BSpline( float( m ) - a );
			vec4 vecCooef1 = vec4( f,f,f,f );
			float f1 = BSpline ( -( float( n ) - b ) );
			vec4 vecCoeef2 = vec4( f1, f1, f1, f1 );
            nSum = nSum + ( vecData * vecCoeef2 * vecCooef1  );
            nDenom = nDenom + (( vecCoeef2 * vecCooef1 ));
        }
    }
    return nSum / nDenom;
}*/

vec3 minTextColor = vec3((1.0 / 256) * 36, (1.0 / 256) * 28, (1.0 / 256) * 43);
vec4 gradeText(vec4 color) {
	color.r = color.r * color.r * 0.6 + color.r * 0.4;
	color.g = color.g * color.g * 0.8 + color.g * 0.2;
	color.b = color.b * color.b * 0.4 + color.b * 0.6;

    if (color.r < minTextColor.r) color.r = minTextColor.r;
    if (color.g < minTextColor.g) color.g = minTextColor.g;
    if (color.b < minTextColor.b) color.b = minTextColor.b;

    return color;
}

vec4 outlineTexture(sampler2D textureSampler, vec2 texCoord, vec4 color) {
    vec2 tex_size = textureSize(textureSampler, 0);
    if (tex_size.x < 256) // Don't apply to low-res textures. Prevents effect being applied to maps
        return color;
    float tex_offset = 0.0075; // Outline thickness as fraction of texture atlas width
    vec2 tex_coord2;
    float sample;
    float mean = 0;
    for( int m = -1; m <= 1; m += 1 ) {
        for( int n = -1; n <= 1; n += 1) {
            tex_coord2 = texCoord + vec2(float(m) * tex_offset, float(n) * tex_offset);
            if (tex_coord2.x > 0 && tex_coord2.x < 1 && tex_coord2.y > 0 && tex_coord2.y < 1) {
                sample = texture(textureSampler, tex_coord2).r;
                mean = mean + sample;
            }
        }
    }
    /*mean = mean / 4 - 3;
    if (mean < 0) mean = 0;*/
    mean = mean / 4.5 - 1;
    if (mean < 0) mean = 0;
    color.r *= mean;
    color.g *= mean;
    color.b *= mean;
    color = gradeText(color);
    return color;
}

float RGBtoSaturation(vec3 c) {
    float M = max(c.r, max(c.g, c.b));
    float m = min(c.r, min(c.g, c.b));
    return (M - m) / M;
}

vec3 RGBtoHSV(vec3 c) {
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 RGBtoHSL(vec3 color) {
    float r = color.r; float g = color.g; float b = color.b;
    float M = max(r, max(g, b));
    float m = min(r, min(g, b));
    float c = M - m;
    float h; float s; float l;
    if (c == 0) {
        h = 0;
        s = 0;
        l = r;
    } else {
        if (M == r) {
            h = (g - b) / c;
            if (h < 0)
                h = h + 6;
        }
        else if (M == g) {
            h = (b - r) / c + 2;
        }
        else if (M == b) {
            h = (r - g) / c + 4;
        }
        h = h * 60;
        if (h < 0) {
            h = h + 360;
        }
        l = (M + m) / 2;
        s = c / (1 - abs(2 * l - 1));
    }
    return vec3(h / 360.0, s, l);
}

float hueToRGB(float p, float q, float t) {
    if (t < 0)
        t += 1.0;
    if (t > 1)
        t -= 1.0;
    if (t < 1.0/6.0)
        return p + (q - p) * 6.0 * t;
    if (t < 1.0/2.0)
        return q;
    if (t < 2.0/3.0)
        return p + (q - p) * (2.0/3.0 - t) * 6.0;
    return p;
}

vec3 HSVtoRGB(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

vec3 HSLtoRGB(vec3 color) {
    float h = color.x; float s = color.y; float l = color.z; 
    float r; float g; float b; float q; float p;
    if (s == 0) {
        r = l;
        g = l;
        b = l;
    } else {
        if (l < 0.5) {
            q = l * (1.0 + s);
        } else {
            q = l + s - l * s;
        }
        p = 2 * l - q;
        r = hueToRGB(p, q, h + 1.0/3.0);
        g = hueToRGB(p, q, h);
        b = hueToRGB(p, q, h - 1.0/3.0);
    }
    return vec3(r, g, b);
}

float Cubic(float value) {
    if (value < 0.5)
    {
        return value * value * value * value * value * 16.0; 
    }
    value -= 1.0;
    return value * value * value * value * value * 16.0 + 1.0;
}

vec4 basicColorGradingMatteOnly(vec4 color) {
    return color + matte;
}

vec4 basicColorGrading(vec4 color) {
    return color * mult + matte;
}

vec4 shadeBasic(vec4 color, vec4 vertexColor) {
    vertexColor = vertexColor * 1.1;
    vertexColor = clamp(vertexColor, 0, 1);
    color = color * vertexColor;

    color = basicColorGrading(color);

    return color;
}

vec4 shadeEntity(vec4 color, vec4 vertexColor, vec4 ColorModulator, vec4 lightMapColor, vec4 overlayColor, float vertexDistance, float fogStart, float fogEnd, vec4 fogColor) {
    // Make textures a bit lighter
    /*color.rgb *= 0.95;
    color.rgb += 0.05;*/

    vec3 vertexColorHSV = RGBtoHSV(vertexColor.rgb);
    // Make entities brighter, but not on coloured textures like leather armour
    if (vertexColorHSV.y <= 0.001) {
        vertexColorHSV.z = sqrt(vertexColorHSV.z);
    }
    vertexColor = vec4(HSVtoRGB(vertexColorHSV), vertexColor.a);

    color = color * vertexColor * ColorModulator;
    color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a);
    color *= lightMapColor;

    vec3 hsv = RGBtoHSV(color.rgb);
    
    // Increase contrast and reduce saturation in unlit regions
    float light = 1 - lightMapColor.g;
    light = light * light;
    hsv.z *= 1 + light * 0.75;
    hsv.y *= 1 - light * 0.3;

    // Fix black point
    hsv.z -= 0.035;

    color = vec4(HSVtoRGB(hsv), color.a);
    color = linear_fog(color, vertexDistance, fogStart, fogEnd, fogColor + matte);
    color = basicColorGrading(color);
    return color;
}

vec4 shade(vec4 color, vec4 normal, vec4 vertexColor, vec4 lightColor, float vertexDistance, float fogStart, float fogEnd, vec4 fogColor) {
    vec3 vertexColorHSV = RGBtoHSV(vertexColor.rgb);

    // Toonify the lightColor * vertexColor by converting to HSV and stepping the value V
    vec3 shadeHSV = RGBtoHSV(vertexColor.rgb * lightColor.rgb);
    
    shadeHSV.z = sqrt(shadeHSV.z);

    shadeHSV.z = floor(shadeHSV.z * 11.96) / 12;
    // Undo the sqrt, but mainly for coloured textures like grass/leaves
    shadeHSV.z = shadeHSV.z * shadeHSV.z * vertexColorHSV.y * 0.65 + shadeHSV.z * (1 - vertexColorHSV.y * 0.8);
    // Prevent coloured textures from getting too dark
    shadeHSV.z += vertexColorHSV.y * 0.05;

    // Clamp the shading, raising the darkest shadow, and capping the brightest lighting to prevent overexposure on bright blocks like snow
    //shadeHSV.z = clamp(shadeHSV.z, 0.05, 1);

    vec4 shadeColor = vec4(HSVtoRGB(shadeHSV), 1.0);
    // Colour grading the shading
    //shadeColor.b = shadeColor.b - 0.01 + (1 - shadeHSV.z) * 0.04;
    //shadeColor.g = shadeColor.g - (1 - shadeHSV.z) * 0.01;

    // Apply shading to color
    color = color * shadeColor;

    // Convert color to HSV
    vec3 hsv = RGBtoHSV(color.rgb);

    // Fix black point
    hsv.z -= 0.035;

    // Saturate image, more so in darker colours
    hsv.y = hsv.y * (1.15 - hsv.z * 0.15);

    // Increase contrast and reduce saturation in unlit regions
    float light = 1 - lightColor.g;
    light = light * light;
    hsv.z *= 1 + light * 0.75 * (1 - hsv.z);
    hsv.y *= 1 - light * 0.3;

    // Shine
    /*float lightColorMax = max(lightColor.r, max(lightColor.g, lightColor.b));
    float shine = vertexDistance / fogEnd * lightColorMax;
    hsv.z = hsv.z + (hsv.z * shine);*/

    // Contrast and gamma
    hsv.z = Cubic(hsv.z) * 0.15 + hsv.z * 0.87 + hsv.z * hsv.z * 0.05;

    // Convert HSV back to color
    color = vec4(HSVtoRGB(hsv), color.a);

    // Colour the shine cyan
    /*color.g = color.g + (1 - color.g) * shine * 0.5;
    color.b = color.b + (1 - color.b) * shine * 0.5;*/

    color = basicColorGrading(color);

    color = linear_fog(color, vertexDistance, fogStart, fogEnd, fogColor + matte); // Fog
    return color;
}

vec4 shadeTranslucent(vec4 color, vec4 vertexColor, vec4 lightColor, float vertexDistance, float fogStart, float fogEnd, vec4 fogColor) {
    vec3 vertexColorHSV = RGBtoHSV(vertexColor.rgb);

    // Toonify the lightColor * vertexColor by converting to HSV and stepping the value V
    vec3 shadeHSV = RGBtoHSV(vertexColor.rgb * lightColor.rgb);
    
    shadeHSV.z = sqrt(shadeHSV.z);

    shadeHSV.z = floor(shadeHSV.z * 11.96 + 0.75) / 12 - 0.083;
    // Undo the sqrt, but mainly for coloured textures like grass/leaves
    shadeHSV.z = shadeHSV.z * shadeHSV.z * vertexColorHSV.y * 0.75 + shadeHSV.z * (1 - vertexColorHSV.y * 0.8);
    // Prevent coloured textures from getting too dark
    shadeHSV.z += vertexColorHSV.y * 0.05;

    // Clamp the shading, raising the darkest shadow, and capping the brightest lighting to prevent overexposure on bright blocks like snow
    //shadeHSV.z = clamp(shadeHSV.z, 0.05, 1);

    vec4 shadeColor = vec4(HSVtoRGB(shadeHSV), 1.0);
    // Colour grading the shading
    //shadeColor.b = shadeColor.b - 0.01 + (1 - shadeHSV.z) * 0.04;
    //shadeColor.g = shadeColor.g - (1 - shadeHSV.z) * 0.01;

    // Apply shading to color
    color = color * shadeColor;

    // Convert color to HSV
    vec3 hsv = RGBtoHSV(color.rgb);

    // Fix black point
    hsv.z -= 0.035;

    // Saturate image, more so in darker colours
    hsv.y = hsv.y * (1.15 - hsv.z * 0.15);

    // Increase contrast and reduce saturation in unlit regions
    float light = 1 - lightColor.g;
    light = light * light;
    hsv.z *= 1 + light * 0.75 * (1 - hsv.z);
    hsv.y *= 1 - light * 0.4;

    // Shine
    float lightColorMax = max(lightColor.r, max(lightColor.g, lightColor.b));
    float shine = vertexDistance / fogEnd * lightColorMax;
    hsv.z = hsv.z + (hsv.z * shine);

    // Contrast and gamma
    hsv.z = Cubic(hsv.z) * 0.1 + hsv.z * 0.73 + hsv.z * hsv.z * 0.2;

    // Convert HSV back to color
    color = vec4(HSVtoRGB(hsv), color.a);

    // Colour the shine cyan
    color.g = color.g + (1 - color.g) * shine * 0.5;
    color.b = color.b + (1 - color.b) * shine * 0.5;

    color = basicColorGrading(color);

    color = linear_fog(color, vertexDistance, fogStart, fogEnd, fogColor + matte); // Fog
    return color;
}