#pragma once

#include "ofMain.h"
#include "ofxFXObject.h"

class ofxFrontSide : public ofxFXObject {
public:
    
    ofxFrontSide(){
        passes = 1;
        internalFormat = GL_RGBA32F;
        fragmentShader = STRINGIFY(
                                   uniform float     time;           // shader playback time (in seconds)
                                   uniform vec3      mouse;
                                   uniform vec2      resolution;           // viewport resolution (in pixels)
								   uniform sampler2D tex0;          // Buf from backside
								   uniform sampler2D tex1;          // BufA

								   uniform float s1;
								   uniform float s2;
								   uniform float s3;
								   uniform float s4;
								   uniform float s5;
								   uniform float s6;
								   uniform float s7;
								   uniform float s8;
								   uniform float s9;


																	// based on gltracy's marching examples
								   float ttime = 0.0;
																	// ray marching
								   const int max_iterations = 1;
								   float stop_threshold = 0.001;
								   float grad_step = 0.01;
								   const float clip_far = 1000.0;

								   float speed = 1.0;

								   // Optimized port and generalization of 
								   // http://learningwebgl.com/blog/?p=2858
								   float stride = 8.0; // Must be a multiple of 4
								   bool wiggle = true;


								   // math
								   const float PI = 3.14159265359;
								   const float DEG_TO_RAD = PI / 180.0;

								   // distance function
								   float dist_sphere(vec3 pos, float r) {
									   return length(pos) - r;
								   }

								   float dist_box(vec3 pos, vec3 size) {
									   return length(max(abs(pos) - size, 0.0));
								   }

								   int mode = 0;

								   float repeat(vec3 pos) {
									   pos.x = mod(pos.x, 1.) - 0.5;
									   pos.y = mod(pos.y, 1.) - 0.5;
									   pos.z = mod(pos.z, 1.) - 0.5;
									   if (mode == 0)
										   return -dist_sphere(pos, 0.5);
									   else
										   return dist_sphere(pos, 0.5);
								   }

								   // get distance in the world
								   float dist_field(vec3 pos) {
									   // ...add objects here...

									   // object 0 : sphere
									   float d0 = 0.0;

									   // object 1 : cube
									   float d1 = dist_box(pos, vec3(ttime*10.));

									   float d2 = repeat(pos);

									   // union     : min( d0,  d1 )
									   // intersect : max( d0,  d1 )
									   // subtract  : max( d1, -d0 )
									   return max(d2, min(d1, -d0));
								   }

								   // phong shading
								   vec3 shading(vec3 v, vec3 n, vec3 eye) {
									   // ...add lights here...

									   float shininess = 2.8;

									   vec3 final = vec3(0.0);

									   vec3 ev = normalize(v - eye);
									   vec3 ref_ev = reflect(ev, n);

									   // light 0
									   {
										   vec3 light_pos = vec3(cos(ttime*0.1)*100., 0.0, -1.0 + v.z*0.0001);
										   vec3 light_color = vec3(n.z*0.4, n.z*0.49, n.z*0.5);

										   vec3 vl = normalize(light_pos - v);

										   float diffuse = max(-1.0 + n.z*0.8, dot(vl, n))*0.6;
										   float specular = max(0.0, dot(vl, ref_ev))*0.8;
										   specular = pow(specular, shininess);

										   final += light_color * (diffuse + specular);
									   }

									   // light 1
									   {
										   vec3 light_pos = vec3(100. - abs(cos(ttime*0.1)*200.), 50. + abs(cos(ttime*1.)*180.), 10.0);
										   vec3 light_color = vec3(0.0, 0.0, 0.0);

										   vec3 vl = normalize(light_pos - v);

										   float diffuse = max(-1., dot(vl, n));
										   float specular = max(-10.5 + sin(n.z*0.1*(ttime*ttime*1.))*25., dot(vl, ref_ev));
										   specular = pow(specular, shininess);

										   final += light_color * (diffuse + specular);
									   }

									   return final;
								   }

								   // get gradient in the world
								   vec3 gradient(vec3 pos) {
									   vec3 dx = vec3(grad_step, 0.0, 0.0);
									   vec3 dy = vec3(0.0, grad_step, 0.0);
									   vec3 dz = vec3(0.0, 0.0, grad_step);
									   return normalize(
										   vec3(
											   dist_field(pos + dx) + dist_field(pos - dx)*pos.x*0.1,
											   dist_field(pos + dy) + dist_field(pos - dy)*pos.y*0.01,
											   dist_field(pos + dz) + dist_field(pos - dz)*pos.z*0.5
										   )
									   );
								   }

								   // ray marching
								   float ray_marching(vec3 origin, vec3 dir, float start, float end) {
									   float depth = start;
									   for (int i = 0; i < max_iterations; i++) {
										   float dist = dist_field(origin + dir * depth)*ttime*0.00035;
										   if (dist < stop_threshold) {
											   return depth;
										   }
										   depth += dist;
										   if (depth >= end) {
											   return end;
										   }
									   }
									   return end;
								   }

								   // get ray direction
								   vec3 ray_dir(float fov, vec2 size, vec2 pos) {
									   vec2 xy = pos - size * 0.5;

									   float cot_half_fov = tan((150.0 - fov * 0.5) * DEG_TO_RAD);
									   float z = size.y * 0.5 * cot_half_fov;

									   return normalize(vec3(xy, -z));
								   }

								   // camera rotation : pitch, yaw
								   mat3 rotationXY(vec2 angle) {
									   vec2 c = cos(angle);
									   vec2 s = sin(angle);

									   return mat3(
										   c.y, 0.0, -s.y,
										   s.y * s.x, c.x, c.y * s.x,
										   s.y * c.x, -s.x, c.y * c.x
									   );
								   }

								   vec3 hash3(float n) { return fract(sin(vec3(n, n + 1.0, n + 2.0))*43758.5453123); }


								   void main()
								   {

									   if (time < 82.) {
										   ttime = time * 0.1;
									   }
									   else {
										   ttime = (time - 82.) * 0.1;

									   }



									   vec2 uv = gl_FragCoord.xy / resolution.xy;
									   //    if (uv.x < 0.5) uv.x = 1.0-uv.x;

									   // default ray dir
									   vec3 dir = ray_dir(ttime*0.001, resolution.xy, uv*resolution.xy);

									   // default ray origin
									   vec3 eye = vec3(float(s1)*0.001, float(s2)*0.001, float(s3)*0.001);

									   // rotate camera
									   mat3 rot = rotationXY(vec2(float(s5)*0.001, float(s6)*0.001));
									   dir = rot * dir;
									   eye = rot * eye;

									   // ray marching
									   float depth = ray_marching(eye, dir, float(s7)*0.001, clip_far);
									   if (depth >= clip_far) {
										   gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
										   return;
									   }

									   // shading
									   vec3 pos = eye + dir * depth;
									   mode = 0;
									   vec3 n = vec3(0.0);
									   vec4 oc1 = vec4(0.0);
									   vec4 oc2 = vec4(0.0);
									   mode = 1;
									   vec3 n2 = vec3(0.0);
									   vec4 oc3 = vec4(0.0);
									   vec4 oc4 = vec4(0.0);

									   mode = 1;
									   n = gradient(pos);
									   oc1 = vec4(shading(pos*float(s8)*0.001, n*0.61, eye), 1.0);
									   oc1 *= 0.2 + 0.8*pow(26.0*uv.x*uv.y*(3.0 - uv.x)*(1.05 - uv.y), 0.55);
									   oc2 = vec4(shading(pos + vec3(0., 0., 0.), n*float(s9)*0.001, eye), 1.0)*1.2;
									   oc1 = oc1 + ((oc1 + oc2*0.01)*-abs(cos(ttime*0.3 + n.z*90.1)*n.z*0.1));
									   oc1 = clamp(oc1, vec4(0.0), vec4(1.0))*3.4 + pos.z*0.00005;

									   mode = 0;
									   n2 = gradient(pos);
									   oc3 = vec4(shading(pos, n2*0.8, eye), 1.0);
									   oc3 *= 0.2 + 0.8*pow(26.0*uv.x*uv.y*(3.0 - uv.x)*(1.05 - uv.y), 0.55);
									   oc4 = vec4(shading(pos + vec3(0., 0., 0.), n2*float(s9)*0.01, eye), 1.0)*1.2;

									   oc3 = oc3 + ((oc3 + oc4*0.01)*-abs(cos(ttime*0.3 + n2.z*500.1)*n2.z*.1));
									   oc3 = clamp(oc3, vec4(0.0), vec4(1.0))*2.0;

									   float v = dot(oc1.rgb + oc3.rgb, oc1.rgb + oc3.rgb)*n2.z*0.1;
									   vec2 p = gl_FragCoord.xy;
									   if (wiggle) p.x += floor(sin(p.x * 0.08) * 2.0 + sin(p.y * 0.01 + p.x * 0.07));
									   p = mod(p.xx + vec2(p.y, -p.y), vec2(stride));

									   vec4 sampleback = texture2D(tex0, uv);
									   vec4 samplebufa = texture2D(tex1, uv);

									   if (time < 72.) {

										   float val = abs(cos(time*0.48*0.2));

											  vec4 rev = sampleback - samplebufa*0.1;


											   vec4 fro = sampleback + ((oc1 + oc3) + 0.1*oc3.b*vec4(vec3(
												   float((v > 1.00) || (p.x != 0.0)) *
												   float((v > 0.70) || (p.y != 0.0)) *
												   float((v > 0.35) || (p.x != stride / 2.0)) *
												   float((v > 0.18) || (p.y != stride / 2.0)) *
												   float((v > 0.10) || (p.x != stride / 4.0)) *
												   float((v > 0.02) || (p.y != stride / 4.0))*(3. - pos.z*1.)), 1.0))*((samplebufa.rgb*0.1).r*p.x*0.1);

											   gl_FragColor = mix(rev, fro, val);

									   }
									   else if (time >= 72.) {

										   gl_FragColor = (sampleback*(1.0-(time - 72.)*0.05)) + ((oc1 + oc3) + 0.1*oc3.b*vec4(vec3(
											   float((v > 1.00) || (p.x != 0.0)) *
											   float((v > 0.70) || (p.y != 0.0)) *
											   float((v > 0.35) || (p.x != stride / 2.0)) *
											   float((v > 0.18) || (p.y != stride / 2.0)) *
											   float((v > 0.10) || (p.x != stride / 4.0)) *
											   float((v > 0.02) || (p.y != stride / 4.0))*(3. - pos.z*1.)), 1.0))*(cos(sampleback.b))*4.0;
									   }
								   }
								   );
    };
    void update(float time, float _s1, float _s2, float _s3, float _s4, float _s5, float _s6, float _s7, float _s8, float _s9 ){
        
        shader.setUniform1f("time", time);
        shader.setUniform2f("resolution", (float)width, (float)height);
        shader.setUniform2f("mouse", (float)ofGetMouseX()/width, (float)ofGetMouseY()/height);
		shader.setUniform1f("s1", _s1);
		shader.setUniform1f("s2", _s2);
		shader.setUniform1f("s3", _s3);
		shader.setUniform1f("s4", _s4);
		shader.setUniform1f("s5", _s5);
		shader.setUniform1f("s6", _s6);
		shader.setUniform1f("s7", _s7);
		shader.setUniform1f("s8", _s8);
		shader.setUniform1f("s9", _s9);
	};
    
};

