#version 400

struct EnvLight
{
	vec3  diffuse;
	float intensity;
	vec3 position;
};
 
layout(std140) uniform EnvironmentLightBlock
{
	int ub_size;
	int ub_lightRef;
	EnvLight ub_lightSources[1024];
};

//lights
struct Light
{
	int   count;
	vec4  diffuse[10];
	float intensity[10];
	float angle[10];
	float exponent[10];
	vec3  attenuation[10];
	vec3  position[10];
	vec3  direction[10];
};
uniform Light lights;
uniform float ambientLightIntensity;

uniform sampler2D colorMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;
uniform sampler2D materialInfoMap;
uniform sampler2D diffuseMap;
uniform sampler2D specularMap;

uniform sampler2DShadow shadowMap[10];

uniform mat4 viewMatrixInv;
uniform mat4 lightViewMatrix[10];
uniform mat4 lightProjMatrix[10];
uniform mat4 scaleBiasMatrix;

uniform vec2 planes;
uniform float depthOffset;

smooth in vec2 VS_TexCoord;
smooth in vec3 VS_ViewVector;

layout(location = 0) out vec4 colorOut;

vec3 spheremapDec(in vec2 enc)
{
  vec3 ret = vec3(enc * 2.0f, 0.0f) + vec3(-1.0f, -1.0f, 1.0f);
  float l = dot(ret.xy, -ret.xy) + 1.0f;
  ret.z = l;
  ret.xy *= sqrt(l);
  return ret.xyz * 2.0f + vec3(0.0f, 0.0f, -1.0f);
}

//in i - light num
//in eyePos - fragment position in eye space
void spotLight(inout vec3 L, inout float attenuation, in int i, in vec3 eyePos)
{
	attenuation = 0.0;
	L = normalize(lights.position[i] - eyePos);

	float spotEffect = dot(normalize(lights.direction[i]), -L);
	if (spotEffect > cos(lights.angle[i]))
	{	
		spotEffect = pow(spotEffect, lights.exponent[i]);

		//attentuation
		float dist = length(lights.position[i] - eyePos);   //distance from vertex to light
		attenuation = spotEffect / (lights.attenuation[i].x + lights.attenuation[i].y * dist + lights.attenuation[i].z * dist * dist);
	}
}

void main()
{
	//material info from g-buffer
	vec4 materialInfo = texture(materialInfoMap, VS_TexCoord);
	
	switch(int(materialInfo.x*10.1))
	{
	case 1:		//don't render reflective geometry
		discard;	
	case 2:     //phong
	{
		//view vector
		vec3 viewVector = normalize(VS_ViewVector);

		//depth
		float depth = texture(depthMap, VS_TexCoord).x;

		//normal
		vec3 N = spheremapDec(texture(normalMap, VS_TexCoord).xy);
		float len = length(N);

		//diffuse and specular colors 
		vec4 diffuse  = texture(diffuseMap, VS_TexCoord);
		vec4 specular = texture(specularMap, VS_TexCoord);

		//position in eye space
		vec3 eyePos;
		eyePos.z = -planes.y/(planes.x+depth);
		eyePos.xy = viewVector.xy/viewVector.z*eyePos.z;

		//compute lighting
		colorOut = vec4(0.0, 0.0, 0.0, 1.0);
		for(int i = 0; i < lights.count; ++i)
		{
			vec3 L;
			float attenuation;
			bool pointLight = false;
			bool envLight = false;

			if(lights.intensity[i] == 0) { //environment light
				envLight = true;
			    L = normalize(lights.position[i] - eyePos);			//light vector
				L = normalize(-lights.direction[i]);
			}
			else if(length(lights.direction[i]) == 0.0)
			{
				//PointLight
			    L = normalize(lights.position[i] - eyePos);			//light vector
				float dist = length(lights.position[i] - eyePos);   //distance from vertex to light
				attenuation = 1.0 / (lights.attenuation[i].x + lights.attenuation[i].y * dist + lights.attenuation[i].z * dist * dist);

				pointLight = true;
			}
		    else if(lights.angle[i] == 0.0)
			{
				//DirectionalLight
				L = normalize(-lights.direction[i]);
				attenuation = 1.0;
			}
		    else
			{
			    //SpotLight
				spotLight(L, attenuation, i, eyePos);
			}

			vec3 E = normalize(-eyePos);                    //viewpoint
			vec3 R = normalize(-reflect(L,N));              //reflected light ray

			float NdotL = max(dot(N,L), 0.0);
			float RdotE = max(dot(R,E), 0.0);

			vec4 Id = diffuse * NdotL;
			vec4 Is = specular * pow(RdotE, materialInfo.y*255);

			float shadowMapVal = 1.0;
			if(!pointLight)   //shadows for point lights are not supported yet
			{
				//fragment position in world space
				eyePos += L * depthOffset;		//set offset in eye space
				vec4 smPos = viewMatrixInv * vec4(eyePos, 1.0);

				//tranform to shadow map
				smPos = scaleBiasMatrix * lightProjMatrix[i] * lightViewMatrix[i] * smPos;
				smPos /= smPos.w;

				//get value from shadow map
				shadowMapVal = texture(shadowMap[i], smPos.xyz);
			}
			if(envLight) {
				//add environment lights
				int s = ub_size; //just to make sure it's used only once
				float ref = ub_lightRef <= 0 ? 100.f : float(ub_lightRef); //tmp, fixme
				EnvLight light;
				vec4 envColor = vec4(.0f, .0f, .0f, 1.0f);
				
				for(int j = 0; j < s; j++) {
					light = ub_lightSources[j];
				
					L = normalize(light.position - eyePos);
					vec3 Rv = normalize(-reflect(L,N));
				
					float NdL = max(dot(N,L), 0.0);
					float RdE = max(dot(Rv,E), 0.0);
				
					vec4 Id = diffuse * NdL;
					vec4 Is = specular * pow(RdE, materialInfo.y*255);
					envColor += (light.intensity / ref) * vec4(light.diffuse, 0.0) * (Id + Is);			
				}
				colorOut += shadowMapVal * envColor;
			} else {
				//set fragment color
				colorOut += attenuation * shadowMapVal * lights.intensity[i] * lights.diffuse[i] * (Id + Is);
			}
		}
		
		//add ambient color
		colorOut += ambientLightIntensity * diffuse;  //multiply diffuse material by ambient light
		break;
	}
	case 3:		//shader geometry
	{
		//view vector
		vec3 viewVector = normalize(VS_ViewVector);

		//depth
		float depth = texture(depthMap, VS_TexCoord).x;

		//position in eye space
		vec3 eyePos;
		eyePos.z = -planes.y/(planes.x+depth);
		eyePos.xy = viewVector.xy/viewVector.z*eyePos.z;

		vec4 colorIn = texture(colorMap, VS_TexCoord);
		colorOut = colorIn;
		for(int i = 0; i < lights.count; ++i)
		{
			//count diffuse part
			vec3 L;
			float attenuation;

			if(length(lights.direction[i]) == 0.0)
				//PointLight
				continue;
			else if(lights.angle[i] == 0.0)
			{
				//DirectionalLight
				L = normalize(-lights.direction[i]);
				attenuation = 1.0;
			}
			else
			{
				//SpotLight
				spotLight(L, attenuation, i, eyePos);
			}

			//position in world space
			eyePos += L * depthOffset;		//set offset in eye space
			vec4 smPos = viewMatrixInv * vec4(eyePos, 1.0);

			//tranform to shadow map
			smPos = scaleBiasMatrix * lightProjMatrix[i] * lightViewMatrix[i] * smPos;
			smPos /= smPos.w;
			smPos.z -= depthOffset;		//set offset

			//get shadow value
			float shadowMapVal = texture(shadowMap[i], smPos.xyz);

			vec3 N = spheremapDec(texture(normalMap, VS_TexCoord).xy);
			float NdotL = max(dot(N,L), 0.0);
			vec4 diffuse = attenuation * (lights.intensity[i] * lights.diffuse[i] * NdotL);

			//substract lighting
			colorOut -= (1-shadowMapVal) * diffuse * colorIn;
		}
		break;
	}
	default:
		discard; //discard pixels on background
	}
}
