#version 330 core

struct Material
{
    sampler2D texture_diffuse1;
    sampler2D texture_specular1;
    sampler2D texture_normal1;
    float shininess;
};

struct PointLight
{
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

out vec4 FragColor;

uniform Material material;

#define NR_POINT_LIGHTS_RESERVED 2
uniform PointLight pointLights[NR_POINT_LIGHTS_RESERVED];
uniform int NR_POINTLIGHTS_SET;

uniform vec3 viewPos;
uniform vec3 addedColor;

uniform bool useNormalMapping;
in vec3 TangentLightPos[NR_POINT_LIGHTS_RESERVED];
in vec3 TangentViewPos;
in vec3 TangentFragPos;

uniform samplerCube depthMap;
uniform float far_plane;
uniform int shadows;

in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoord;

vec3 CalcPointLightMaterial(PointLight light, vec3 lightPos, Material material, vec3 fragPos, vec3 normal, vec3 viewDir);
float ShadowCalculation(vec3 fragPos, vec3 lightPos);
float ShadowCalculationPCF(vec3 fragPos, vec3 lightPos);
float ShadowCalculationPCFSampleDisk(vec3 fragPos, vec3 lightPos);

vec3 sampleOffsetDirections[20] = vec3[]
(
    vec3(1, 1, 1),  vec3(1, -1, 1),  vec3(-1, -1, 1),  vec3(-1, 1, 1),
    vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
    vec3(1, 1, 0),  vec3(1, -1, 0),  vec3(-1, -1, 0),  vec3(-1, 1, 0),
    vec3(1, 0, 1),  vec3(-1, 0, 1),  vec3(1, 0, -1),   vec3(-1, 0, -1),
    vec3(0, 1, 1),  vec3(0, -1, 1),  vec3(0, -1, -1),  vec3(0, 1, -1)
    );

void main()
{
    vec3 norm;

    if (!useNormalMapping)
    {
        norm = normalize(Normal);
    }
    else
    {
        norm = texture(material.texture_normal1, TexCoord).rgb;
        norm = normalize(norm * 2.0 - 1.0);
    }


    vec3 viewDir = normalize(viewPos - FragPos);

    vec3 result = vec3(0.0);

    for (int i = 0; i < NR_POINTLIGHTS_SET; ++i)
    {
        result += CalcPointLightMaterial(pointLights[i], TangentLightPos[i], material, FragPos, norm, viewDir);
    }

    FragColor = vec4(result, 1.0);
    
}

vec3 CalcPointLightMaterial(PointLight light, vec3 lightPos, Material material, vec3 fragPos, vec3 normal, vec3 viewDir)
{
    // get initial colors values from texture
    vec3 ambColor = texture(material.texture_diffuse1, TexCoord).rgb * addedColor;
    vec3 diffColor = texture(material.texture_diffuse1, TexCoord).rgb * addedColor;

    // Ambient part
    vec3 ambient = ambColor * light.ambient;

    // Diffuse part
    vec3 lightDir = normalize(lightPos - TangentFragPos);
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffColor);

    // specular part
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * texture(material.texture_specular1, TexCoord).rgb);

    // attenuation
    float distance = length(lightPos - TangentFragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    vec3 result;

    if (shadows == 0)
    {
        result = ambient * attenuation + diffuse * attenuation + specular * attenuation;

    }
    else if (shadows == 1)
    {
        float shadow = ShadowCalculation(fragPos, lightPos);
        result = (ambient + (1.0 - shadow) * (diffuse + specular)) * attenuation;
    }
    else if (shadows == 2)
    {
        float shadow = ShadowCalculationPCF(fragPos, lightPos);
        result = (ambient + (1.0 - shadow) * (diffuse + specular)) * attenuation;
    }
    else
    {
        float shadow = ShadowCalculationPCFSampleDisk(fragPos, lightPos);
        result = (ambient + (1.0 - shadow) * (diffuse + specular)) * attenuation;
    }

    return result;
}

float ShadowCalculation(vec3 fragPos, vec3 lightPos)
{
    vec3 fragToLight = fragPos - lightPos;
    float closestDepth = texture(depthMap, fragToLight).r;

    closestDepth *= far_plane;
    float currentDepth = length(fragToLight);

    float bias = 0.05;
    float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;

    // debug function to visualize cubemap depth buffer
    //FragColor = vec4(vec3(closestDepth / far_plane), 1.0);

    return shadow;
}

float ShadowCalculationPCF(vec3 fragPos, vec3 lightPos)
{
    vec3 fragToLight = fragPos - lightPos;
    float closestDepth = texture(depthMap, fragToLight).r;

    closestDepth *= far_plane;
    float currentDepth = length(fragToLight);

    float shadow = 0.0;
    float bias = 0.05;
    float samples = 4.0;
    float offset = 0.1;

    for (float x = -offset; x < offset; x += offset / (samples * 0.5))
    {
        for (float y = -offset; y < offset; y += offset / (samples * 0.5))
        {
            for (float z = -offset; z < offset; z += offset / (samples * 0.5))
            {
                float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r;
                closestDepth *= far_plane;

                if (currentDepth - bias > closestDepth)
                    shadow += 1.0;
            }
        }
    }

    shadow /= (samples * samples * samples);


    // debug function to visualize cubemap depth buffer
    //FragColor = vec4(vec3(closestDepth / far_plane), 1.0);

    return shadow;
}

float ShadowCalculationPCFSampleDisk(vec3 fragPos, vec3 lightPos)
{
    vec3 fragToLight = fragPos - lightPos;
    float closestDepth = texture(depthMap, fragToLight).r;

    closestDepth *= far_plane;
    float currentDepth = length(fragToLight);

    float shadow = 0.0;
    float bias = 0.05;
    int samples = 20;
    float viewDistance = length(viewPos - fragPos);
    float diskRadius = 0.05;

    for (int i = 0; i < samples; ++i)
    {
        float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;
        closestDepth *= far_plane;

        if (currentDepth - bias > closestDepth)
            shadow += 1.0f;
    }

    shadow /= float(samples);

    // debug function to visualize cubemap depth buffer
    //FragColor = vec4(vec3(closestDepth / far_plane), 1.0);

    return shadow;
}
