// Store a fragment shader source code as string
// Always Define Version!
#version 460 core
#define NUMBER_OF_LIGHTS 4

///////////
// Types //
///////////
struct Material
{
	sampler2D diffuseMap0;
	bool hasDiffuseMap;
	bool hasSpecularMap;
	bool hasNormalMap;
	vec3 specularColor;
	float diffuseStrength;
	float shininess;
	float specularStrength;
};

struct Light
{
	 vec3 position;
	 vec3 direction;
	 vec3 color;
	 float strength;
	 float ambientStrength;
	 float linAttenFact; // attenuation factors control decay of light intensity
	 float quadrAttenFact;
	 float cutOff; // for a light cone
	 float outerCutOff;
	 bool activated;
	 int type; // 0 = ambient, 1 = point light, 2 = directional Light, 3 = spotlight
};

////////////////////////////
// Input/Output Variables //
////////////////////////////
in vec3 Normal;						// Define Input Variable. It has the same name as the output variable of the vertex shader. THis way it is passed through
in vec3 FragPos;
in vec2 TexCoordOI;
out vec4 FragColor;					// Define output (mandatory) which must be a color vector (r,g,b,transparency)

//////////////
// Uniforms //
//////////////
uniform Material material;
uniform Light lightSources[NUMBER_OF_LIGHTS];
uniform vec3 cameraPosition;
uniform samplerCube shadowDepthMap;
uniform float shadowFarPlane;
uniform bool shadowsActive;

///////////////
// Functions //
///////////////
float calculateShadowFactor(vec3 fragPosition, Light light)
{
	if(shadowsActive)
	{
		// vector from light source to fragment position yields distance from light to fragment (occludee)
		vec3 vectorLightToFrag = fragPosition - light.position;
		float distanceLightToFrag = length(vectorLightToFrag);
		float normalizedDistanceToOccludingFrag = texture(shadowDepthMap, vectorLightToFrag).r;
		float distanceLightToOccludingFrag = shadowFarPlane * normalizedDistanceToOccludingFrag;
		vec3 vectorLightToOccludingFrag =  distanceLightToOccludingFrag * normalize(vectorLightToFrag);

		// create directions which are later added to occluding point to generate 20 samples of its  neighbourhood
		float numberOfSamples = 20.0;
		vec3 directions[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)
		); 
	
		// sample on a neighbourhood around occluding fragment and take average value to soften shadow at edges
		float sampleRadius = 0.06;
		float bias = 0.15; 
		float numberOfSamplesInLight = 0.0f;
		for(int i=0; i < numberOfSamples; i++)
		{
			vec3 vectorLightToOccludingSamplePoint    = vectorLightToOccludingFrag + (directions[i] * sampleRadius);
			float normalizedDistanceLightToOccludingSamplePoint =  texture(shadowDepthMap, vectorLightToOccludingSamplePoint).r;
			float distanceLightToOccludingSamplePoint = shadowFarPlane * normalizedDistanceLightToOccludingSamplePoint;
			if(distanceLightToFrag - bias < distanceLightToOccludingSamplePoint) numberOfSamplesInLight += 1.0f;
 
		}

		// Return averaged shadow values between 0 and 1 (1 = fully in light, 0 = full in shadow)
		float shadowFactor = numberOfSamplesInLight/numberOfSamples;
		return shadowFactor;
	}
	else // no schadows active
	{
		return 1.0f;
	}
}

vec3 calculateFragColorFromLight(Light light, Material material, vec3 Normal, vec3 cameraPosition, vec3 fragPos)
{
	if(!light.activated) return vec3(0,0,0);
	vec3 normal = normalize(Normal);
	vec3 viewDir = normalize( cameraPosition - fragPos);

	// Get color of current fragment from material texture
	vec3 diffuseTextureColor; 
	vec3 specularTextureColor;
	if(material.hasDiffuseMap) diffuseTextureColor = vec3(texture(material.diffuseMap0, TexCoordOI));
	else diffuseTextureColor = vec3(0.5, 0.5, 0.5);
	//if(material.hasSpecularMap) specularTextureColor = vec3(texture(material.specularMap0, TexCoordOI));
	//else specularTextureColor = diffuseTextureColor;
	specularTextureColor = diffuseTextureColor;
	
	// Ambient Light
	if(light.type == 0)
	{
		vec3 colorComponentAmbientLight =  diffuseTextureColor * light.color * light.strength;
		return colorComponentAmbientLight;
	}

	// Point Light
	else if(light.type == 1)
	{
		//Attenuation for pointlight sources
		float distanceFromLight = length(light.position - fragPos);
		float attenuation = 1.0 / (1 + light.linAttenFact * distanceFromLight + light.quadrAttenFact *(distanceFromLight*distanceFromLight));

		// POINT LIGHT: Diffuse Light Component
		vec3 pointLightDirection = normalize(light.position - fragPos);
		float diffusionFactor = max(dot(normal, pointLightDirection),0);
		vec3 diffuseColorFromPointLight = diffusionFactor * material.diffuseStrength * diffuseTextureColor;
		diffuseColorFromPointLight *= attenuation;

		// POINT LIGHT: specular lighting
		vec3 reflectDir = reflect(-pointLightDirection, normal);
		float specularRaw = pow(max(dot(reflectDir, viewDir), 0.0f), material.shininess);
		vec3 specularColorFromPointLight =  material.specularStrength * specularRaw * specularTextureColor;
		specularColorFromPointLight *= attenuation;

		// POINT LIGHT: shadow factor (1 or 0)
		float shadowFactor = calculateShadowFactor(fragPos, light);
		
		// POINT LIGHT: Bring all factors together for final color contribution
		vec3 colorComponentPointLight = (diffuseColorFromPointLight + specularColorFromPointLight) * light.color * light.strength * shadowFactor;
		return colorComponentPointLight;
	}

	// Directional light
	else if(light.type == 2)
	{
		// DIRECTIONAL LIGHT: Diffuse component
		float directionalLightDiffusionFactor = max(dot(normal, - light.direction),0);
		vec3 diffuseColorFromDirectionalLight = directionalLightDiffusionFactor * diffuseTextureColor;

		// DIRECTIONAL LIGHT: Specular Component
		vec3 reflectDir = reflect(light.direction, normal);
		float specularRaw = pow(max(dot(reflectDir, viewDir), 0.0f), material.shininess);
		vec3 specularColorFromDirectionalLight =  material.specularStrength * specularRaw * specularTextureColor;

		vec3 colorComponentDirectionalLight = (diffuseColorFromDirectionalLight + specularColorFromDirectionalLight) * light.strength * light.color;
		return colorComponentDirectionalLight;
	}

	// Spotlight
	if(light.type == 3)
	{
		//Attenuation for spotLight
		float distanceSpotlight = length(light.position - fragPos);
		float attenuationSpotlight = 1.0 / (1 + light.linAttenFact * distanceSpotlight + light.quadrAttenFact *(distanceSpotlight*distanceSpotlight));

		// SPOT LIGHT (Flash Light on camera) diffuse and specular
		vec3 diffuseSpotlightColor = vec3(0.0f, 0.0f, 0.0f);
		vec3 specularSpotlightColor = vec3(0.0f, 0.0f, 0.0f);
		bool isFragInLightCone = false;
		float scalarProduct = max(0, dot(normalize(light.direction), normalize(fragPos-light.position)));
		if( scalarProduct > light.outerCutOff ) 
		{
			isFragInLightCone = true;
		}
		if(isFragInLightCone)
		{
			// Intensity
			float coneIntensity = (scalarProduct - light.outerCutOff) / (light.cutOff-light.outerCutOff);
			coneIntensity = clamp(coneIntensity, 0.0, 1.0);

			//Diffuse flash light
			float diffusionFactor = max(dot(normal, -light.direction),0);
			diffuseSpotlightColor = diffusionFactor * material.diffuseStrength * diffuseTextureColor;
			diffuseSpotlightColor *= attenuationSpotlight;
			diffuseSpotlightColor *= coneIntensity;
		
			//specular flash light
			vec3 reflectDir = reflect(light.direction, normal);
			float specularRaw = pow(max(dot(reflectDir, viewDir), 0.0f), material.shininess);
			specularSpotlightColor =  material.specularStrength * specularRaw * specularTextureColor;
			specularSpotlightColor *= attenuationSpotlight;
			specularSpotlightColor *= coneIntensity;
		}
		vec3 colorComponentSpotlight = (diffuseSpotlightColor + specularSpotlightColor)* light.strength * light.color;
		return colorComponentSpotlight;
	}

	// Undefined Light type
	else
	{
		return vec3(0,0,0);
	}
}

void main()
{
	// Accumulate color contributions form each light source (Phong Lighting)
	vec3 accumulatedColorComponents = vec3(0,0,0);
	for(int i; i<NUMBER_OF_LIGHTS; i++)
	{
		accumulatedColorComponents += calculateFragColorFromLight(lightSources[i], material, Normal, cameraPosition, FragPos);
	}
	// Return resulting color of fragment 
	FragColor = vec4(accumulatedColorComponents, 1);
};