#version 330 core

#define POINT_LIGHT_COUNT 4

struct Material {
  sampler2D diffuse1;
  sampler2D specular1;
  float shininess;
};

struct DirLight {
  vec3 direction;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
};

struct PointLight {
  vec3 position;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
  float constant;
  float linear;
  float quadratic;
};

struct SpotLight {
  vec3 position;
  vec3 direction;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
  float cutoff;
  float outer_cutoff;
};

in vec3 vert_normal;
in vec3 frag_position;
in vec2 uv_coords;

out vec4 color;

uniform Material material;
uniform DirLight directional_light;
uniform PointLight point_lights[POINT_LIGHT_COUNT];
uniform SpotLight spot_light;
uniform vec3 camera_position;

vec3 calc_dir_light(DirLight light, vec3 normal, vec3 view_direction);
vec3 calc_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_direction);
vec3 calc_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_direction);

void main() {
  vec3  normal         = normalize(vert_normal);
  vec3  view_direction = normalize(camera_position - frag_position);

  vec3 result = calc_dir_light(directional_light, normal, view_direction);

  for (int i = 0; i < POINT_LIGHT_COUNT; ++i) {
    result += calc_point_light(point_lights[i], normal, frag_position, view_direction);
  }

  result += calc_spot_light(spot_light, normal, frag_position, view_direction);

  color = vec4(result, 1.0);
};

vec3 calc_dir_light(DirLight light, vec3 normal, vec3 view_direction) {
  vec3 light_direction     = normalize(-light.direction);
  vec3  reflect_direction  = reflect(-light_direction, normal);
  float diff               = max(dot(normal, light_direction), 0.0);
  vec3  diff_tex           = vec3(texture(material.diffuse1, uv_coords));
  float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
  vec3  ambient            = light.ambient * diff_tex;
  vec3  diffuse            = light.diffuse * (diff * diff_tex);
  vec3  specular           = light.specular * (spec * vec3(texture(material.specular1, uv_coords)));

  return ambient + diffuse + specular;
}

vec3 calc_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_direction) {
  vec3 light_direction     = normalize(light.position - frag_position);
  vec3  reflect_direction  = reflect(-light_direction, normal);
  float distance           = length(light.position - frag_position);
  float attenuation        = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
  float diff               = max(dot(normal, light_direction), 0.0);
  vec3  diff_tex           = vec3(texture(material.diffuse1, uv_coords));
  float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
  vec3  ambient            = light.ambient * diff_tex * attenuation;
  vec3  diffuse            = light.diffuse * (diff * diff_tex) * attenuation;
  vec3  specular           = light.specular * (spec * vec3(texture(material.specular1, uv_coords))) * attenuation;

  return ambient + diffuse + specular;
}

vec3 calc_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_direction) {
  vec3 light_direction     = normalize(light.position - frag_position);
  vec3  reflect_direction  = reflect(-light_direction, normal);
  float theta              = dot(light_direction, normalize(-light.direction));
  float epsilon            = light.cutoff - light.outer_cutoff;
  float intensity          = clamp((theta - light.outer_cutoff) / epsilon, 0.0, 1.0);
  float diff               = max(dot(normal, light_direction), 0.0);
  float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
  vec3  diff_tex           = vec3(texture(material.diffuse1, uv_coords));
  vec3  ambient            = light.ambient * diff_tex;
  vec3  diffuse            = light.diffuse * (diff * diff_tex) * intensity;
  vec3  specular           = light.specular * (spec * vec3(texture(material.specular1, uv_coords))) * intensity;

  return ambient + diffuse + specular;
}
