#version 410
/*
* Copyright 2023 Vienna University of Technology.
* Institute of Computer Graphics and Algorithms.
* This file is part of the GCG Lab Framework and must not be redistributed.
*/

in VertexData {
    vec3 position_world;
    vec3 normal_world;
    vec2 uv;
} vert;

out vec4 color;

uniform vec3 camera_world;

uniform vec3 materialCoefficients;// x = ambient, y = diffuse, z = specular
uniform float specularAlpha;
uniform sampler2D diffuseTexture;

uniform bool draw_normals;
uniform bool draw_texcoords;
uniform bool draw_fresnel;

uniform vec3 diffuseColor;
uniform bool useTexture;
uniform float opacity;

uniform mat4 lightSpaceMatrix;
uniform sampler2D shadowMap;

uniform struct DirectionalLight {
    vec3 color;
    vec3 direction;
} dirL;

uniform struct PointLight {
    vec3 color;
    vec3 position;
    vec3 attenuation;
} pointL;


vec3 phong(vec3 n, vec3 l, vec3 v, vec3 diffuseC, float diffuseF, vec3 specularC, float specularF, float alpha, bool attenuate, vec3 attenuation) {
    float d = length(l);
    l = normalize(l);
    float att = 1.0;
    if (attenuate) att = 1.0f / (attenuation.x + d * attenuation.y + d * d * attenuation.z);
    vec3 r = reflect(-l, n);
    return (diffuseF * diffuseC * max(0, dot(n, l)) + specularF * specularC * pow(max(0, dot(r, v)), alpha)) * att;
}

// Computes the reflection direction for an incident vector I about normal N, 
// and clamps the reflection to a maximum of 180, i.e. the reflection vector
// will always lie within the hemisphere around normal N.
// Aside from clamping, this function produces the same result as GLSL's reflect function.
vec3 clampedReflect(vec3 I, vec3 N)
{
    return I - 2.0 * min(dot(N, I), 0.0) * N;
}

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

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // perspective divide
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

    // transform to [0,1]
    projCoords = projCoords * 0.5 + 0.5;

    // outside light frustum
    if (projCoords.z > 1.0 || projCoords.x > 1.0 || projCoords.x < 0.0 || projCoords.y > 1.0 || projCoords.y < 0.0) {
        return 0.0;
    }


    float currentDepth = projCoords.z;

    vec3 normal = normalize(vert.normal_world);
    vec3 lightDir = normalize(-dirL.direction);

    float bias = max(0.005 * (1.0 - dot(normal, lightDir)), 0.0005);

    float shadow = 0.0;

    vec2 texelSize = 1.0 / textureSize(shadowMap, 0);

    for (int x = -1; x <= 1; ++x)
    {
        for (int y = -1; y <= 1; ++y)
        {
            // Sample depth at the current offset coordinate block
            float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;

            // Perform standard shadow test and pile on the score
            shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
        }
    }

    return shadow / 9.0;
}

void main() {
    vec3 n = normalize(vert.normal_world);
    vec3 v = normalize(vert.position_world - camera_world);

    vec4 fragPosLightSpace = lightSpaceMatrix * vec4(vert.position_world, 1.0);
    float shadow = ShadowCalculation(fragPosLightSpace);


    vec3 R = normalize(clampedReflect(v, n));
    vec3 F0 = vec3(0.1);

    vec3 baseAlbedo = diffuseColor;
    if (useTexture) {
        baseAlbedo = texture(diffuseTexture, vert.uv).rgb;
    }

    color = vec4(baseAlbedo * materialCoefficients.x, opacity);

    // Add directional light contribution (use baseAlbedo instead of texColor)
    vec3 dirLight = phong(
    n,
    -dirL.direction,
    -v,
    dirL.color * baseAlbedo,
    materialCoefficients.y,
    dirL.color,
    materialCoefficients.z,
    specularAlpha,
    false,
    vec3(0)
    );

    color.rgb += dirLight * (1.0 - shadow);

    // Add point light contribution
    color.rgb += phong(n, pointL.position - vert.position_world, -v, pointL.color * baseAlbedo, materialCoefficients.y, pointL.color, materialCoefficients.z, specularAlpha, true, pointL.attenuation);


    if (draw_fresnel) {
        vec3 skyColor = vec3(0.5, 0.7, 1.0);
        vec3 F0 = vec3(0.04);// Default for non-metals
        vec3 reflectivity = fresnelSchlick(max(dot(n, -v), 0.0), F0);

        color.rgb = mix(color.rgb, skyColor * 0.5, reflectivity);
    }

    if (draw_normals) {
        color.rgb = n*0.5 + 0.5;
    }
    if (draw_texcoords) {
        color.rgb = vec3(vert.uv, 0);
    }
}

