#version 330 core

#define MAX_TEXTURES 32
struct Material {
  sampler2D diffuse[MAX_TEXTURES];
  sampler2D specular[MAX_TEXTURES];
  float shininess;
};

struct DirectionalLight {
  vec3 direction;

  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
};

struct PointLight {
  vec3 position;

  vec3 ambient;
  vec3 diffuse;
  vec3 specular;

  // attentuation factors
  float kC;
  float kL;
  float kQ;
};

struct SpotLight {
  vec3 position;

  vec3 ambient;
  vec3 diffuse;
  vec3 specular;

  // attenuation factors
  float kC;
  float kL;
  float kQ;

  // vector for the direction directly in front of the spotlight
  vec3 front;

  // spot radius
  float radius_inner;
  float radius_outer; // to smooth out the light

};

// this is the result of a light creation. This contains the multipliers for each kind of a light we want
// to have.
struct LightFactor {
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
};

in vec2 TexCoords;
in vec3 FragNormal;
in vec3 VertexWorldPos;
uniform Material material;
uniform PointLight pointLight;
uniform DirectionalLight dirLight;
uniform vec3 cameraPosition;

out vec4 FragColor;

LightFactor make_directional_light(DirectionalLight light, vec3 CONST_viewDir) {
  LightFactor res;

  vec3 DL_lightDir = normalize(-light.direction);
  res.ambient = light.ambient;

  float DL_diffuseStrength = max(dot(DL_lightDir, FragNormal), 0.0);
  res.diffuse = light.diffuse * DL_diffuseStrength;

  vec3  DL_reflectDir	    = reflect(-DL_lightDir, FragNormal);
  float DL_specularity		= max(dot(CONST_viewDir, DL_reflectDir), 0.0);
  float DL_shinePower			= pow(DL_specularity, material.shininess);
  res.specular	= light.specular * DL_shinePower;

  return res;
};

LightFactor make_point_light(PointLight light, vec3 CONST_viewDir) {
  LightFactor res;

  float PL_lightDistance = length(light.position - VertexWorldPos);
  float PL_attenuationFactor = 1.0 / 
  (light.kC + (light.kL * PL_lightDistance) + (light.kQ * PL_lightDistance * PL_lightDistance));
  res.ambient = PL_attenuationFactor * light.ambient;

  vec3 PL_lightDir = normalize(light.position - VertexWorldPos);
  float PL_diffuseStrength = max(dot(PL_lightDir, FragNormal), 0.0);
  res.diffuse = PL_attenuationFactor * light.diffuse * PL_diffuseStrength;

  vec3  PL_reflectDir		= reflect(-PL_lightDir, FragNormal);
  float PL_specularity	= max(dot(CONST_viewDir, PL_reflectDir), 0.0);
  float PL_shinePower		= pow(PL_specularity, material.shininess);
  res.specular		      = PL_attenuationFactor * PL_shinePower * light.specular;

  return res;
}

LightFactor make_spot_light(SpotLight light, vec3 CONST_viewDir) {
  LightFactor res;

  float SL_lightDistance = length(light.position - VertexWorldPos);
  float SL_attenuationFactor = 1.0 / 
  (light.kC + (light.kL * SL_lightDistance) + (light.kQ * SL_lightDistance * SL_lightDistance));
  vec3 SL_lightDir = normalize(light.position - VertexWorldPos);

  res.ambient = SL_attenuationFactor * light.ambient;

  float SL_diffAmount = dot(SL_lightDir, normalize(-light.front));
  float SL_spotLightFadeFactor = clamp((SL_diffAmount - light.radius_outer)/(light.radius_inner - light.radius_outer), 0.0f, 1.0f);
  float SL_diffuseStrength = max(dot(SL_lightDir, FragNormal), 0.0);
  res.diffuse = SL_spotLightFadeFactor * SL_attenuationFactor * light.diffuse * SL_diffuseStrength;

  vec3  SL_reflectDir		= reflect(-SL_lightDir, FragNormal);
  float SL_specularity	= max(dot(CONST_viewDir, SL_reflectDir), 0.0);
  float SL_shinePower		= pow(SL_specularity, material.shininess);
  res.specular		      = SL_spotLightFadeFactor * SL_attenuationFactor * SL_shinePower * light.specular;

  return res;
}

void main() {
  vec3 CONST_viewDir		= normalize(cameraPosition - VertexWorldPos);
  vec3 combinedAmbience = vec3(0.0);
  vec3 combinedDiffuse  = vec3(0.0);
  vec3 combinedSpecular = vec3(0.0);

  LightFactor DL_factors = make_directional_light(dirLight, CONST_viewDir);
  combinedAmbience += DL_factors.ambient;
  combinedDiffuse += DL_factors.diffuse;
  combinedSpecular += DL_factors.specular;

  //LightFactor PL_factors = make_point_light(pointLight, CONST_viewDir);
  //combinedAmbience += PL_factors.ambient;
  //combinedDiffuse += PL_factors.diffuse;
  //combinedSpecular += PL_factors.specular;

  vec3 ambientLight  = combinedAmbience * vec3(texture(material.diffuse[0],  TexCoords));
  vec3 diffuseLight  = combinedDiffuse  * vec3(texture(material.diffuse[0],  TexCoords));
  vec3 specularLight = combinedSpecular * vec3(texture(material.specular[0], TexCoords));

  vec3 color = ambientLight + diffuseLight + specularLight;
  FragColor = vec4(color, 1.0);
};