var Glitch = function(){
    var size = TDEMO.MAIN.getRendererSize();
    PartBase.call(this, {camera : new THREE.OrthographicCamera( -size[0]/2, size[0]/2, size[1]/2, -size[1]/2, 0.01, 40 )});
    this.camera.position.z = 1;

    //POSTPROCESSING

    this.setBloom(0.5);

    this.shaderScene = new THREE.Scene();
    var aspect = this.width/this.height;
    this.shaderCamera = new THREE.PerspectiveCamera(50, aspect, this.near, this.far);

    this.shaderScene.add(this.shaderCamera);

    this.shader = this.initShader({
        resolution : new THREE.Vector2(this.width, this.height),
        time : 0,
        fov : this.shaderCamera.getEffectiveFOV(),
        fogColor : new THREE.Vector3(0.125, 0.125, 0.35),
        ro : this.shaderCamera.position,
        target: this.shaderCamera.getWorldDirection(),

        texture : TDEMO.TEXTURES.getTextures().valot
    });
    var geometry = new THREE.PlaneBufferGeometry(this.width, this.height);
    this.plane = new THREE.Mesh(geometry, this.shader);
    //this.plane.position.z = -1;

    this.scene.add(this.plane);
};

Glitch.prototype = PartBase.prototype.inheritance();

Glitch.prototype.animate = function(elapsedTime, delta){
    PartBase.prototype.animate.call(this, elapsedTime, delta, false);

    this.shader.uniforms.fov.value = this.shaderCamera.getEffectiveFOV();
    var hb = TDEMO.SYNC.getHeartBeat();
    this.shader.uniforms.time.value = hb;

    this.lens = TDEMO.SYNC.getLens();
    this.shaderCamera.filmGauge = this.lens.fg;
    this.shaderCamera.setFocalLength(this.lens.fl);

    //Animating camera position
    var camp = TDEMO.SYNC.getCameraPosition();
    this.shaderCamera.position.x = camp.x;
    this.shaderCamera.position.y = camp.y;
    this.shaderCamera.position.z =  camp.z;

    var camr = TDEMO.SYNC.getCameraRotation();
    this.shaderCamera.rotation.x = camr.x;
    this.shaderCamera.rotation.y = camr.y;
    this.shaderCamera.rotation.z =  camr.z;

    this.shaderCamera.updateProjectionMatrix();

    this.shader.uniforms.ro.value = this.shaderCamera.position;
    this.shader.uniforms.target.value = this.shaderCamera.getWorldDirection();
};

Glitch.prototype.initShader = function(uf){

    var uniforms = {
        resolution : {type: "v2", value: uf.resolution},
        time : {type: "f", value : uf.time},
        fov : {type : "f", value : uf.fov},
        fogColor : {type: "v3", value: uf.fogColor},

        ro : {type : "v3", value: uf.ro},
        target : {type : "v3", value: uf.target},

        texture:   { type: "t", value: uf.texture},
        noiseT: {type: "t", value: TDEMO.TEXTURES.getTextures().kansi}
    };

    var vertex = [
        "varying vec2 vUv;",
		"void main() {",
			"vUv = uv;",
			"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
		"}"
    ].join("\n");

    var fragment = [
        "#define STEPS 28",
        "#define FAR 40.0",

        //"#define saturate(x) clamp(x, 0, 1)",

        "uniform vec2 resolution;",
        "uniform float time;",
        "uniform float fov;",
        "uniform vec3 fogColor;",

        "uniform vec3 ro;",
        "uniform vec3 target;",

        "uniform sampler2D texture;",
        "uniform sampler2D noiseT;",

        "varying vec2 vUv;",

        //noise function from here: https://www.shadertoy.com/view/MsXGRf
        // Created by inigo quilez - iq/2013
        // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
        "float noise( in vec3 x ){",
            "vec3 p = floor(x);",
            "vec3 f = fract(x);",
            "f = f*f*(3.0-2.0*f);",
            "vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;",
            "vec2 rg = texture2D( noiseT, (uv+ 0.5)/256.0 ).yx;",
            "return mix( rg.x, rg.y, f.z );",
        "}",

        // Sign function that doesn't return 0
        "float sgn(float x) {",
        	"return (x<0.0)?-1.0:1.0;",
        "}",

        "vec2 sdf(vec3 p){",
            "float n = noise(p*time);",
            "return vec2((length(p) - 1.0)-n*0.5*time, 0.0);",
        "}",

        //calculate normals for objects
        "vec3 normals(vec3 p){",
            //vec3 eps = vec3(0.1/R.x, 0.0, 0.0 );
            "vec3 eps = vec3(0.5/resolution.x, 0.0, 0.0 );",
            "vec3 nor = vec3(",
                "sdf(p+eps.xyy).x - sdf(p-eps.xyy).x,",
                "sdf(p+eps.yxy).x - sdf(p-eps.yxy).x,",
                "sdf(p+eps.yyx).x - sdf(p-eps.yyx).x);",
            "return normalize(nor);",
        "}",

        //Enhanced sphere tracing algorithm introduced by Mercury
        "float march(in vec3 ro, in vec3 rd){",
            "vec3 p = vec3(0.);",
            "float t = .0001;",
            "float step = 0.;",

            "float omega = 1.7;",//muista testata eri arvoilla! [1,2]
            "float prev_radius = 0.;",

            "float candidate_t = t;",
            "float candidate_error = 1000.;",
            "float sg = sgn(sdf(ro).x);",

            //It's just so much better with static value!
            "float pixelr = .5/resolution.x;",

            "for(int i = 0; i < STEPS; ++i){",
                "p = rd*t+ro;",
                "float sg_radius = sg*sdf(p).x;",
                "float radius = abs(sg_radius);",

                "bool fail = omega > 1. && (radius+prev_radius) < step;",
                "if(fail){",
                    "step -= omega * step;",
                    "omega = 1.;",
                "}",
                "else{",
                    "step = sg_radius*omega;",
                "}",
                "prev_radius = radius;",
                "float error = radius/t;",

                "if(!fail && error < candidate_error){",
                    "candidate_t = t;",
                    "candidate_error = error;",
                "}",

                "if(!fail && error < pixelr || t > FAR){",
                    "break;",
                "}",
                "t += step;",
            "}",

            //discontinuity reduction
            "float er = candidate_error;",
            "for(int j = 0; j < 6; ++j){",
                "float radius = abs(sg*sdf(p).x);",
                "p = p + rd*(radius-er);",
                "t = length(p-ro);",
                "er = radius/t;",

                "if(er < candidate_error){",
                    "candidate_t = t;",
                    "candidate_error = er;",
                "}",
            "}",

            "if(t > FAR || candidate_error > pixelr){",
                "return FAR;",
            "}",
            "return candidate_t;",
        "}",

        "vec3 colorify(vec3 ld, vec3 p, vec3 dir, float id){",
            "vec3 n = normals(p);",
            "float nl = dot(n,ld);",
            "float intensity = saturate(nl);",

            "float angle = max(dot(reflect(ld, n), dir), 0.);",
            "float spec = pow(angle, 3.0);",

            "vec3 ambient = texture2D(texture, (vUv*n.xy+0.5+time*0.01)*0.2).yxz;",

            //ambient + lambertian*diffuse + spec*specular
            "vec3 color = ambient + intensity*fogColor + spec*fogColor;",

            "return saturate(color);",
        "}",

        "vec3 fog(vec3 col, vec3 p, vec3 o){",
            "float dist = length(p-o);",
            "float fogAmount = 1.0 - exp( -dist*.06);",
            "return mix(col, fogColor*texture2D(texture, vec2(vUv.x+time*0.1, vUv.y)).rgb, fogAmount);",
        "}",

        // Functio to set camera
        // o = camera origin
        // target = camera target (lookAt)
        // cr = right angle
        "mat3 setCamera(vec3 o, vec3 t, float cr ){",
        	"vec3 cw = normalize(t-o);",//z
        	"vec3 cp = vec3(sin(cr), cos(cr),0.0);",//up
        	"vec3 cu = normalize( cross(cw,cp) );",//x
        	"vec3 cv = normalize( cross(cu,cw) );",//y

            "return mat3( cu, cv, cw );",
        "}",

        "void main(){",
            "vec2 q = vUv;//gl_FragCoord.xy/resolution;",
            "vec2 v = -1.0+2.0*q;//screenspace coordinates -1..1",
            "v.x *= resolution.x/resolution.y;//aspect correction",

            "mat3 cam = setCamera(ro, target, 0.);",
        	"vec3 rd = normalize(cam*vec3(v.xy, radians(fov)));",


            "float t = march(ro, rd);",
            "vec3 p = rd*t+ro;",
            "float id = sdf(p).y;",

            "gl_FragColor = vec4(fogColor, 1.0);",

            "if(t < FAR){",
                //(target-origin)/magnitude == normalized light direction
                "vec3 ld = (ro-target)/distance(target, ro);",
                "gl_FragColor.xyz = colorify(ld, p, rd, id);",
            "}",
            "gl_FragColor.xyz = fog(gl_FragColor.xyz, p, ro);",
        "}",

    ].join("\n");

    var material = new THREE.ShaderMaterial( {
        uniforms : uniforms,
        vertexShader: vertex,
        fragmentShader: fragment,
    } );

    return material;
}
