#version 450
#extension GL_ARB_separate_shader_objects : enable

#define MAX_LIGHTS 4

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 = 2) uniform Data
{
    vec3 ambient;
    bool preMultiply;
    vec4 multiplier;
    uint ambientPointLightCount;
    uint pointLightCount;
    uint spotLightCount;
    AmbientPointLight ambientLights[MAX_LIGHTS];
    PointLight pointLights[MAX_LIGHTS];
    SpotLight spotLights[MAX_LIGHTS];
} data;

layout(binding = 3) uniform sampler2D colorMap;
layout(binding = 4) uniform sampler2D normalMap;
layout(binding = 5) uniform sampler2D specularMap;

layout(location = 0) in vec4 fVertPos;
layout(location = 1) in vec3 fTanPos;
layout(location = 2) in vec2 fUV;
layout(location = 3) in vec3 fViewPos;
layout(location = 4) in mat3 fTBN;

layout(location = 0) out vec4 result;

vec3 CalcAmbientPointLight(vec3 normal, vec3 vertexPos, vec3 viewDir, 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), texture(specularMap, fUV).r);

    return diffuse + specular;
}

vec3 CalcPointLight(vec3 normal, vec3 vertexPos, vec3 viewDir, 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), texture(specularMap, fUV).r);

    return (diffuse + specular) * attenuation;
}

vec3 CalcSpotLight(vec3 normal, vec3 vertexPos, vec3 viewDir, 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), texture(specularMap, fUV).r);

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

void main()
{
    vec3 viewDir = normalize(fViewPos - fTanPos);
    vec3 nNorm = normalize(texture(normalMap, fUV).rgb * 2.0f - 1.0f);

    vec3 final = data.ambient;

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

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

    //Spot Lights
    for (uint i = 0; i < data.spotLightCount; ++i)
        final += CalcSpotLight(nNorm, fVertPos.xyz, viewDir, data.spotLights[i]);

    vec4 tColor = texture(colorMap, fUV);

    result = vec4(tColor.rgb * final * data.multiplier.rgb, tColor.a * data.multiplier.a);

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