#version 450

const float PI = 3.14159265359;
const int LIGHT_TYPE_DIRECTIONAL = 0;
const int LIGHT_TYPE_POINT = 1;
const int LIGHT_TYPE_SPOT = 2;

struct Material {
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 bloom;
    float roughness;
    float metallic;
};

layout(location = 0) in vec3 inColor;
layout(location = 1) in vec2 fragUV;
layout(location = 2) in vec3 inWorldPosition;
layout(location = 3) in vec3 inWorldNormal;

layout(location = 0) out vec4 outColor;

layout(std140, binding = 0) uniform UniformBuffer
{
    mat4 projectionMatrix;
    mat4 viewMatrix;
    mat4 transformationMatrix;
    vec4 cameraPosition;
    Material material;

} uniformBuffer;

layout(binding = 1) uniform sampler2D diffuseTexture;

struct Light {
    vec4 position;
    vec4 direction;
    vec4 colorIntensity;
    vec4 attenuation;
    vec4 cutoff;
    ivec4 meta; // x = aktiv, y = welcher enum typ ist es
};

layout(std140, binding = 2) uniform LightBlock
{
    ivec4 header; // x = anzahl
    Light lights[10];
} lightBlock;

float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a = roughness * roughness;
    float a2 = a * a;

    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH * NdotH;

    float denom = NdotH2 * (a2 - 1.0) + 1.0;
    denom = PI * denom * denom;

    return a2 / max(denom, 0.0001);
}

float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;

    return NdotV / max(NdotV * (1.0 - k) + k, 0.0001);
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);

    float ggx1 = GeometrySchlickGGX(NdotV, roughness);
    float ggx2 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

vec3 FresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

vec3 CookTorranceBRDF(
    vec3 N,
    vec3 V,
    vec3 L,
    vec3 albedo,
    vec3 radiance,
    float metallic,
    float roughness
) {
    vec3 H = normalize(V + L);

    float NdotL = max(dot(N, L), 0.0);
    float NdotV = max(dot(N, V), 0.0);

    if (NdotL <= 0.0 || NdotV <= 0.0) {
        return vec3(0.0);
    }

    vec3 F0 = vec3(0.04);
    F0 = mix(F0, albedo, metallic);

    float D = DistributionGGX(N, H, roughness);
    float G = GeometrySmith(N, V, L, roughness);
    vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);

    vec3 numerator = D * G * F;
    float denominator = 4.0 * NdotV * NdotL + 0.0001;
    vec3 specular = numerator / denominator;

    vec3 kS = F;
    vec3 kD = vec3(1.0) - kS;

    // metals do not have diffuse reflection
    kD *= 1.0 - metallic;

    vec3 diffuse = kD * albedo / PI;

    return (diffuse + specular) * radiance * NdotL;
}

void main()
{
    vec3 normal = normalize(inWorldNormal);

    vec3 baseColor = texture(diffuseTexture, fragUV).rgb;

    vec3 albedo = baseColor * uniformBuffer.material.diffuse.rgb;
    float metallic = uniformBuffer.material.metallic;
    float roughness = uniformBuffer.material.roughness;
    float alpha = uniformBuffer.material.diffuse.a;

    vec3 toCamera = normalize(uniformBuffer.cameraPosition.xyz - inWorldPosition);

    vec3 totalLight = vec3(0.0);

    for (int i = 0; i < lightBlock.header.x; ++i) {
        Light light = lightBlock.lights[i];

        if (light.meta.x == 0) {
            continue;
        }

        int type = light.meta.y;

        if (type == LIGHT_TYPE_DIRECTIONAL) {
            vec3 L = normalize(-light.direction.xyz);
            vec3 radiance = light.colorIntensity.rgb * light.colorIntensity.w;

            totalLight += CookTorranceBRDF(
                normal,
                toCamera,
                L,
                albedo,
                radiance,
                metallic,
                roughness
            );
        }
        else if (type == LIGHT_TYPE_POINT) {
            vec3 lightPos = light.position.xyz;

            vec3 toPointLight = lightPos - inWorldPosition;
            float distance = length(toPointLight);
            vec3 L = normalize(toPointLight);

            float attenuation = 1.0 / (
                light.attenuation.x +
                light.attenuation.y * distance +
                light.attenuation.z * distance * distance
            );

            vec3 radiance = light.colorIntensity.rgb * light.colorIntensity.w * attenuation;

            totalLight += CookTorranceBRDF(
                normal,
                toCamera,
                L,
                albedo,
                radiance,
                metallic,
                roughness
            );
        }
        else if (type == LIGHT_TYPE_SPOT) {
            vec3 spotPos = light.position.xyz;
            vec3 toSpotLight = spotPos - inWorldPosition;
            float spotDistance = length(toSpotLight);
            vec3 L = normalize(toSpotLight);

            vec3 spotDirection = normalize(light.direction.xyz);
            float theta = dot(L, -spotDirection);
            float intensity = smoothstep(light.cutoff.y, light.cutoff.x, theta);

            float attenuation = 1.0 / (
                light.attenuation.x +
                light.attenuation.y * spotDistance +
                light.attenuation.z * spotDistance * spotDistance
            );

            vec3 radiance = light.colorIntensity.rgb * light.colorIntensity.w * attenuation * intensity;

            totalLight += CookTorranceBRDF(
                normal,
                toCamera,
                L,
                albedo,
                radiance,
                metallic,
                roughness
            );
        }
    }

    vec3 ambient = vec3(0.10) * albedo;
    vec3 color = ambient + totalLight;

    //color = color / (color + vec3(1.0));
    outColor = vec4(color, alpha);
}