#version 430 core
/*
* Copyright 2019 Vienna University of Technology.
* Institute of Computer Graphics and Algorithms.
* This file is part of the ECG Lab Framework and must not be redistributed.
*/

in VertexData {
	vec3 position_world;
	vec3 normal_world;
	vec2 uv;
} vert;

out vec4 color;

uniform vec3 camera_world;

uniform vec3 materialCoefficients; // x = ambient, y = diffuse, z = specular 
uniform float specularAlpha;
uniform sampler2D diffuseTexture;

uniform struct DirectionalLight {
	vec3 color;
	vec3 direction;
} dirL;

uniform struct PointLight {
	vec3 color;
	vec3 position;
	vec3 attenuation;
} pointL;

uniform struct SpotLight {
	vec3 position;
	vec3 direction;
	float cutOff;
} spotL;

uniform bool materialObject;

in vec3 colorData; 
const float levels = 3.0;

uniform samplerCube depthMap;

uniform float far_plane;
uniform bool shadows;
uniform bool celShaded;

//(floor(att*levels)/levels)


vec3 gridSamplingDisk[20] = vec3[]
(
   vec3(1, 1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1, 1,  1), 
   vec3(1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
   vec3(1, 1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1, 1,  0),
   vec3(1, 0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1, 0, -1),
   vec3(0, 1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0, 1, -1)
);

float ShadowCalculation(vec3 fragPos)
{
    vec3 fragToLight = fragPos - pointL.position;
    float closestDepth = texture(depthMap, fragToLight).r;
    float currentDepth = length(fragToLight);
	float shadow = 0.0;
    float bias = 0.15;
    int samples = 20;
    float viewDistance = length(camera_world - fragPos);
    float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;
    for(int i = 0; i < samples; ++i)
    {
        float closestDepth = texture(depthMap, fragToLight + gridSamplingDisk[i] * diskRadius).r;
        closestDepth *= far_plane;   // undo mapping [0;1]
        if(currentDepth - bias > closestDepth)
            shadow += 1.0;
    }
    shadow /= float(samples);
    return shadow;
}

vec3 phong(vec3 n, vec3 l, vec3 v, vec3 diffuseC, float diffuseF, vec3 specularC, float specularF, float alpha, bool attenuate, vec3 attenuation) {
	float d = length(l);
	l = normalize(l);
	float att = 1.0;
	if(attenuate) att = 1.0f / (attenuation.x + d * attenuation.y + d * d * attenuation.z);
	vec3 r = reflect(-l, n);
	if(materialCoefficients.z != 0.0)
		return (diffuseF * diffuseC * max(0, dot(n, l)) + specularF * specularC * pow(max(0, dot(r, v)), alpha)) * att;
	else
		return (diffuseF * diffuseC * max(0, dot(n, l))) * att; 
}

vec3 lighting(float ambientStrength, float diffuseStrength, float specularStrength, vec3 lightColor, vec3 norm, vec3 lightPos, vec3 FragPos, vec3 objectColor){
    vec3 ambient = ambientStrength * lightColor;
  	
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diffuseStrength* diff * lightColor;
    
    vec3 viewDir = normalize(camera_world - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;
    
    float shadow = shadows ? ShadowCalculation(vert.position_world) : 0.0;

    vec3 result = (ambient + ((1.0-shadow*1.5)*(diffuse + specular))) * objectColor;
    return result;
}

vec3 rgb2hsv(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);
}

void main() {
	vec3 n = normalize(vert.normal_world);
	vec3 v = normalize(camera_world - vert.position_world);
	
	vec3 texColor = texture(diffuseTexture, vert.uv).rgb;
	if(materialObject)
		texColor = colorData.rgb;
	color = materialCoefficients.x * vec4(texColor * materialCoefficients.x, 1); // ambient

	// add directional light contribution
	//color.rgb += phong(n, -dirL.direction, v, dirL.color * texColor, materialCoefficients.y, dirL.color, materialCoefficients.z, specularAlpha, false, vec3(0));
			
	// add point light contribution
    color.rgb += lighting(materialCoefficients.x, materialCoefficients.y, materialCoefficients.z, pointL.color, n, pointL.position, vert.position_world, texColor);

	vec3 lightDir = normalize(spotL.position - vert.position_world);
	float theta = dot(lightDir, normalize(-spotL.direction));

	if(theta > spotL.cutOff)
    { 
		color.rgb += vec3(0.5, 0.5, 0.0) + phong(n, pointL.position - vert.position_world, v, vec3(1, 1, 0) * texColor, materialCoefficients.y, pointL.color, materialCoefficients.z, specularAlpha, true, pointL.attenuation);
	}

	float strength = rgb2hsv(color.rgb).z;
	strength = (floor(strength*levels)/levels);
	if(celShaded)
        color.rgb *= strength*2;
}

