
#define LIGHTS
#ifndef NUM_LIGHTS
	#define NUM_LIGHTS 1
#endif

#define MAX_LOOPCOUNT 8.0

#define SPECULAR

#define ADAPTIVE_STEPS
//#define ADAPTIVE_STEPS_DIST

#define REFLECTION

#define FRESNEL
	#define FRESNEL_BIAS 0.1
	#define FRESNEL_POWER 1.5
	#define FRESNEL_SCALE 2.0

#define FOG

//#define SURFACETEX
	#define SURFACETEX_SCALE 16.0


//varying vec3 normal;
varying vec3 lightDir[NUM_LIGHTS];
varying vec3 eye;
varying float camdot;
varying vec3 pos;
varying vec3 tn, tt, tb;
varying vec4 glVertex;
varying vec3 v_m;

uniform sampler2D t_height;
uniform samplerCube skyCube;
//uniform sampler2D surfacetex;
uniform sampler2D seedTex;
uniform sampler3D surfacenoise;
uniform sampler2D extratex;
uniform float parallaxHeight;
uniform float texSize;// = 1024.0;
uniform float time;
uniform float znear;
uniform float zfar;

vec3 noiset(vec3 xyz) {
	return texture2D(seedTex, vec2(xyz.x+sin(xyz.z), xyz.y+cos(xyz.z))).rgb;
}
float noiseval(vec2 coord, float newTime, float loopCount) {
	const float octave = 30.0;
	coord *= octave;

	float scale = 0.0078125;
	float divider = 128.0;
	float mul = 128.0;
	float val = 0.0;

	while (loopCount > 0.0) {
		val += float(noiset(vec3(coord*scale, newTime*scale))*mul);
		scale *= 2.0;
		//newTime += 4.0;
		newTime *= 2.0;
		mul *= 0.5;
		divider += mul;
		loopCount -= 1.0;
	}

	return val/divider;
}

float noisenorm(vec2 coord, float newTime, float loopVal) {
	const float octave = 30.0;
	coord *= octave;

	float scale = 0.0078125;
	float divider = 128.0;
	float mul = 128.0;
	float val = 0.0;

/*	while (loopCount > 0.0) {
		val += noiset(vec3(coord*scale, newTime*scale))*mul;
		scale *= 2.0;
		mul *= 0.5;
		divider += mul;
		loopCount -= 1.0;
	}*/
	scale *= pow(2.0, loopVal);
	mul *= pow(0.5, loopVal);
	newTime *= pow(2.0, loopVal);
	divider += mul*loopVal;
	val = float(noiset(vec3(coord*scale, newTime*scale))*mul);

	return val/divider;
}


float getHeight(vec2 tc, float loopCount) {
	return (1.0-texture2D(extratex, tc).r) + noiseval(tc, time*0.05, loopCount);
//	return texture2D(t_height, tc).r;

	//float dist = length(pos)/zfar;
/*	dist = dist*dist;
	dist *= 32.0;*/
/*	float power = 1.0;
	float height = power*texture2D(t_height, texcoord).r;
	while (dist < 0.75) {
		texcoord *= vec2(4.0, 4.0);
		power *= 0.075;
		dist += 0.25;
		height += power*texture2D(t_height, texcoord).r;
	}
	return height;*/
/*	float height = 0.0;
	float power = 1.0;
	for (float i = 0.0; i < 4.0; i += 1.0) {
		height += power*texture2D(t_height, tc).r;
		tc *= vec2(4.0, 4.0);
		power = 0.1-i*0.025;
	}*/


/*	sampler3D noise = surfacenoise;
	vec2 xy = tc;
	float t = time;

	float height = 0.0;
	float power = 1.0;
	float bias = 0.0;
	for (float i = 0; i < loopCount; i += 1.0) {
		vec2 nx = vec2(xy.x+t*2.0, xy.y);
		vec2 ny = vec2(xy.x+t*1.0, xy.y);
		float h =
			(sin(texture3D(noise, vec3(nx*0.5,  (t*2.0+xy.x*0.212+xy.y*0.371)*0.5), bias).r))*0.75+
			(sin(texture3D(noise, vec3(ny*1.0,  (t*1.5+xy.x*0.241+xy.y*0.131)*1.0), bias).r))*0.250+
			texture3D(noise, vec3(xy*4.0,  (t*0.7+xy.x*0.151+xy.y*0.297)*4.0), bias).r*0.125+
			texture3D(noise, vec3(xy*16.0, (t*0.3+xy.x*0.197+xy.y*0.111)*16.0), bias).r*0.0625;
		height += power*h;
		xy *= vec2(4.0, 4.0);
		power = 0.01+i*0.01;
	}


	return clamp(height, 0.0, 1.0);*/
}


vec3 getBumpNormal(sampler2D height, vec2 xy, float loopCount) {
/*	float o = texture2D(height, xy).r;
	float h = texture2D(height, xy+vec2(1.0/texSize, 0.0)).r;
	float v = texture2D(height, xy+vec2(0.0, 1.0/texSize)).r;*/

	float o = getHeight(xy, loopCount);
	float h = getHeight(xy+vec2(1.0/texSize, 0.0), loopCount);
	float v = getHeight(xy+vec2(0.0, 1.0/texSize), loopCount);
	float dh = o-h;
	float dv = o-v;

/*	float o = getHeight(xy, loopCount);
	float h = getHeight(xy+vec2(1.0/texSize, 0.0), loopCount);
	float v = getHeight(xy+vec2(0.0, 1.0/texSize), loopCount);
	vec3 oh = vec3(1.0/texSize, (1.0/texSize)*(o-h), 0.0);
	vec3 ov = vec3(0.0,         (1.0/texSize)*(o-v), 1.0/texSize);
	return -normalize(cross(oh, ov));*/


/*	float lc = loopCount-fmod(loopCount, 1.0);
	float dh=0.0, dv=0.0;
	float mul = pow(0.5, lc);

	//float mul = 1.0;
	while (lc > 0.0) {
		float o = noisenorm(xy, time*0.05, lc);
		float h = noisenorm(xy+vec2(1.0/texSize, 0.0), time*0.05, lc);
		float v = noisenorm(xy+vec2(0.0, 1.0/texSize), time*0.05, lc);
		dh += mul*(o-h);
		dv += mul*(o-v);
		mul *= 2.0;
		lc -= 1.0;
	}*/

	//vec2 d2 = normalize(vec2(dh, dv));

/*	float x0 = getHeight(xy+vec2(-1.0/texSize, 0.0), loopCount);// texture2D(height, xy+vec2(-1.0/texSize, 0.0)).r;
	float x2 = getHeight(xy+vec2(1.0/texSize, 0.0), loopCount);// texture2D(height, xy+vec2( 1.0/texSize, 0.0)).r;
	float y0 = getHeight(xy+vec2(0.0, -1.0/texSize), loopCount);// texture2D(height, xy+vec2(0.0, -1.0/texSize)).r;
	float y2 = getHeight(xy+vec2(0.0, 1.0/texSize), loopCount);// texture2D(height, xy+vec2(0.0,  1.0/texSize)).r;
	float dh = x0-x2;
	float dv = y0-y2;*/

	//return normalize(vec3(dh, parallaxHeight*7.5, dv));
	return normalize(vec3(dh, parallaxHeight*4.0, dv));
}


void main() {
	vec3 e = normalize(eye);

	#ifdef FOG
		float maxZ = gl_Fog.end;
	#else
		float maxZ = zfar;
	#endif

//	float loopCount = min(1.0+pow(length(pos)/zfar, 2.0)*3.0, 4.0);
//	float loopCount = min(2.0+4.0-pow(length(pos)/zfar, 0.05), MAX_LOOPCOUNT);
//	float loopCount = min(1.0+((MAX_LOOPCOUNT-1.0)-pow(length(pos)/zfar, 0.01)*(MAX_LOOPCOUNT-1.0)), MAX_LOOPCOUNT);
//	float loopCount = clamp(pow(1.0-length(pos)/maxZ, 10.0)*MAX_LOOPCOUNT, 1.0, MAX_LOOPCOUNT);
	float loopCount = clamp(pow(1.0-(length(pos)/maxZ+0.25), 2.0)*(MAX_LOOPCOUNT*2.0), 1.0, MAX_LOOPCOUNT);

	#ifdef ADAPTIVE_STEPS
		float lod = 2.0+70.0*camdot;
/*	#elif defined ADAPTIVE_STEPS_DIST
		float lod = 30.0+70.0*camdot;
		float dist = 1.0-length(pos)/zfar;
		lod *= dist*dist*dist;
		lod += 5.0;*/
	#else
		float lod = 5.0;
	#endif

	vec2 texcoord = gl_TexCoord[0].xy;
	// get normal and initial height
	float height = texture2D(t_height, texcoord).r;
//	float height = getHeight(texcoord, loopCount);

	#ifdef SURFACETEX
		//vec2 texcoord2 = texcoord*SURFACETEX_SCALE;
	#endif

	// steep
	float numSteps = lod;
//	numSteps = mix(numSteps*2.0, numSteps, e.z);

	float fogEff = length(pos)/gl_Fog.end*2.0;
	fogEff = clamp(pow(fogEff, 2.0), 0.0, 1.0);
	numSteps = mix(numSteps, 1.0, fogEff);

	float step = 1.0/numSteps;
	vec2 delta = vec2(-e.x,e.z)*parallaxHeight/(e.y*numSteps);
	#ifdef SURFACETEX
		//vec2 delta_surface = delta*SURFACETEX_SCALE;
	#endif
	float bheight = 1.0;
	int counter = 0;
	while (height < bheight && counter < 100) {
		bheight -= step;
		texcoord += delta;
		height = texture2D(t_height, texcoord).r;
//		height = getHeight(texcoord, loopCount);
		#ifdef SURFACETEX
			//texcoord2 += delta_surface;
		#endif
		counter++;
	}

	// get surface normal
	//vec3 n = getBumpNormal(t_height, texcoord, loopCount);
	vec3 n = getBumpNormal(t_height, texcoord, loopCount);

	// initialize some variable
	vec4 col = vec4(0.1, 0.15, 0.75, 1.0);
	vec4 final = vec4(0.0);
	vec4 fambient = vec4(0.15, 0.15, 0.25, 1.0);	// color below surface
//	vec4 fambient = vec4(0.0, 0.0, 0.0, 1.0);
	vec4 fdiff = vec4(0.0);
	vec4 fspec = vec4(0.0);

	// calculate lights
	#ifdef LIGHTS
		for (int i = 0; i < NUM_LIGHTS; i++) {
			vec3 l = normalize(lightDir[i]);

			vec3 puoli = normalize(e+l);

			float diff = dot(n, l);
			//fambient += gl_LightSource[i].ambient;

			/*if (diff > 0.0) */{
				fdiff += gl_LightSource[i].diffuse*diff;
				fspec += vec4(gl_LightSource[i].specular.xyz, 1.0)*pow(max(dot(puoli, n), 0.0), gl_LightSource[i].specular.w);
			}
		}
	#endif

	// reflection
	#ifdef REFLECTION
		vec3 r = normalize(reflect(-e, n));
		r = vec3(dot(r, vec3(tt.x, tn.x, tb.x)), dot(r, vec3(tt.y, tn.y, tb.y)), dot(r, vec3(tt.z, tn.z, tb.z)));
		r.z = -r.z;
		vec4 skyCol = textureCube(skyCube, r);
	#else
		vec4 skyCol = vec4(0.75, 0.75, 0.95, 1.0);
	#endif

	// fresnel
	#ifdef FRESNEL
		float fresnel = clamp(FRESNEL_BIAS+pow(1.0-dot(e, n), FRESNEL_POWER)*FRESNEL_SCALE, 0.0, 1.0);
		skyCol = mix(fambient, skyCol, fresnel);
	#endif

	vec4 finalCol = /*fambient*col*/ /*+ fdiff*col*/ skyCol;

	#ifdef LIGHTS
//		finalCol *= fambient*col + fdiff*col;
	#endif

	#ifdef SURFACETEX
/*		float spower = 0.100*texture3D(surfacenoise, vec3(texcoord2,       time+bheight*0.03)).r+
		               //0.125*texture3D(surfacenoise, vec3(texcoord2*0.5,   time+bheight*0.03)).g+
		               0.200*texture3D(surfacenoise, vec3(texcoord2*0.25,  time+bheight*0.03)).b+
		               0.600*texture3D(surfacenoise, vec3(texcoord2*0.125, time+bheight*0.03)).a;
		spower = pow(spower, 6.0);
		spower *= 4.0;

		// känni
		texcoord2 = normalize(refract(-e, n, 1.3)).xz;

		vec4 fcol = texture2D(surfacetex, texcoord2);
		finalCol = mix(finalCol, fcol, spower);*/
	#endif

	#ifdef SPECULAR
		finalCol += fspec;
	#endif


	vec3 p = pos;
	p.z -= normalize(p).z*(1.0-bheight)/-e.y*200.0;	// hattuvakio
	vec2 planes = vec2(-zfar/(zfar-znear), -zfar*znear/(zfar-znear));
	gl_FragDepth = ((planes.x*p.z+planes.y)/-p.z);

	// fog
	#ifdef FOG
		float fogFactor = clamp((gl_Fog.end-gl_FogFragCoord)*gl_Fog.scale, 0.0, 1.0);
		finalCol = mix(gl_Fog.color, finalCol, fogFactor);
	#endif

	if (texture2D(extratex, texcoord).r > 0.01) finalCol = pow(finalCol, 3.0);

	gl_FragColor = finalCol;
	//gl_FragColor = vec4(loopCount/MAX_LOOPCOUNT);
	//	gl_FragColor = finalCol + pow(height+0.05, 4.0)*0.75;
}
