#version 400

const int MAX_POINT_LIGHTS = 2;
const int MAX_SPOT_LIGHTS = 3;

in vec4 vsColor;
in vec2 vsTextureCoord;
in vec3 vsNormal;
in vec3 vsTangent;
in vec3 vsWorldPosition;
in vec4 vsLightPositions[MAX_SPOT_LIGHTS];

out vec4 fsFragmentColor;

struct BaseLight
{ 
	vec3  color;
	float ambientIntensity;
	float diffuseIntensity;
};

struct DirectionalLight
{ 
	BaseLight base;  
	vec3 direction;  
};

struct Attenuation
{
	float constant;
	float linear;
	float exponential;
};

struct PointLight
{
	BaseLight base;
	vec3 position;
	Attenuation attenuation;
};

struct SpotLight
{
	PointLight pointLight;
	vec3 direction; 
	float cutoff;
};

uniform int uPointLightsNumber;
uniform int uSpotLightsNumber;
uniform DirectionalLight uDirectionalLight;
uniform PointLight uPointLights[MAX_POINT_LIGHTS];
uniform SpotLight uSpotLights[MAX_SPOT_LIGHTS];
uniform sampler2D uTextureSampler;
uniform sampler2D uShadowMapSampler[MAX_SPOT_LIGHTS];
uniform sampler2D uNormalMapSampler;
uniform vec3 uCameraPosition;
uniform float uMaterialSpecularIntensity;
uniform float uMaterialSpecularPower;
uniform bool uCalculateShadows;

float CalcShadowFactor(vec4 lightPosition, int i)
{  	
	vec3 projectionCoordinates = lightPosition.xyz / lightPosition.w;
	vec2 UVCoordinates;
	UVCoordinates.x = 0.5f * projectionCoordinates.x + 0.5f;
	UVCoordinates.y = 0.5f * projectionCoordinates.y + 0.5f;
	
	float z = 0.5f * projectionCoordinates.z + 0.5f;

	float depth = texture(uShadowMapSampler[i], UVCoordinates).x;

	if (depth < z + 0.00001f)
		return 0.2f;
	else
		return 1.0f;

} 

vec4 CalcLightInternal(BaseLight light, vec3 _lightDirection, vec3 _normal, float shadowFactor)
{
	vec4 diffuseColor = vec4(0, 0, 0, 0);  
	vec4 specularColor = vec4(0, 0, 0, 0);
	
	vec3 lightDirectionOpposite = normalize(-_lightDirection);
	vec3 normal = normalize(_normal);		 

	float diffuseFactor = dot(normal, lightDirectionOpposite);
	  
	if (diffuseFactor > 0 )
	{  
		diffuseColor = vec4(light.color* light.diffuseIntensity * diffuseFactor, 1.0f);  

		vec3 vertexToEyeDirection = normalize(uCameraPosition - vsWorldPosition); 
		
		vec3 lightDirection = normalize(_lightDirection);
		vec3 lightReflectionDirection = normalize(reflect(lightDirection, normal));

		float specularFactor = dot(vertexToEyeDirection, lightReflectionDirection);		  
		if (specularFactor > 0) 
		{
			specularFactor = pow(specularFactor, uMaterialSpecularPower);
			specularColor = vec4(light.color * uMaterialSpecularIntensity * specularFactor, 1.0f);
		}
	} 

	vec4 ambientColor = vec4(light.color* light.ambientIntensity, 1.0f);
	return ( ambientColor + shadowFactor * (diffuseColor + specularColor) );
}

vec4 CalcDirectionalLight(vec3 normal)
{  
	return CalcLightInternal(uDirectionalLight.base, uDirectionalLight.direction, normal, 1.0);
}

vec4 CalcPointLight(PointLight pointLight, vec3 normal, int i) 
{
	vec3 lightDirection = vsWorldPosition - pointLight.position; 
	float distance = length(lightDirection);
	lightDirection = normalize(lightDirection); 

	float shadowFactor = 1.0f;
	if(uCalculateShadows)
	{
		shadowFactor = CalcShadowFactor(vsLightPositions[i], i);  
	}
		
	vec4 color = CalcLightInternal(pointLight.base, lightDirection, normal, shadowFactor); 
	float attenuation =  pointLight.attenuation.constant + 
						 pointLight.attenuation.linear * distance + 
						 pointLight.attenuation.exponential * distance * distance;
		
	return color / attenuation; 
}

vec4 CalcSpotLight(SpotLight l, vec3 normal, int i)
{
	vec3 lightToPixel = normalize(vsWorldPosition - l.pointLight.position);  
	float spotFactor = dot(lightToPixel, l.direction);  
		
	if (spotFactor > l.cutoff) 
	{
		vec4 color = CalcPointLight(l.pointLight, normal, i);
		return color * (1.0 - (1.0 - spotFactor) * 1.0/(1.0 - l.cutoff)); 
	}  
	else 
	{
		return vec4(0,0,0,0);
	}  
}

vec3 CalcBumpedNormal(vec3 _normal, vec3 _tangent, vec2 textureCoordinates)
{
	vec3 normal = normalize(_normal); 

	vec3 tangent = normalize(_tangent);  
	tangent = normalize(tangent - dot(tangent, normal) * normal);

	vec3 bitangent = cross(tangent, normal);

	vec3 bumpMapNormal = texture(uNormalMapSampler, textureCoordinates).xyz;  
	bumpMapNormal = 2.0 * bumpMapNormal - vec3(1.0, 1.0, 1.0);

	vec3 newNormal; 
	mat3 tangentBitangentNormal = mat3(tangent, bitangent, normal);  
	newNormal = tangentBitangentNormal * bumpMapNormal;  
	newNormal = normalize(newNormal); 
	return newNormal;  
}

subroutine void RenderPassType();
subroutine uniform RenderPassType renderPass;

subroutine (RenderPassType)
void recordDepth()
{}

subroutine (RenderPassType)
void defaultShading()
{
	vec3 normal =  CalcBumpedNormal(vsNormal, vsTangent, vsTextureCoord);
	
	vec4 totalLight = CalcDirectionalLight(normal);

	for (int i = 0 ; i < uPointLightsNumber ; i++) 
	{ 
		totalLight += CalcPointLight(uPointLights[i], normal, i);  
	} 
	
	for (int i = 0 ; i < uSpotLightsNumber ; i++) 
	{  
		totalLight += CalcSpotLight(uSpotLights[i], normal, i);  
	} 

	vec4 color = texture2D(uTextureSampler, vsTextureCoord);
	if(vsColor != vec4(.0f, .0f, .0f, 1.0f) && color == vec4( 1.0f, 1.0f, 1.0f, 1.0f))
	{
		color = vsColor;
	}
	
	fsFragmentColor = color * totalLight;
}

void main()
{
	renderPass();
}
