#version 430 core
in VertexData {
	vec3 position_world;
	vec3 normal_world;
	vec2 uv;
	vec4 FragPosLightSpace;
} vert;

layout (location = 0) out vec4 color;
layout (location = 1) out vec4 BrightColor;

uniform vec3 camera_world;
uniform vec3 lightPos;

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

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

uniform struct PointLight {
	vec3 color;
	vec3 position;
	vec3 attenuation;
	bool enabled;
};
#define NR_POINT_LIGHTS 9
uniform PointLight pointL[NR_POINT_LIGHTS];

vec3 phong(vec3 n, vec3 l, vec3 v, vec3 diffuseC, float diffuseF, vec3 specularC, float specularF, float alpha, bool attenuate, vec3 attenuation, bool CalcShadow, float shadow) {
	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);
	float shad = 0.0;
	if(CalcShadow) shad = shadow;
	return (diffuseF * diffuseC * max(0, dot(n, l)) + specularF * specularC * pow(max(0, dot(r, v)), alpha)) * att * (1.0 - shad); 
}

vec2 poissonDisk[16] = vec2[]( 
   vec2( -0.94201624, -0.39906216 ), 
   vec2( 0.94558609, -0.76890725 ), 
   vec2( -0.094184101, -0.92938870 ), 
   vec2( 0.34495938, 0.29387760 ), 
   vec2( -0.91588581, 0.45771432 ), 
   vec2( -0.81544232, -0.87912464 ), 
   vec2( -0.38277543, 0.27676845 ), 
   vec2( 0.97484398, 0.75648379 ), 
   vec2( 0.44323325, -0.97511554 ), 
   vec2( 0.53742981, -0.47373420 ), 
   vec2( -0.26496911, -0.41893023 ), 
   vec2( 0.79197514, 0.19090188 ), 
   vec2( -0.24188840, 0.99706507 ), 
   vec2( -0.81409955, 0.91437590 ), 
   vec2( 0.19984126, 0.78641367 ), 
   vec2( 0.14383161, -0.14100790 ) 
);

// Returns a random number based on a vec3 and an int.
float random(vec3 seed, int i){
	vec4 seed4 = vec4(seed,i);
	float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
	return fract(sin(dot_product) * 43758.5453);
}

float ShadowCalculation(vec4 fragPosLightSpace){
	//perspective divide
	vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

	//transform to range [0,1]
	projCoords = projCoords * 0.5 + 0.5;

	//get closest depth value from light's perspective
	float closestDepth = texture(shadowMap, projCoords.xy).r;

	//get depth of current fragment from light's perspective
	float currentDepth = projCoords.z;

	//check whether current frag is in shadow
	vec3 lightDir = normalize(lightPos - vert.position_world);
	vec3 normal = normalize(vert.normal_world);
	float bias = max(0.0022 * (1.0 - dot(normal, lightDir)), 0.0018);

	//PCF
	float shadow = 0.0;
	for (int i=0;i<4;i++){
		//int index = int(16.0*random(gl_FragCoord.xyy, i))%16;
		int index = int(16.0*random(floor(vert.position_world.xyz*1000.0), i))%16;
  		if ( texture( shadowMap, projCoords.xy + poissonDisk[index]/2000.0).r  <  currentDepth - bias ){
    			shadow += 0.22;
  		}
	} 

	if(projCoords.z > 1.0){
		shadow = 0.0;
	}
	
	return shadow;
}

void main() {	
	vec3 n = normalize(vert.normal_world);
	vec3 v = normalize(camera_world - vert.position_world);
	
	vec3 texColor = texture(diffuseTexture, vert.uv).rgb;
	color = vec4(texColor * materialCoefficients.x, 1); // ambient
	
	float shadow = ShadowCalculation(vert.FragPosLightSpace);
	// add directional light contribution
	color.rgb += phong(n, -dirL.direction, v, dirL.color * texColor, materialCoefficients.y, dirL.color, materialCoefficients.z, specularAlpha, false, vec3(0), true, shadow);
			
	// add point light contribution
	for(int i = 0; i < NR_POINT_LIGHTS; i++){
		if(pointL[i].enabled){
			color.rgb += phong(n, pointL[i].position - vert.position_world, v, pointL[i].color * texColor, materialCoefficients.y, pointL[i].color, materialCoefficients.z, specularAlpha, true, pointL[i].attenuation, false, shadow);
		}
	}
	float brightness = dot(color.rgb, vec3(0.34,0.34,0.34));
	if(brightness > 0.9){
		BrightColor = vec4(color.rgb, 1.0);
	}else{
		BrightColor = vec4(0.0,0.0,0.0,1.0);
	}

}
