#version 330 core
out vec4 FragColor;

const int MAX_POINT_LIGHTS = 10;

in vec3 FragPosition;
in vec3 Normal;
in vec3 Color;

struct BaseLight
{
	vec3 color;
    float ambientIntensity;
    float diffuseIntensity;
};

struct Attenuation
{
	float constant;
	float linear;
	float exponential;
};

struct PointLight
{
    BaseLight base;
	Attenuation atten;
	vec3 position;
    float farPlane;
    samplerCube depthMap;
};

struct Material
{
    vec3 ambientColor;
    vec3 diffuseColor;
	vec3 specularColor;
	float shininess;
};

uniform int numOfPointLights;
uniform PointLight pointLights[MAX_POINT_LIGHTS];
uniform Material material;

//uniform sampler2D diffuseTexture;


uniform vec3 viewPosition;

//uniform float farPlane;
//uniform bool shadows;   

vec3 gridSamplingDisk[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)
);

int bayer[64] = int[64](
     0, 32,  8, 40,  2, 34, 10, 42, 
    48, 16, 56, 24, 50, 18, 58, 26, 
    12, 44,  4, 36, 14, 46,  6, 38, 
    60, 28, 52, 20, 62, 30, 54, 22, 
     3, 35, 11, 43,  1, 33,  9, 41, 
    51, 19, 59, 27, 49, 17, 57, 25,
    15, 47,  7, 39, 13, 45,  5, 37,
    63, 31, 55, 23, 61, 29, 53, 21 
);

float ShadowCalculation(PointLight light, vec3 normal)
{
    // get vector between fragment position and light position
    vec3 fragToLight = FragPosition - light.position;
    float currentDepth = length(fragToLight);

	vec3 lightDir = normalize(light.position - FragPosition);

    float shadow = 0.0;
    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
    //float bias = 0.15;
    int samples = 20;
    float viewDistance = length(viewPosition - FragPosition);
    float diskRadius = (1.0 + (viewDistance / light.farPlane)) / 25.0;
    for(int i = 0; i < samples; ++i)
    {
        float closestDepth = texture(light.depthMap, fragToLight + gridSamplingDisk[i] * diskRadius).r;
        //FragColor = vec4(vec3(closestDepth / farPlane), 1.0);
        closestDepth *= light.farPlane;   // undo mapping [0;1]
        if(currentDepth - bias > closestDepth)
            shadow += 1.0;
    }
    shadow /= float(samples);

    //shadow = dither(shadow);
        
    // display closestDepth as debug (to visualize depth cubemap) 
    // FragColor = vec4(vec3(closestDepth / farPlane), 1.0);
        
    return shadow;
}

vec4 dither(vec4 color)
{
    float downsampleFactor = 2;
    float ditherSize = 0.05;
    float colorSize = 8.0;

    float _x = (gl_FragCoord.x/downsampleFactor);
	float _y = (gl_FragCoord.y/downsampleFactor);
    _x = mod(_x, 8);
	_y = mod(_y, 8);
    int _M = bayer[8*int(_y) + int(_x)];
    float _z = float(_M) * (1.0 / pow(8, 2.0));
    _z -= 0.5;

    color.x += _z * ditherSize;
	color.y += _z * ditherSize;
	color.z += _z * ditherSize;

    //color = greyscale;
    color = floor(color * (colorSize - 1) + 0.5) / (colorSize - 1);
    vec3 greyscale = vec3(0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);

    return color;
}

/*vec3 diffuse(vec3 _lightColor, vec3 _lightPosition)
{
    // diffuse
    vec3 lightDir = normalize(_lightPosition - FragPosition);
    vec3 lightDist = _lightPosition - FragPosition;
    float diff = max(dot(lightDir, Normal), 0.0);

    float linear = 0.2;
    float exponential = 0.2;
    // Apply attenuation: light fades over distance
    float attenuation = 1.0 / (1.0 + (linear * length(lightDist)) + (exponential * pow(length(lightDist), 2.0)));

    vec3 diffuse = diff * lightColor;
    return diffuse;
}*/

/*vec3 specular(vec3 _lightPosition)
{
     // specular
    vec3 lightDir = normalize(_lightPosition - FragPosition);
    vec3 viewDir = normalize(viewPosition - FragPosition);
    vec3 reflectDir = reflect(-lightDir, Normal);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);  
    spec = pow(max(dot(Normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;    
    return specular;
}*/

vec4 calculateAmbient(BaseLight light)
{
    vec4 ambientColor = vec4(light.color, 1.0) *
                        light.ambientIntensity *
                        vec4(material.ambientColor, 1.0);

    return ambientColor;
}

vec4 calculateDiffuse(BaseLight light, vec3 lightDirection, vec3 normal)
{
    vec4 diffuseColor = vec4(0.0);
    float diffuseFactor = dot(normal, -lightDirection);

    if (diffuseFactor > 0.0)
	{
		diffuseColor = vec4(light.color, 1.0) *
					   light.diffuseIntensity *
					   vec4(material.diffuseColor, 1.0) *
					   diffuseFactor;
	}

    return diffuseColor;
}

vec4 calculateSpecular(BaseLight light, vec3 lightDirection, vec3 normal)
{
    vec4 specularColor = vec4(0.0);
    vec3 pixelToCamera = normalize(viewPosition - FragPosition);
    vec3 lightReflect = normalize(reflect(lightDirection, normal));
    float specularFactor = dot(pixelToCamera, lightReflect);

    if (specularFactor > 0.0)
    {
        specularColor = vec4(light.color, 1.0) *
						light.diffuseIntensity *
						vec4(material.specularColor, 1.0) *
						specularFactor;
    }
    
    return specularColor * material.shininess;
}

vec4 caclulatePointLight(int index, vec3 normal)
{
    vec3 lightDirection = FragPosition - pointLights[index].position;
    float lightDistance = length(lightDirection);
    lightDirection = normalize(lightDirection);

    float shadow = 1.0 - ShadowCalculation(pointLights[index], normal);

    vec4 color = calculateAmbient(pointLights[index].base) + 
                 shadow *
				 (calculateDiffuse(pointLights[index].base, lightDirection, normal) +
				 calculateSpecular(pointLights[index].base, lightDirection, normal));


   // color = vec4(Color, 1.0);

    float attenuation = pointLights[index].atten.constant +
					    pointLights[index].atten.linear * lightDistance +
					    pointLights[index].atten.exponential * (lightDistance * lightDistance);

    return color / attenuation;
}

void main()
{
	vec3 normal = normalize(Normal);
    vec4 totalLight = vec4(0.0, 0.0, 0.0, 0.0);

    for (int i = 0; i < numOfPointLights; i++)
    {
		totalLight += caclulatePointLight(i, normal);
    }

    //totalLight = dither(totalLight);
    FragColor = totalLight;
}

