#version 450
#extension GL_ARB_separate_shader_objects : enable

#define MAX_LIGHTS 4

struct Material
{
	vec4 color;
	bool diffused;
	float specular;
	float shininess;
};

struct AmbientPointLight
{
	vec3 color;
	vec3 pos;
};

struct PointLight
{
	vec3 color;
	float constant;
	vec3 pos;
	float linear;
	float quadratic;
};

struct SpotLight
{
	vec3 color;
	float innerCutOff;
	vec3 pos;
	float outerCutOff;
	vec3 dir;
	float constant;
	float linear;
	float quadratic;
};

layout(std140, binding = 1) uniform Data
{
	vec3 ambient;
	bool preMultiply;
	vec3 camPos;
	Material material;
	uint ambientPointLightCount;
	uint pointLightCount;
	uint spotLightCount;
	AmbientPointLight ambientLights[MAX_LIGHTS];
	PointLight pointLights[MAX_LIGHTS];
	SpotLight spotLights[MAX_LIGHTS];
} data;

layout(location = 1) in vec3 fNorm;
layout(location = 0) in vec4 fWorldPos;

layout(location = 0) out vec4 result;

vec3 CalcAmbientPointLight(vec3 normal, vec3 vertexPos, vec3 viewDir, Material material, AmbientPointLight light)
{
	vec3 lightDir = normalize(light.pos - vertexPos);

	vec3 diffuse = light.color * max(dot(normal, lightDir), 0.0f);

	vec3 specular = light.color * pow(max(dot(normal, normalize(lightDir + viewDir)), 0.0), material.shininess); 

	return diffuse + specular;
}

vec3 CalcPointLight(vec3 normal, vec3 vertexPos, vec3 viewDir, Material material, PointLight light)
{
	vec3 lightDir = normalize(light.pos - vertexPos);

	float dist = length(light.pos - vertexPos);

	float attenuation = 1.0f / (light.constant + light.linear * dist + light.quadratic * (dist * dist));

	vec3 diffuse = light.color * max(dot(normal, lightDir), 0.0f);

	vec3 specular = light.color * pow(max(dot(normal, normalize(lightDir + viewDir)), 0.0), material.shininess); 

	return (diffuse + specular) * attenuation;
}

vec3 CalcSpotLight(vec3 normal, vec3 vertexPos, vec3 viewDir, Material material, SpotLight light)
{
	vec3 lightDir = normalize(light.pos - vertexPos);

	float dist = length(light.pos - vertexPos);

	float attenuation = 1.0f / (light.constant + light.linear * dist + light.quadratic * (dist * dist));

	float theta = dot(lightDir, normalize(-light.dir));
	float epsilon = light.innerCutOff - light.outerCutOff;
	float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0f, 1.0f);

	vec3 diffuse = light.color * max(dot(normal, lightDir), 0.0f);
	
	vec3 specular = light.color * pow(max(dot(normal, normalize(lightDir + viewDir)), 0.0), material.shininess);

	return (diffuse + specular) * intensity * attenuation;
}

void main()
{
	if (data.material.diffused == true)
	{
		vec3 viewDir = normalize(data.camPos - fWorldPos.xyz);
		vec3 nNorm = normalize(fNorm);

		vec3 final = data.ambient;
	 
	 	//Ambient Point Lights
		for (uint i = 0; i < data.ambientPointLightCount; ++i)
		final += CalcAmbientPointLight(nNorm, fWorldPos.xyz, viewDir, data.material, data.ambientLights[i]);

		//Point Lights
		for (uint i = 0; i < data.pointLightCount; ++i)
		final += CalcPointLight(nNorm, fWorldPos.xyz, viewDir, data.material, data.pointLights[i]);

		//Spot Lights
		for (uint i = 0; i < data.spotLightCount; ++i)
		final += CalcSpotLight(nNorm, fWorldPos.xyz, viewDir, data.material, data.spotLights[i]);
 
		//outColor = vec4(data.spotLights[0].quadratic, 0.0f, 0.0f, 1.0f);

		result = vec4(data.material.color.rgb * final, data.material.color.a);
	}
	else
	{
		result = data.material.color;
	}

	if (data.preMultiply)
		result = vec4(result.rgb * result.a, result.a);
}