#version 330 core

out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} fs_in;

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 
  
struct Light {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    vec3 position;
};

uniform Material material;
uniform Light light;

uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;

uniform vec3 viewPos;

void main() {           
   
    vec3 normal = normalize(fs_in.Normal);
	vec3 lightDir = normalize(light.position - fs_in.FragPos);

    // ambient term
    vec3 ambient = light.ambient * material.ambient;

    // diffuse term
    float diff = max(dot(lightDir, normal), 0.0f);
    vec3 diffuse = light.diffuse * diff * material.diffuse;

    // specular term
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    vec3 H = normalize(lightDir + viewDir);
    float spec = pow(max(dot(normal, H), 0.0f), material.shininess);
	vec3 specular = light.specular * (spec * material.specular);

    // shadow calculation
	vec3 projCoords = (fs_in.FragPosLightSpace.xyz / fs_in.FragPosLightSpace.w) * 0.5 + 0.5; //transformation to [0.0, 1.0];
	float closestDepth = texture(shadowMap, projCoords.xy).r;
	float currentDepth = projCoords.z;
	float bias = 0.05f;  

	float shadow = 0.0f;
    vec2 texelSize = 1.2f / textureSize(shadowMap, 0);
    for(int x = -1; x <= 1; ++x) {
        for(int y = -1; y <= 1; ++y) {
            float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; 
            shadow += currentDepth - bias > pcfDepth  ? 1.0f : 0.0f;     
        }
    }
    shadow /= 9.0f;

	if(projCoords.z > 1.0f) shadow = 0.0f;
	
	vec3 texColor = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 lighting = (ambient + (1.0f - shadow) * (diffuse + specular)) * texColor;    
    FragColor = vec4(lighting, 1.0f);
}