#version 120

#define FXAA_SPAN_MAX	8.0
#define FXAA_REDUCE_MUL 1.0/8.0
#define FXAA_REDUCE_MIN 1.0/128.0

bool runFXAA = #{{bFXAA}};
// expose these as tick boxes, suggested defaults are set to true
// godrays from stars, scales with shader level setting
bool lightShaftsOn = #{{bGodRays}};
// bloom, scales with shader level setting
bool bloomOn = #{{bBloom}};
// darkend edges, brighten center on all zoom levels
bool vignetteOn = #{{bVignette}};
// sharpen function, on zoom out, to increase icon readability without and especially with bloom
bool sharpen = #{{bBloom}};
// "movie feel" radial color fringe effect, only up close in systems
bool chromaticAberrationOn = #{{bChromaticAberration}};
// "movie feel" grain everywhere
bool filmGrain = #{{bFilmGrain}};

//must be odds for bloom!
const int bloomPasses = #{{level:extreme}} ? 9 : (#{{level:high}} ? 7 : 5);
const int rayPasses = #{{level:extreme}} ? 24 : (#{{level:high}} ? 20 : 16);

const int nLightCount = 2;
vec3 light[nLightCount];
float dist[nLightCount];

const float pi = 3.14159265358;
const float tau = 6.28318530716;
uniform sampler2D screen, depthTex;
uniform vec2 texSize;
uniform vec2 lightPos[nLightCount];
uniform float lightActive[nLightCount]; // might need to implement a kill switch
uniform float lightRadius[nLightCount];
uniform float cycle;

varying vec2 aspectRatio;
varying vec2 uv;
varying vec4 lightVec[nLightCount];
varying vec2 radialAnimation[nLightCount];
varying vec4 camVec, camDirection[nLightCount];
varying vec3 lightColor[nLightCount];
varying vec4 pos;

varying float lightDepth[nLightCount], fallOffDepth[nLightCount];


// none of these needs to be exposed
const float sharpness = 0.25; // sharpen intensity
const float chromaticPower = 1.0; // chromatic fringe intensity
const float vignetteFactor = 0.5; // power of vignette on un-bloomed screen.
const float bloomTreshold = 0.25; // how low a value does blooming start
const float bloomIntensity = 1.5; // intensity of the blooming.
const float bloomScale = 4.0; // scale of bloom!
const float rayScale = 0.006;
const float rayIntensity = 0.75;
const float frameSize = 32.0; // fade power that reduces bloom near the edges to clean bad bloom from wrapping - less is more!
const float filmGrainIntensity = 2.0;
// random noise functions ahead
const vec4 hashSeed = vec4(.16532,.17369,.15787, .14987);

// sine stabilized rand for film grain
float rand( vec2 n ){
	return fract(sin(dot(n.xy, vec2(12.9898, 78.233)))* 43758.5453);
}

float hash12(vec2 p){
	vec3 p3  = fract(vec3(p.xyx) * hashSeed.xyz);
    p3 += dot(p3, p3.yzx + 19.19);
    return fract((p3.x + p3.y) * p3.z);
}

float noise(vec2 n){
    const vec2 d = vec2(0.0, 1.0);
    vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
    return mix(mix(hash12(b), hash12(b + d.yx), f.x), mix(hash12(b + d.xy), hash12(b + d.yy), f.x), f.y);
}

vec3 texture(vec2 uv){
    return texture2D(screen,uv).rgb;
}

vec3 square(vec3 x){
	return x*x;
}

float square(float x){
	return x*x;
}

float pow4(float x){
	x *= x;
	return x*x;
}

float linearFalloff(vec2 x){
	x *= x;
	return pow(x.x + x.y, 0.5);
}

float pow32(float x){

	x = x*x;
	x = x*x;
	x = x*x;
	x = x*x;
	x = x*x;
	return x;
}

vec3 toLinear(vec3 color){
	return pow(color, vec3(2.2));
}

vec4 toGamma(vec4 color){
	return pow(color, vec4(0.45));
}

vec4 toLinear(vec4 color){
	return pow(color, vec4(2.2));
}

float toLinear(float color){
	return pow(color, 2.2);
}

float toGamma(float color){
	return pow(color, 0.45);
}

vec3 toGamma(vec3 color){
	return pow(color, vec3(0.45));
}

float dotter(vec3 x){
	return dot(x,x);
}

float dotter(vec2 x){
	return dot(x,x);
}

float dotter(vec4 x){
	return dot(x,x);
}

float normpdf(float x, float sigma){
	return 0.39894*exp(-0.5 * x * x / (sigma * sigma)) / sigma;
}

vec3 vignette(vec3 color, float radialFade){
    return color * ((1.0 - (1.0 - radialFade) * vignetteFactor) + vignetteFactor * 0.5 * vec3(0.9));
}

vec3 redShift(float t){
    return 0.5 + 0.5 * cos( tau *(t+ vec3(0.0, 0.1, 0.2)) );
}

void main(){
	vec2 centeredUV = uv - 0.5;
	float frame = pow(abs(centeredUV.x)* 2.0, frameSize);
	frame = clamp(mix(pow(abs(centeredUV.y)* 2.0, frameSize), frame, frame) * 2.0 - 0.5, 0.0,1.0);
	float radialFade = 1.0 - min(1.0, dot(centeredUV, centeredUV) * 2.0);
	
	vec4 color = vec4(0.0);//texture2D(screen, uv);	
	vec3 sum = vec3(0.0);
	
	if(runFXAA == true){
		vec2 add = 1.0 / texSize.xy;
		
		vec3 rgbNW = texture(uv+vec2(-add.x, -add.y));
		vec3 rgbNE = texture(uv+vec2( add.x, -add.y));
		vec3 rgbSW = texture(uv+vec2(-add.x,  add.y));
		vec3 rgbSE = texture(uv+vec2( add.x,  add.y));
		vec3 rgbM  = texture(uv);
			
		vec3 luma	 = vec3(0.299, 0.587, 0.114);
		float lumaNW = dot(rgbNW, luma);
		float lumaNE = dot(rgbNE, luma);
		float lumaSW = dot(rgbSW, luma);
		float lumaSE = dot(rgbSE, luma);
		float lumaM  = dot(rgbM,  luma);
		
		float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
		float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
		
		vec2 dir;
		dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
		dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));
	
			float dirReduce = max(
			(lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
		
		float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
	
		dir = min(vec2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),
			max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
			dir * rcpDirMin)) * add;
	
			
		vec3 rgbA = 0.5 * (texture(uv + dir * (1.0/3.0 - 0.5)) +
								texture(uv + dir * 0.5));
		
		vec3 rgbB = rgbA * 0.5 + 0.25 *
			(texture(uv.xy + dir * (-0.5)) +
			texture(uv.xy + dir * (0.5)));
		
		float lumaB = dot(rgbB, luma);
		if((lumaB < lumaMin) || (lumaB > lumaMax))
		{
			color.rgb = rgbA;
		}else
		{
			color.rgb = rgbB;
		}
	}
	else
	{
		color = texture2D(screen, uv);		
	}
	
	vec3 linearColor = toLinear(color.rgb);
	float depth = texture2D(depthTex, uv).r;

	// calculate falloff as it's needed in multiple places
	float falloff[2] = float[2](float(0.0), float(0.0));	
	for (int i = 0; i < nLightCount; i++){
		falloff[i] = clamp((1.0 / fallOffDepth[i]) * (lightRadius[i] * 2.0), 0.0, 1.0);
	}
	
	// kill mask for effects either in system only or galactic zoom only
	float inSystemKill = square(1.0 - min(1.0, (falloff[0] + falloff[1]) * 10.0));
	
	if (sharpen == true){
  
		vec2 step = 1.0 / texSize.xy;
		
		vec3 sampA = toLinear(texture(uv + vec2(-step.x, -step.y) * 1.5 ));
		vec3 sampB = toLinear(texture(uv + vec2( step.x, -step.y) * 1.5 ));
		vec3 sampC = toLinear(texture(uv + vec2(-step.x,  step.y) * 1.5 ));
		vec3 sampD = toLinear(texture(uv + vec2( step.x,  step.y) * 1.5 ));
	
		vec3 around = 0.25 * (sampA + sampB + sampC + sampD);
		
		color.rgb = toGamma(linearColor + (linearColor - around) * mix(0.0, sharpness, inSystemKill));
	}
	
	if (chromaticAberrationOn == true)
	{
		vec3 refractiveIndex = 1.0 + vec3(0.002, 0.004, 0.006) * mix(chromaticPower, 0.0, inSystemKill) * min(1.0, square((1.0 - depth) * 1024));
		vec3 texVec = vec3(uv * 2.0 - 1.0, 1.0);
		vec3 normalVec = vec3(0.0, 0.0, -1.0);
		vec3 redRefractionVec = refract(texVec, normalVec, refractiveIndex.r);
		vec3 greenRefractionVec = refract(texVec, normalVec, refractiveIndex.g);
		vec3 blueRefractionVec = refract(texVec, normalVec, refractiveIndex.b);
		vec2 redTexCoord = clamp(((redRefractionVec / redRefractionVec.z).xy + vec2(1.0)) / vec2(2.0), vec2(0.0), vec2(1.0));
		vec2 greenTexCoord = clamp(((greenRefractionVec / greenRefractionVec.z).xy + vec2(1.0)) / vec2(2.0), vec2(0.0), vec2(1.0));
		vec2 blueTexCoord = clamp(((blueRefractionVec / blueRefractionVec.z).xy + vec2(1.0)) / vec2(2.0), vec2(0.0), vec2(1.0));

		color.rgb =
		mix(mix(vec3(
			texture2D(screen, redTexCoord).r, 
			texture2D(screen, greenTexCoord).g, 
			texture2D(screen, blueTexCoord).b)
		, color.rgb, radialFade), color.rgb, frame);
	}

	if (bloomOn == true){

		const int mSize = bloomPasses;
		const int kSize = (mSize-1)/2;

		float kernel[mSize];		
		float sigma = float(mSize);
		
		float divider = 0.0;
		for (int j = 0; j <= kSize; ++j)
		{
			kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
		}
		
		for (int j = 0; j < mSize; ++j)
		{
			divider += kernel[j];
		}
		
		for (int i=-kSize; i <= kSize; ++i)
		{
			for (int j=-kSize; j <= kSize; ++j)
			{
				sum += kernel[kSize+j] * kernel[kSize+i] * texture(uv +vec2(float(i),float(j)) / (texSize / (bloomScale)));	
			}		
		}
		
		sum /= square(divider);
		
		// kill bloom on the edges, where it will sample the other side from texture wrapping, and smoothstep
		sum = mix(sum * sum * (3.0 - 2.0 * sum), color.rgb, frame);
		// color correct and blend
		sum = toGamma((toLinear(color.rgb) + toLinear(max(vec3(0.0), sum * bloomIntensity - bloomTreshold) * 0.5)));
		color.rgb = sum;
	}
	
	if (lightShaftsOn == true){

		float rayValue = 0.0;

	for (int i = 0; i < nLightCount; i++) {
		int rayCount = rayPasses / (i + 1);
		rayValue = float(rayCount);
		// keep a raw non distorted samp for the noise to be filtereds
		float rayRaw = step(lightDepth[i], depth);
		
		// ray part
		float rays = (1.0 - rayRaw);
		float rayFalloff = rayScale;
		for (int j = 0; j < rayCount; j++) {
	
			float raySamp = step(lightDepth[i], texture2D(depthTex, uv + lightVec[i].xy * lightDepth[i] * rayFalloff).r);
			rays += (1.0 - raySamp) * (float(rayCount - j) / rayValue);
			rayFalloff += rayScale;
		
		}
		rays /= rayValue;
		rays *= rayIntensity;
		
		// radial noise part
		vec2 radialPointCoords = uv - lightPos[i].xy;
		vec2 radialNoiseCoords = vec2(128.0 * abs(vec2(atan(radialPointCoords.x, radialPointCoords.y), atan(radialPointCoords.x + radialPointCoords.y, -1.0 * radialPointCoords.x + radialPointCoords.y)) / tau));	
	
		float radialNoise = noise(radialNoiseCoords + camDirection[i].xy * 8.0 + radialAnimation[i] * 96.0) * rayRaw;
		
		float positionFade = square(clamp(lightVec[i].z, 0.0, 1.0));
		radialNoise *= 0.25 * falloff[i];
		rays = (1.0 - rays) * falloff[i];
		vec3 rayColor = (redShift(linearFalloff(radialPointCoords * aspectRatio) + lightColor[i].b + 0.35 + radialNoise) * 0.25 + 0.75) * lightColor[i];
		color.rgb += rays * rayColor * (1.0 + radialNoise) * positionFade;
		}
	}
	
	if (vignetteOn == true){
		color.rgb = vignette(color.rgb, radialFade);	
	}
	
	if (filmGrain == true){
		float t = fract(cycle);
		color.rgb += (rand(uv + 0.07 * t) + rand(uv + 0.11 * t) - 1.0) * 0.0078125 * filmGrainIntensity;
	}

	gl_FragColor = vec4(abs(color.rgb), 1.0);
}