varying vec3 v_positionSrc;
varying vec3 v_position;
varying vec3 v_normal;
varying vec3 v_tangent;

uniform int num_lights = 2;
uniform bool enable_texture = false;
uniform bool separate_specular = false;
uniform bool enable_doubleside = false;
uniform bool enable_lighting = true;
uniform bool enable_flatshade = false;
uniform bool enable_vcolor = false;

uniform sampler2D tex_color;

uniform int shadowmap_pixels = 2048;
uniform int shadow_light_idx = 0;
uniform bool enable_shadow = true;
uniform bool enable_softshadow = true;
uniform float color_shadow_ratio = 0.6;

uniform sampler2D shadow_buffer;

varying vec4 v_shadow_pos;

uniform bool enable_envmap = false;
uniform sampler2D tex_envmap;

uniform bool enable_matcap = false;
uniform sampler2D tex_matcap;
uniform float matcap_rate = 1.0;

uniform bool enable_normalmap = false;
uniform sampler2D tex_normal;

varying vec3 v_ref_view;
varying vec3 v_ref_normal;

uniform float env_reflection = 0.5;
uniform float fresnel_refact = 1.6;

uniform bool enable_hole = false;
uniform bool enable_hole_rev = false;
uniform vec3 hole_pos;
uniform float hole_range = 0.0;

uniform float const_brightness = 1.0;

float PI = 3.14159265358979323846264;


bool RequireFlipNormal(void)
{
	if (enable_flatshade)
		return false;
	if (!enable_doubleside)
		return false;
	if (gl_FrontFacing)
		return false;

	return true;
}


vec4 GetPhongDiffuse(float LN, int light_idx)
{
	if (enable_vcolor)
	{
		return gl_LightSource[light_idx].diffuse * gl_Color * LN;
	}
	else
	{
		return gl_FrontLightProduct[light_idx].diffuse * LN;
	}
}

vec4 GetPhongSpecular(vec3 N, vec3 L, vec3 V, int light_idx)
{
	vec3 H = normalize(L + V);
	float NH = dot(N, H);
	if (NH <= 0.0)
		return vec4(0.0, 0.0, 0.0, 0.0);

	float NHP = pow(NH, gl_FrontMaterial.shininess);

	return gl_FrontLightProduct[light_idx].specular * NHP;
}

float GetFresnelReflection()
{
	float fr = fresnel_refact;
	float c = dot(v_ref_normal, v_ref_view);
	if (RequireFlipNormal())
		c = -c;

	float g = sqrt(fr * fr + c * c - 1.0);
	float gpc = g + c;
	float gmc = g - c;
	float gpc1 = c * gpc - 1.0;
	float gmc1 = c * gmc + 1.0;
	float gc = gmc / gpc;
	float gc1 = gpc1 / gmc1;
	return 0.5 * gc * gc * (1.0 + gc1 * gc1);
}

float GetFresnelReflectionLimit()
{
	return clamp(GetFresnelReflection(), 0.0, 1.0);
}


float get_depth(vec4 uv)
{
	return texture2DProj( shadow_buffer , uv ).x;
}

vec4 get_shadow_uv(void)
{
	vec4 shadow_uv = v_shadow_pos / v_shadow_pos.w;
	shadow_uv += 1.0;
	shadow_uv *= 0.5;
	shadow_uv.w = 1.0;

	return shadow_uv;
}

int GetNumSoftshadowLaysAround8(void)
{
	float thre = -0.01;

	vec4 shadow_uv = get_shadow_uv();
	float frag_depth = shadow_uv.z;

	vec4 c = shadow_uv;
	float d = 1.0 / shadowmap_pixels;
	float s[9];
	s[0] = get_depth( c );
	s[1] = get_depth( vec4(c.x-d, c.y, c.z, c.w) );
	s[2] = get_depth( vec4(c.x+d, c.y, c.z, c.w) );
	s[3] = get_depth( vec4(c.x, c.y-d, c.z, c.w) );
	s[4] = get_depth( vec4(c.x, c.y+d, c.z, c.w) );
	s[5] = get_depth( vec4(c.x-d, c.y-d, c.z, c.w) );
	s[6] = get_depth( vec4(c.x+d, c.y-d, c.z, c.w) );
	s[7] = get_depth( vec4(c.x-d, c.y+d, c.z, c.w) );
	s[8] = get_depth( vec4(c.x+d, c.y+d, c.z, c.w) );

	int num_lay = 0;
	for (int i = 0; i < 9; ++i)
	{
		if ( (s[i] - frag_depth) >= thre)
			++num_lay;
	}

	return num_lay;
}

bool IsNotShadowed(void)
{
	float thre = -0.01;

	vec4 shadow_uv = get_shadow_uv();
	float frag_depth = shadow_uv.z;

	vec4 c = shadow_uv;
	float d = 1.0 / shadowmap_pixels;
	float s = get_depth( c );

	return ( ( s - frag_depth ) >= thre );
}

float GetLightInRatio(void)
{
	if (enable_softshadow)
	{
		int num_lay = GetNumSoftshadowLaysAround8();
		return (num_lay / 9.0);
	}
	else
	{
		if (IsNotShadowed())
			return 1.0;
		else
			return 0.0;
	}
}

vec4 GetAmbientAndEmission(void)
{
	vec4 ref_amb = gl_FrontMaterial.ambient * gl_LightModel.ambient;
	for (int i = 0; i < num_lights; ++i)
	{
		ref_amb += gl_FrontLightProduct[i].ambient;
	}

	return ref_amb + gl_FrontMaterial.emission;
}

vec4 SetLimitXYZ1(vec4 v)
{
	v.x = min(v.x, 1.0);
	v.y = min(v.y, 1.0);
	v.z = min(v.z, 1.0);

	return v;
}

vec3 GetFlatNormal(void)
{
	vec3 dx = dFdx(v_position);
	vec3 dy = dFdy(v_position);
	return normalize(cross(dx, dy));
}

vec3 GetFlagNormal(void)
{
	if (enable_flatshade)
		return GetFlatNormal();

	vec3 N;
	if (enable_normalmap)
	{
		vec3 t_normal = texture2DProj(tex_normal, gl_TexCoord[0]).xyz;
		t_normal *= 2.0;
		t_normal -= 1.0;
		t_normal = normalize(t_normal);

		vec3 VT = normalize(v_tangent);
		vec3 VN = normalize(v_normal);
		vec3 VB = normalize(cross(VN, VT));

		N = normalize(VT * t_normal.x + VB * t_normal.y + VN * t_normal.z);
	}
	else
	{
		N = normalize(v_normal);
	}

	return N;
}

vec3 GetDSNormal(vec3 N)
{
	if (RequireFlipNormal())
		return -N;
	else
		return N;
}

vec4 GetPhongReflectionSumWithShadow(vec3 N)
{
	vec4 ret_d = vec4(0.0, 0.0, 0.0, 0.0);
	vec4 ret_s = vec4(0.0, 0.0, 0.0, 0.0);
	for (int i = 0; i < num_lights; ++i)
	{
		vec3  light_xyz = gl_LightSource[i].position.xyz;
		float light_w   = gl_LightSource[i].position.w;
		vec3 L = normalize( light_xyz - v_position * light_w );
		vec3 V = normalize( -v_position );

		float LN = dot(L, N);
		if (LN <= 0.0)
			continue;

		float lr = 1.0;
		if (enable_shadow && i == shadow_light_idx)
			lr = GetLightInRatio();

		ret_d += GetPhongDiffuse(LN, i) * lr;
		ret_s += GetPhongSpecular(N, L, V, i) * lr;
	}

	vec4 ret_ae = GetAmbientAndEmission();

	vec4 ret;
	if (enable_texture)
	{
		vec4 t_color = texture2DProj( tex_color , gl_TexCoord[0] );

		if (separate_specular)
			ret = SetLimitXYZ1(ret_ae + ret_d) * t_color + ret_s;
		else
			ret = SetLimitXYZ1(ret_ae + ret_d + ret_s) * t_color;

		ret.w = t_color.w * gl_FrontMaterial.diffuse.w;

		return ret;
	}
	else
	{
		ret = ret_ae + ret_d + ret_s;
		ret.w = gl_FrontMaterial.diffuse.w;
		return SetLimitXYZ1(ret);
	}
}


vec4 GetEnvironmentReflection()
{
	vec3 R = normalize(v_ref_normal * 2.0 * dot(v_ref_normal, v_ref_view) - v_ref_view);
	//vec3 R = normalize(v_ref_normal);

	float ty = acos(R.y) / PI;

	float tx = 0.25 - atan(R.x, R.z) / (2.0 * PI);

	vec4 env_texcoord = vec4(tx, ty, 0.0, 1.0);
	vec4 ret = texture2DProj(tex_envmap, env_texcoord);
	ret *= env_reflection * GetFresnelReflectionLimit();

	ret.w = 0.0;
	return ret;
}


vec4 GetConstantColorWithShadow(void)
{
	vec4 ret = gl_Color * const_brightness;

	if(enable_texture)
		ret *= texture2DProj(tex_color, gl_TexCoord[0]);

	if(enable_shadow)
		ret.xyz *= (GetLightInRatio() * (1.0 - color_shadow_ratio) + color_shadow_ratio);

	return ret;
}


float NormToUV(float n)
{
	float t = 0.5 + n * 0.5;
	if (!gl_FrontFacing)
		t = 1.0 - t;

	return t;
}

vec4 GetMatcapColor(vec3 N)
{
	float tx = NormToUV(N.x);
	float ty = NormToUV(-N.y);

	vec4 env_texcoord = vec4(tx, ty, 0.0, 1.0);
	vec4 ret = texture2DProj(tex_matcap, env_texcoord);

	ret *= matcap_rate;

	ret.w = 0.0;
	return ret;
}


// 0 : default
// 1 : discarded by hole
// 2 : hole border
int getHoleState(void)
{
	if (!enable_hole && !enable_hole_rev)
		return 0;

	vec3 d = v_positionSrc - hole_pos;
	float ev = dot(d, d);
	if (enable_hole)
	{
		if (ev < hole_range * hole_range)
			return 1;
	}
	else
	{
		if (ev > hole_range * hole_range)
			return 1;
	}

	if (abs(sqrt(ev) - hole_range) < 0.01)
		return 2;

	return 0;
}

void main(void)
{
	int holeState = getHoleState();
	if (holeState == 1)
		discard;

	gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);

	vec3 N = GetFlagNormal();

	if (enable_lighting)
		gl_FragColor += GetPhongReflectionSumWithShadow(GetDSNormal(N));
	else
		gl_FragColor += GetConstantColorWithShadow();

	if (enable_envmap)
		gl_FragColor += GetEnvironmentReflection();

	if (enable_matcap)
		gl_FragColor += GetMatcapColor(N);

	if (holeState == 2)
	{
		if (gl_FragColor.x < gl_FragColor.z)
			gl_FragColor.x = 1.0 - (1.0 - gl_FragColor.x) * 0.2;
		else
			gl_FragColor.z = 1.0 - (1.0 - gl_FragColor.z) * 0.2;
	}
}
