/*
The MIT License (MIT)
Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
*/

if (typeof AFRAME === 'undefined') {
    throw new Error('Component attempted to register before AFRAME was available.');
}

AFRAME.registerComponent('avatar', {
    init: function () {
    },
    tick: function () {  
    }
})
       

AFRAME.registerComponent('desktop-controls', {
    init: function () {
        this.mouseMoveEvent();
    },
    mouseMoveEvent(){
    let self = this;
    this.avatar = document.querySelector('#avatarControl'); 
    this.cursor =  document.querySelector ('#mouse-' + vwf_view.kernel.moniker());
    this.domElement = this.el.sceneEl.renderer.domElement;

    this.raycaster = new THREE.Raycaster();
    this.mouse = new THREE.Vector3();
    this.handDirection = new THREE.Vector3();

    // this.plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 5); //this.plane = new THREE.Plane();
    // var helper = new THREE.PlaneHelper( this.plane, 1, 0xffff00 );
    // this.el.sceneEl.object3D.add( helper );
    
    //helper
    // this.arrayHelper = new THREE.ArrowHelper( this.raycaster.ray.direction, this.raycaster.ray.origin, 100, Math.random() * 0xffffff );
    // this.el.sceneEl.object3D.add(this.arrayHelper);

    let controllerID = 'mouse-' + vwf_view.kernel.moniker();

    this.domElement.addEventListener('mousedown', function (e) {
        
        if(!this.xrcontroller){
            this.xrcontroller = document.querySelector('#'+ controllerID);
        }

        if(this.xrcontroller) {
           
            let intersection = this.xrcontroller.components.raycaster.intersections[0];
            let point = intersection ? intersection.point : null;
            let elID = intersection ? intersection.object.el.id : null;
            if(point) {
                //console.log('Point to: ', point);
                
                self.avatar.setAttribute('look-controls', 'enabled', false)  
        }
    
            if (e.button == 1) {
                vwf_view.kernel.callMethod(controllerID, "triggerdown", [point, elID, controllerID]);
              }
    
              if (e.button == 0) {
                vwf_view.kernel.callMethod(controllerID, "mousedown", [point, elID, controllerID]);
              }  

        }
 
            
    });

    this.domElement.addEventListener('mouseup', function (e) {

        if(!this.xrcontroller){
            this.xrcontroller = document.querySelector('#'+ controllerID);
        }

        if(this.xrcontroller) {

            let intersection = this.xrcontroller.components.raycaster.intersections[0];
            let point = intersection ? intersection.point : null;
            let elID = intersection ? intersection.object.el.id : null;
            if(point) {
                //console.log('Point to: ', point);
            }

            if (!self.avatar.getAttribute('look-controls').enabled)
                self.avatar.setAttribute('look-controls', 'enabled', true)  


        if (e.button == 1) {
            vwf_view.kernel.callMethod(controllerID, "triggerup", [point, elID, controllerID]);
        }
        if (e.button == 0) {
            vwf_view.kernel.callMethod(controllerID, "mouseup", [point, elID, controllerID]);
          }   

        }
    });


      const onDocumentMouseMove = (event) => {

        event.preventDefault();

         if(this.camera){

            let rect = this.domElement.getBoundingClientRect();
        
            this.mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
            this.mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
    
            this.raycaster.setFromCamera( this.mouse, this.camera );
            this.handDirection.copy(this.raycaster.ray.direction);
            this.el.object3D.lookAt(this.handDirection.negate());

            if(!self.xrcontroller){
                self.xrcontroller = document.querySelector('#'+ controllerID);
            }

            if(self.xrcontroller){
                let intersection = this.xrcontroller.components.raycaster.intersections[0];
                let point = intersection ? intersection.point : null;
                let elID = intersection ? intersection.object.el.id : null;
                if(point) {
                //console.log('Point to: ', point, ' intersect ', elID);
                self.intersectionData = {
                    point: point,
                    elID: elID
                } 
            }
            else {
                self.intersectionData = null;
                
            }


            }
            
         }
            
    }
       
        this.domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );

    },
    tick: function () {  
        if(!this.camera){
            this.camera = document.querySelector('#avatarControl').getObject3D('camera');
        }
    }



})

AFRAME.registerComponent('scene-utils', {


    init: function () {
        this.mirrors = {};
        this.interpolationComponents = {};

        this.checkRenderer();
        //document.querySelector('a-scene').renderer.capabilities

        //this.setCameraControl();
        const sceneEnterVR = (e) => {

            let driver = vwf.views["/drivers/view/aframe"];

            // let avatarEl = document.querySelector('#avatarControlParent');
             let avatarID = 'avatar-' + vwf_view.kernel.moniker();

            driver.hmd = true;

            if (driver.threeDoFMobile || _app.config.d3DoF ) {
                driver.threeDoF = true;
                 //vwf_view.kernel.callMethod(avatarID, "updateYPositionForXR", [0.0]);
            } else if (driver.sixDoFMobile || driver.sixDoFDesktop || _app.config.d6DoF ) {
                driver.sixDoF = true;
            }

        }

        const sceneExitVR = (e) => {

            let driver = vwf.views["/drivers/view/aframe"];
            let avatarID = 'avatar-' + vwf_view.kernel.moniker();

            driver.hmd = false;
            
            if (driver.threeDoFMobile || _app.config.d3DoF ) {
                driver.threeDoF = false;
                //vwf_view.kernel.callMethod(avatarID, "updateYPositionForXR", [-1.6]);

            } else if (driver.sixDoFMobile || driver.sixDoFDesktop || _app.config.d6DoF ) {
                driver.sixDoF = false;
            }

        }

        this.el.sceneEl.addEventListener('enter-vr', sceneEnterVR);
        this.el.sceneEl.addEventListener('exit-vr', sceneExitVR);

    },

    setCameraControl(){

        let avatarEl = document.querySelector('#avatarControl');
        //avatarEl.setAttribute('look-controls', 'enabled', true)

        document.addEventListener('keydown', (event) => {
            const keyName = event.key;
          
            if (keyName === 'Alt') {
              // do not alert when only Control key is pressed.
              console.log(keyName, ' pressed');
              avatarEl.setAttribute('look-controls', 'enabled', true)
              return;
            }
        })
        
        document.addEventListener('keyup', (event) => {
            const keyName = event.key;
          
            if (keyName === 'Alt') {
              // do not alert when only Control key is pressed.
              console.log(keyName, ' released');
              avatarEl.setAttribute('look-controls', 'enabled', false)
              return;
            }
        })


    },

    checkRenderer: function(){

        if(this.el.sceneEl.renderer){
            //FIX For Safari WebGL1 renderer context
            let webgl2 = this.el.sceneEl.renderer.capabilities.isWebGL2;
            if(!webgl2){
                
                AFRAME.registerShader('msdf1', {
                    schema: {
                      alphaTest: {type: 'number', is: 'uniform', default: 0.5},
                      color: {type: 'color', is: 'uniform', default: 'white'},
                      map: {type: 'map', is: 'uniform'},
                      negate: {type: 'boolean', is: 'uniform', default: true},
                      opacity: {type: 'number', is: 'uniform', default: 1.0}
                    },
                  
                    raw: true,
                  
                    vertexShader: [
                        'attribute vec2 uv;',
                        'attribute vec3 position;',
                        'uniform mat4 projectionMatrix;',
                        'uniform mat4 modelViewMatrix;',
                        'varying vec2 vUV;',
                        'void main(void) {',
                        '  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
                        '  vUV = uv;',
                        '}'
                      ].join('\n'),
                    
                      fragmentShader: [
                        '#ifdef GL_OES_standard_derivatives',
                        '#extension GL_OES_standard_derivatives: enable',
                        '#endif',
                    
                        'precision highp float;',
                        'uniform bool negate;',
                        'uniform float alphaTest;',
                        'uniform float opacity;',
                        'uniform sampler2D map;',
                        'uniform vec3 color;',
                        'varying vec2 vUV;',
                    
                        'float median(float r, float g, float b) {',
                        '  return max(min(r, g), min(max(r, g), b));',
                        '}',
                    
                        // FIXME: Experimentally determined constants.
                        '#define BIG_ENOUGH 0.001',
                        '#define MODIFIED_ALPHATEST (0.02 * isBigEnough / BIG_ENOUGH)',
                    
                        'void main() {',
                        '  vec3 sampleColor = texture2D(map, vUV).rgb;',
                        '  if (negate) { sampleColor = 1.0 - sampleColor; }',
                    
                        '  float sigDist = median(sampleColor.r, sampleColor.g, sampleColor.b) - 0.5;',
                        '  float alpha = clamp(sigDist / fwidth(sigDist) + 0.5, 0.0, 1.0);',
                        '  float dscale = 0.353505;',
                        '  vec2 duv = dscale * (dFdx(vUV) + dFdy(vUV));',
                        '  float isBigEnough = max(abs(duv.x), abs(duv.y));',
                    
                        // When texel is too small, blend raw alpha value rather than supersampling.
                        // FIXME: Experimentally determined constant.
                        '  // Do modified alpha test.',
                        '  if (isBigEnough > BIG_ENOUGH) {',
                        '    float ratio = BIG_ENOUGH / isBigEnough;',
                        '    alpha = ratio * alpha + (1.0 - ratio) * (sigDist + 0.5);',
                        '  }',
                    
                        '  // Do modified alpha test.',
                        '  if (alpha < alphaTest * MODIFIED_ALPHATEST) { discard; return; }',
                        '  gl_FragColor = vec4(color.xyz, alpha * opacity);',
                        '}'
                      ].join('\n')
                  });

            }
            
        }
    },

    update: function () {

    },

     tick: function (t, dt) {

            Object.values(this.mirrors).forEach(el => {
                el.mirrorTick.call(el)
            });
            
            // Object.values(this.interpolationComponents).forEach(el => {
            //     el.interpolationTick.call(el, t, dt)
            // });

     },

     tock: function (t, dt) {

        // Object.values(this.interpolationComponents).forEach(el => {
        //     el.interpolationTock.call(el, t, dt)
        // });

     }
})


AFRAME.registerComponent('linepath', {
    schema: {
        color: { default: '#000' },
        transparent: { default: false },
        opacity: {default: 1 },
        width: { default: 0.01 },
        taper: { default: false },
        path: {
            default: [
                { x: -0.5, y: 0, z: 0 },
                { x: 0.5, y: 0, z: 0 }
            ]

            // Deserialize path in the form of comma-separated vec3s: `0 0 0, 1 1 1, 2 0 3`.
            // parse: function (value) {
            //   return value.split(',').map(coordinates.parse);
            // },

            // Serialize array of vec3s in case someone does setAttribute('line', 'path', [...]).
            // stringify: function (data) {
            //   return data.map(coordinates.stringify).join(',');
            // }
        }
    },

    update: function () {

       //TODO: parse array too

        var material = new MeshLine.MeshLineMaterial({
            color: new THREE.Color(this.data.color), //this.data.color
            lineWidth: this.data.width,
            transparent: this.data.transparent,
            opacity: this.data.opacity
        });

        let points = [];

         this.data.path.forEach(function (vec3) {
           points.push(
                new THREE.Vector3(vec3.x, vec3.y, vec3.z)
            );
        });

        // var geometry = new THREE.Geometry();
        // this.data.path.forEach(function (vec3) {
        //     geometry.vertices.push(
        //         new THREE.Vector3(vec3.x, vec3.y, vec3.z)
        //     );
        // });

        let line = new MeshLine.MeshLine();
        if(this.data.taper){
            line.setPoints(points, p => 1 - p); //p => 2 + Math.sin(50 * p) 
        } else {
            line.setPoints(points);
        }
        
        //line.setGeometry(geometry);

        //new THREE.Line(geometry, material)

        this.el.setObject3D('mesh', new THREE.Mesh(line, material)); //new THREE.Mesh(line.geometry, material)
    },

    remove: function () {
        this.el.removeObject3D('mesh');
    }
});


AFRAME.registerComponent('gizmo', {

    schema: {
        mode: { default: 'translate' }
    },

    update: function (old) {
        let modes = ['translate', 'rotate', 'scale'];
        if (!this.gizmo) {
            let newMode = modes.filter(el => {
                return el == this.data.mode
            })
            if (newMode.length !== 0) {
                this.mode = this.data.mode
                this.transformControls.setMode(this.mode)
            }
        }
    },

    init: function () {
        let self = this
        this.mode = this.data.mode
        let activeCamera = document.querySelector('#avatarControl').getObject3D('camera');
        let renderer = this.el.sceneEl.renderer;

        this.transformControls = new THREE.TransformControls(activeCamera, renderer.domElement);

        this.transformControls.attach(this.el.object3D);
        this.el.sceneEl.setObject3D('control-' + this.el.id, this.transformControls);

        this.transformControls.addEventListener('change', function (evt) {
            // console.log('changed');
            var object = self.transformControls.object;
            if (object === undefined) {
                return;
            }

            var transformMode = self.transformControls.getMode();
            switch (transformMode) {
                case 'translate':
                    vwf_view.kernel.callMethod(object.el.id, 'setPosition',
                        [[object.position.x, object.position.y, object.position.z]]);
                    // vwf_view.kernel.setProperty(object.el.id, 'position',
                    //     [object.position.x, object.position.y, object.position.z]);

                    break;
                case 'rotate':
                    // let q = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
                    //     (object.rotation.x),
                    //     (object.rotation.y),
                    //     (object.rotation.z), 'XYZ'
                    //   ));
                    // let angle = (new THREE.Euler()).setFromQuaternion(q, 'YXZ');

                    // vwf_view.kernel.setProperty(object.el.id, 'rotation', [THREE.Math.radToDeg(angle.x), THREE.Math.radToDeg(angle.y), THREE.Math.radToDeg(angle.z)])
                    vwf_view.kernel.setProperty(object.el.id, 'rotation',
                        [THREE.Math.radToDeg(object.rotation.x), THREE.Math.radToDeg(object.rotation.y), THREE.Math.radToDeg(object.rotation.z)])

                    break;
                case 'scale':
                    vwf_view.kernel.setProperty(object.el.id, 'scale',
                        [object.scale.x, object.scale.y, object.scale.z])

                    break;
            }

            //vwf_view.kernel.fireEvent(evt.detail.target.id, "clickEvent")
        });
    },

    remove: function () {
        this.transformControls.detach();
        this.el.sceneEl.removeObject3D('control-' + this.el.id);

    },

    // tick: function (t) {
    //    // this.transformControls.update();
    // }

});



AFRAME.registerComponent('cursor-listener', {
    init: function () {

        let self = this;

        this.el.addEventListener('click', function (evt) {
            console.log('I was clicked at: ', evt.detail.intersection.point);
            //let cursorID = 'cursor-avatar-' + vwf_view.kernel.moniker();
            if (evt.detail.cursorEl.id.includes(vwf_view.kernel.moniker())) {
                vwf_view.kernel.fireEvent(evt.detail.intersection.object.el.id, "clickEvent", [evt.detail.intersection.point]);
            }
            //vwf_view.kernel.fireEvent(evt.detail.target.id, "clickEvent")
        });

        // this.el.addEventListener('mousedown', function (evt) {
        //     console.log('mousedown at: ', evt.detail.intersection.point);
        //     if (evt.detail.cursorEl.id.includes(vwf_view.kernel.moniker())) {

        //         let point = evt.detail.intersection.point;
        //         // let locPoint = new THREE.Vector3();
        //         // locPoint.copy(evt.detail.intersection.point);
        //         // self.el.object3D.parent.worldToLocal(locPoint);
        //         // let point = AFRAME.utils.coordinates.stringify(locPoint);
        //         //vwf_view.kernel.callMethod('mouse-'+vwf_view.kernel.moniker(), "showHandSelection", [point]);
        //         vwf_view.kernel.fireEvent(evt.detail.intersection.object.el.id, "mousedownEvent", [point]);
                

        //     }

        // })

        // this.el.addEventListener('mouseup', function (evt) {
        //     let intersection =  evt.detail.intersection;
        //     if(intersection)
        //     {
        //         console.log('mouseup at: ', evt.detail.intersection.point);
        //         vwf_view.kernel.fireEvent(evt.detail.intersection.object.el.id, "mouseupEvent", [evt.detail.intersection.point]);
        //     } else {
        //         console.log('mouseup');
        //         //vwf_view.kernel.fireEvent(evt.detail.intersection.object.el.id, "mouseupEvent", []);
        //     }
        //     //vwf_view.kernel.callMethod('mouse-'+vwf_view.kernel.moniker(), "resetHandSelection", []);
             
        // })

    }
});

AFRAME.registerComponent('aabb-collider-listener', {

    // If the target collidable object is moving, set <a-entity data-aabb-collider-dynamic> on the target. By default, collidable objects are presumed to be static for performance purposes.

    init: function () {

        // let self = this;
        // this.me = vwf_view.kernel.moniker();
        this.el.addEventListener('hitstart', function (evt) {
            vwf_view.kernel.fireEvent(evt.target.id, "hitstartEvent");
        })

        this.el.addEventListener('hitend', function (evt) {
            vwf_view.kernel.fireEvent(evt.target.id, "hitendEvent");
        })
    }



});

AFRAME.registerComponent('raycaster-listener', {
    init: function () {

        let self = this;
        this.intersected = false;
        this.casters = {}
        this.me = vwf_view.kernel.moniker();
        this.driver = vwf.views["/drivers/view/aframe"];

        this.el.addEventListener('raycaster-intersected', function (evt) {

            if (evt.detail.el.nodeName == 'A-CURSOR') {
                //console.log('CURSOR was intersected at: ', evt.detail.intersection.point);

            } else {
                if (self.intersected) {

                } else {
                    let point = evt.detail.getIntersection(evt.target).point;
                    //console.log('I was intersected at: ', evt.target, ' point: ', point);//evt.detail.getIntersection().point);
                    vwf_view.kernel.fireEvent(evt.target.id, "intersectEvent", [point]);
                    //vwf.callMethod(evt.target.id, "intersectEventMethod", [point]);
                }

                self.casters[evt.target.id] = evt.target;
                self.intersected = true;
            }

        });

        this.el.addEventListener('raycaster-intersected-cleared', function (evt) {


            if (evt.detail.el.nodeName == 'A-CURSOR') {
                //console.log('CURSOR was intersected at: ', evt.detail.intersection.point);

            } else {
                if (self.intersected) {
                    //console.log('Clear intersection');
                    if (Object.entries(self.casters).length == 1 && (self.casters[evt.target.id] !== undefined)) {
                        vwf_view.kernel.fireEvent(evt.target.id, "clearIntersectEvent")
                        //vwf.callMethod(evt.target.id, "clearIntersectEventMethod", [])
                    }
                    delete self.casters[evt.target.id]
                } else { }

                self.intersected = false;
            }



        });


    }
});

AFRAME.registerComponent('envmap', {

    /**
     * Creates a new THREE.ShaderMaterial using the two shaders defined
     * in vertex.glsl and fragment.glsl.
     */
    init: function () {
        const data = this.data;

        //this.applyToMesh();
        this.el.addEventListener('model-loaded', () => this.applyToMesh());
    },
    /**
     * Update the ShaderMaterial when component data changes.
     */
    update: function () {

    },

    getEnvMap: function () {

        var path = './assets/textures/skybox2/';
        var format = '.jpg';
        var urls = [
            path + 'px' + format, path + 'nx' + format,
            path + 'py' + format, path + 'ny' + format,
            path + 'pz' + format, path + 'nz' + format
        ];

        envMap = new THREE.CubeTextureLoader().load(urls);
        envMap.format = THREE.RGBFormat;
        return envMap;

    },

    /**
     * Apply the material to the current entity.
     */
    applyToMesh: function () {

        const mesh = this.el.getObject3D('mesh');
        //var scene = mesh;
        var envMap = this.getEnvMap();


        mesh.traverse(function (node) {

            if (node.material) {

                node.material.side = THREE.BackSide;
                node.material.needsUpdate = true;
                //side = THREE.DoubleSide; break;

            }

        });

        mesh.traverse(function (node) {

            if (node.material && (node.material.isMeshStandardMaterial ||
                (node.material.isShaderMaterial && node.material.envMap !== undefined))) {

                node.material.envMap = envMap;
                node.material.needsUpdate = true;


            }

        });

        // const mesh = this.el.getObject3D('mesh');
        // if (mesh) {
        //   mesh.material = this.material;
        // }

    },
    /**
     * On each frame, update the 'time' uniform in the shaders.
     */
    // tick: function (t) {

    // }
})

//https://threejs.org/examples/webgl_shaders_sky.html

AFRAME.registerComponent('skyshader', {


    makeSun: function () {
        let sunSphere = new THREE.Mesh(
            new THREE.SphereBufferGeometry(20000, 16, 8),
            new THREE.MeshBasicMaterial({ color: 0xffffff })
        );
        sunSphere.position.y = - 700000;
        sunSphere.visible = true;

        let scene = this.el.sceneEl;
        this.el.sceneEl.setObject3D('sun', sunSphere);
    },

    init: function () {

        //let sunSphereEl = document.querySelector('a-scene').querySelector('#sun');
        //this.sunSphere = sunSphereEl.object3D;
        this.makeSun();
        this.sunSphere = this.el.sceneEl.getObject3D('sun');

        this.sky = new THREE.Sky();
        let scene = this.el.sceneEl;


        let effectController = {
            turbidity: 5,
            rayleigh: 2,
            mieCoefficient: 0.005,
            mieDirectionalG: 0.8,
            luminance: 1,
            inclination: 0, // elevation / inclination
            azimuth: 0.25, // Facing front,
            sun: ! true
        };

        let uniforms = this.sky.uniforms;
        uniforms.turbidity.value = effectController.turbidity;
        uniforms.rayleigh.value = effectController.rayleigh;
        uniforms.luminance.value = effectController.luminance;
        uniforms.mieCoefficient.value = effectController.mieCoefficient;
        uniforms.mieDirectionalG.value = effectController.mieDirectionalG;

        this.el.setObject3D('mesh', this.sky.mesh);

        let distance = 400000;

        var theta = Math.PI * (effectController.inclination - 0.5);
        var phi = 2 * Math.PI * (effectController.azimuth - 0.5);

        this.sunSphere.position.x = distance * Math.cos(phi);
        this.sunSphere.position.y = distance * Math.sin(phi) * Math.sin(theta);
        this.sunSphere.position.z = distance * Math.sin(phi) * Math.cos(theta);

        this.sunSphere.visible = effectController.sun;
        this.sky.uniforms.sunPosition.value.copy(this.sunSphere.position);


    },

    update: function () {

    },

    // tick: function (t) {

    // }
})

AFRAME.registerComponent('sun', {


    init: function () {

        this.sunSphere = new THREE.Mesh(
            new THREE.SphereBufferGeometry(20000, 16, 8),
            new THREE.MeshBasicMaterial({ color: 0xffffff })
        );
        this.sunSphere.position.y = - 700000;
        this.sunSphere.visible = true;

        this.el.setObject3D('mesh', this.sunSphere);

    },

    update: function () {
    },

    // tick: function (t) {
    // }
})

AFRAME.registerComponent('gearvrcontrol', {

    init: function () {
        var self = this;
        var controllerID = 'gearvr-' + vwf_view.kernel.moniker();

        this.el.addEventListener('triggerdown', function (event) {

            if(!self.xrcontroller){
                self.xrcontroller = document.querySelector('#'+ self.controllerID);
            }
    
    
            let intersection = self.xrcontroller.components.raycaster.intersections[0];
            let point = intersection ? intersection.point : null;
            let elID = intersection ? intersection.object.el.id : null;
            if(point) console.log('Point to: ', point);

            vwf_view.kernel.callMethod(controllerID, "triggerdown", [point, elID, self.controllerID]);
        });

        this.el.addEventListener('triggerup', function (event) {
            vwf_view.kernel.callMethod(controllerID, "triggerup", [point, elID, self.controllerID]);
        });

         //X-buttorn Pressed 
         this.el.addEventListener('buttondown', function (e) { //xbuttondown
            //Start pointing position to teleport  
           //let buttonID = e.detail.id;
           //let avatarID = 'avatar-' + vwf_view.kernel.moniker();
           //vwf_view.kernel.callMethod(vwf.application(), "createPrimitive", ['text', buttonID, 'debug', null, avatarID]);
           if(e.detail.id == 2){
            this.emit('teleportstart');
           }
            
        });

        //X-buttorn Released 
        this.el.addEventListener('buttonup', function (e) { //xbuttonup
            //Jump to pointed position
            if(e.detail.id == 2){
                this.emit('teleportend');
               }
            
        });

    },

    update: function () {
    },

    // tick: function (t) {
    // }
})

AFRAME.registerComponent('xrcontroller', {

    schema: {
        hand: { default: 'right' }
    },

    update: function (old) {
        this.hand = this.data.hand;
    },

    init: function () {
        var self = this;
        this.hand = this.data.hand;
        this.controllerID = 'xrcontroller-' + this.hand + '-' + vwf_view.kernel.moniker();

        this.el.addEventListener('triggerdown', function (event) { //pointdown 'triggerdown'

        if(!self.xrcontroller){
            self.xrcontroller = document.querySelector('#'+ self.controllerID);
        }


        let intersection = self.xrcontroller.components.raycaster.intersections[0];
        let point = intersection ? intersection.point : null;
        let elID = intersection ? intersection.object.el.id : null;
        if(point) console.log('Point to: ', point);

            vwf_view.kernel.callMethod(self.controllerID, "triggerdown", [point, elID, self.controllerID]);
            //this.emit('teleportstart');
        });
        this.el.addEventListener('triggerup', function (event) { //pointup 'triggerup'

        if(!self.xrcontroller){
            self.xrcontroller = document.querySelector('#'+ self.controllerID);
        }


        let intersection = self.xrcontroller.components.raycaster.intersections[0];
        let point = intersection ? intersection.point : null;
        let elID = intersection ? intersection.object.el.id : null;
        if(point) console.log('Point to: ', point);

            vwf_view.kernel.callMethod(self.controllerID, "triggerup", [point, elID, self.controllerID]);
            //this.emit('teleportend');
        });

         //X-buttorn Pressed 
         this.el.addEventListener('xbuttondown', function (e) { //xbuttondown
            //Start pointing position to teleport  
            console.log('TELEPORT START: ', e);
            this.emit('teleportstart');
        });

        //X-buttorn Released 
        this.el.addEventListener('xbuttonup', function (e) { //xbuttonup
            //Jump to pointed position
            console.log('TELEPORT END: ', e);
            this.emit('teleportend');
        });

        this.el.addEventListener('teleported', function (e) { //xbuttonup
            //Teleported
            console.log('TELEPORTED: ', e);

        });
    },

    // tick: function (t) {
    // }
})

AFRAME.registerComponent('wmrvrcontrol', {

    schema: {
        hand: { default: 'right' }
    },

    update: function (old) {
        this.hand = this.data.hand;
    },

    init: function () {
        var self = this;
        this.hand = this.data.hand;
        this.controllerID = 'wmrvr-' + this.hand + '-' + vwf_view.kernel.moniker();
        //this.gearel = document.querySelector('#gearvrcontrol');
        this.el.addEventListener('triggerdown', function (event) { //pointdown 'triggerdown'
            vwf_view.kernel.callMethod(self.controllerID, "triggerdown", []);
        });
        this.el.addEventListener('triggerup', function (event) { //pointup 'triggerup'
            vwf_view.kernel.callMethod(self.controllerID, "triggerup", []);
        });
    },

    // tick: function (t) {
    // }
})

AFRAME.registerComponent('streamsound', {

    schema: {
        positional: { default: true }
    },

    init: function () {
        var self = this;

        let driver = vwf.views["/drivers/view/webrtc"];

        this.listener = null;
        this.stream = null;

        if (!this.sound) {
            this.setupSound();
        }

        if (driver) {
            //let avatarID = 'avatar-' + vwf.moniker();
            let avatarID = this.el.id.slice(0, 27); //avatar-0RtnYBBTBU84OCNcAAFY
            let client = driver.state.clients[avatarID];
            if (client) {
                if (client.connection) {
                    this.stream = client.connection.stream;
                    if (this.stream) {
                        this.audioEl = new Audio();
                        this.audioEl.srcObject = this.stream;

                        this.sound.setNodeSource(this.sound.context.createMediaStreamSource(this.stream));
                    }
                }
            }
        }

    },

    setupSound: function () {
        var el = this.el;
        var sceneEl = el.sceneEl;

        if (this.sound) {
            el.removeObject3D(this.attrName);
        }

        if (!sceneEl.audioListener) {
            sceneEl.audioListener = new THREE.AudioListener();
            sceneEl.camera && sceneEl.camera.add(sceneEl.audioListener);
            sceneEl.addEventListener('camera-set-active', function (evt) {
                evt.detail.cameraEl.getObject3D('camera').add(sceneEl.audioListener);
            });
        }
        this.listener = sceneEl.audioListener;

        this.sound = this.data.positional
            ? new THREE.PositionalAudio(this.listener)
            : new THREE.Audio(this.listener);
        el.setObject3D(this.attrName, this.sound);
    },

    remove: function () {
        if (!this.sound) return;

        this.el.removeObject3D(this.attrName);
        if (this.stream) {
            this.sound.disconnect();
        }
    },

    update: function (old) {

    },

    // tick: function (t) {
    // }
})


AFRAME.registerComponent('viewoffset', {

    // fullWidth:
    // fullHeight:
    // xoffset:
    // yoffset:
    // width:
    // height:

    schema: {
        fullWidth: { default: window.innerWidth },
        fullHeight: { default: window.innerHeight },
        xoffset: { default: window.innerWidth / 2 },
        yoffset: { default: window.innerHeight / 2 },
        width: { default: window.innerWidth },
        height: { default: window.innerHeight }
    },


    init: function () {
        var self = this;
        this.el.sceneEl.addEventListener('loaded', setOffset);

        function setOffset() {
            this.setNewOffset();
        }

    },


    update: function (old) {
        this.fullWidth = this.data.fullWidth;
        this.fullHeight = this.data.fullHeight;
        this.xoffset = this.data.xoffset;
        this.yoffset = this.data.yoffset;
        this.width = this.data.width;
        this.height = this.data.height;
        //console.log(this.data);
        this.setNewOffset();
    },

    setNewOffset: function () {
        this.el.object3DMap.camera.setViewOffset(
            this.data.fullWidth,
            this.data.fullHeight,
            this.data.xoffset,
            this.data.yoffset,
            this.data.width,
            this.data.height)
    },

    // tick: function (t) {

    // }
})

AFRAME.registerComponent("virtual-gamepad-controls", {
    schema: {},
  
    init() {
      this.onEnterVr = this.onEnterVr.bind(this);
      this.onExitVr = this.onExitVr.bind(this);
      this.onFirstInteraction = this.onFirstInteraction.bind(this);
      this.onMoveJoystickChanged = this.onMoveJoystickChanged.bind(this);
      this.onMoveJoystickEnd = this.onMoveJoystickEnd.bind(this);
      // this.onLookJoystickChanged = this.onLookJoystickChanged.bind(this);
      // this.onLookJoystickEnd = this.onLookJoystickEnd.bind(this);
  
      this.mockJoystickContainer = document.createElement("div");
      this.mockJoystickContainer.classList.add('mockJoystickContainer');
      const leftMock = document.createElement("div");
      leftMock.classList.add('mockJoystick');
      const leftMockSmall = document.createElement("div");
      leftMockSmall.classList.add('mockJoystick', 'inner');
      leftMock.appendChild(leftMockSmall);
      this.mockJoystickContainer.appendChild(leftMock);
      // const rightMock = document.createElement("div");
      // rightMock.classList.add('mockJoystick');
      // const rightMockSmall = document.createElement("div");
      // rightMockSmall.classList.add('mockJoystick', 'inner');
      // rightMock.appendChild(rightMockSmall);
      // this.mockJoystickContainer.appendChild(rightMock);
      document.body.appendChild(this.mockJoystickContainer);
  
      // Setup gamepad elements
      const leftTouchZone = document.createElement("div");
      leftTouchZone.classList.add('touchZone', 'left');
      document.body.appendChild(leftTouchZone);
  
      this.leftTouchZone = leftTouchZone;
  
      this.leftStick = nipplejs.create({
        zone: this.leftTouchZone,
        color: "white",
        fadeTime: 0
      });
  
      this.leftStick.on("start", this.onFirstInteraction);
      this.leftStick.on("move", this.onMoveJoystickChanged);
      this.leftStick.on("end", this.onMoveJoystickEnd);
  
      // const rightTouchZone = document.createElement("div");
      // rightTouchZone.classList.add('touchZone', 'right');
      // document.body.appendChild(rightTouchZone);
  
      // this.rightTouchZone = rightTouchZone;
  
      // this.rightStick = nipplejs.create({
      //   zone: this.rightTouchZone,
      //   color: "white",
      //   fadeTime: 0
      // });
  
      // this.rightStick.on("start", this.onFirstInteraction);
      // this.rightStick.on("move", this.onLookJoystickChanged);
      // this.rightStick.on("end", this.onLookJoystickEnd);
  
      this.inVr = false;
      this.moving = false;
      this.rotating = false;
  
      this.moveEvent = {
        axis: [0, 0]
      };
  
      // this.rotateYEvent = {
      //   value: 0
      // };
      // this.rotateXEvent = {
      //   value: 0
      // };
  
      this.el.sceneEl.addEventListener("enter-vr", this.onEnterVr);
      this.el.sceneEl.addEventListener("exit-vr", this.onExitVr);
    },
  
    onFirstInteraction() {
      this.leftStick.off("start", this.onFirstInteraction);
      //this.rightStick.off("start", this.onFirstInteraction);
      document.body.removeChild(this.mockJoystickContainer);
    },
  
    onMoveJoystickChanged(event, joystick) {
      const angle = joystick.angle.radian;
      const force = joystick.force < 1 ? joystick.force : 1;
      const moveStrength = 1.85;
      const x = Math.cos(angle) * force * moveStrength;
      const z = Math.sin(angle) * force * moveStrength;
      this.moving = true;
      this.moveEvent.axis[0] = x;
      this.moveEvent.axis[1] = z;
    },
  
    onMoveJoystickEnd() {
      this.moving = false;
      this.moveEvent.axis[0] = 0;
      this.moveEvent.axis[1] = 0;
      this.el.emit("move", this.moveEvent);
    },
  
    onLookJoystickChanged(event, joystick) {
      // Set pitch and yaw angles on right stick move
      const angle = joystick.angle.radian;
      const force = joystick.force < 1 ? joystick.force : 1;
      const turnStrength = 0.5;
      this.rotating = true;
      this.rotateYEvent.value = Math.cos(angle) * force * turnStrength;
      this.rotateXEvent.value = Math.sin(angle) * force * turnStrength;
    },
  
    onLookJoystickEnd() {
      this.rotating = false;
      this.rotateYEvent.value = 0;
      this.rotateXEvent.value = 0;
      this.el.emit("rotateY", this.rotateYEvent);
      this.el.emit("rotateX", this.rotateXEvent);
    },
  
    tick() {
      if (!this.inVr) {
        if (this.moving) {
          this.el.emit("move", this.moveEvent);
        }
  
        // if (this.rotating) {
        //   this.el.emit("rotateY", this.rotateYEvent);
        //   this.el.emit("rotateX", this.rotateXEvent);
        // }
      }
    },
  
    onEnterVr() {
      // Hide the joystick controls
      this.inVr = true;
      this.leftTouchZone.style.display = "none";
      // this.rightTouchZone.style.display = "none";
    },
  
    onExitVr() {
      // Show the joystick controls
      this.inVr = false;
      this.leftTouchZone.style.display = "block";
      // this.rightTouchZone.style.display = "block";
    },
  
    remove() {
      this.el.sceneEl.removeEventListener("entervr", this.onEnterVr);
      this.el.sceneEl.removeEventListener("exitvr", this.onExitVr);
      if (document.getElementsByClassName('mockJoystickContainer').length > 0){
        document.body.removeChild(this.mockJoystickContainer);
      }
      document.body.removeChild(this.leftTouchZone);
      // document.body.removeChild(this.rightTouchZone);
    }
  });
  
////ARJS///

//////////////////////////////////////////////////////////////////////////////
//		arjs-anchor
//////////////////////////////////////////////////////////////////////////////
AFRAME.registerComponent('arjs-anchor', {
    dependencies: ['arjs', 'artoolkit'],
    schema: {
        preset: {
            type: 'string',
        },
        markerhelpers: {	// IIF preset === 'area'
            type: 'boolean',
            default: false,
        },

        // controls parameters
        size: {
            type: 'number',
            default: 1
        },
        type: {
            type: 'string',
        },
        patternUrl: {
            type: 'string',
        },
        barcodeValue: {
            type: 'number'
        },
        changeMatrixMode: {
            type: 'string',
            default: 'modelViewMatrix',
        },
        minConfidence: {
            type: 'number',
            default: 0.6,
        },
        smooth: {
            type: 'boolean',
            default: false,
        },
        smoothCount: {
            type: 'number',
            default: 5,
        },
        smoothTolerance: {
            type: 'number',
            default: 0.01,
        },
        smoothThreshold: {
            type: 'number',
            default: 2,
        },
    },
    init: function () {
        var _this = this

        // get arjsSystem
        var arjsSystem = this.el.sceneEl.systems.arjs || this.el.sceneEl.systems.artoolkit

        //////////////////////////////////////////////////////////////////////////////
        //		Code Separator
        //////////////////////////////////////////////////////////////////////////////

        _this.isReady = false
        _this._arAnchor = null

        //LiveCoding.space fix for editor mode
        if(arjsSystem) {

        // honor object visibility
        if (_this.data.changeMatrixMode === 'modelViewMatrix') {
            _this.el.object3D.visible = false
        } else if (_this.data.changeMatrixMode === 'cameraTransformMatrix') {
            _this.el.sceneEl.object3D.visible = false
        } else console.assert(false)

        // trick to wait until arjsSystem is isReady
        var startedAt = Date.now()
        var timerId = setInterval(function () {
            // wait until the system is isReady
            if (arjsSystem.isReady === false) return

            clearInterval(timerId)

            //////////////////////////////////////////////////////////////////////////////
            //		update arProfile
            //////////////////////////////////////////////////////////////////////////////
            var arProfile = arjsSystem._arProfile

            // arProfile.changeMatrixMode('modelViewMatrix')
            arProfile.changeMatrixMode(_this.data.changeMatrixMode)

            // honor this.data.preset
            var markerParameters = Object.assign({}, arProfile.defaultMarkerParameters)

            if (_this.data.preset === 'hiro') {
                markerParameters.type = 'pattern'
                markerParameters.patternUrl = THREEx.ArToolkitContext.baseURL + 'examples/marker-training/examples/pattern-files/pattern-hiro.patt'
                markerParameters.markersAreaEnabled = false
            } else if (_this.data.preset === 'kanji') {
                markerParameters.type = 'pattern'
                markerParameters.patternUrl = THREEx.ArToolkitContext.baseURL + 'examples/marker-training/examples/pattern-files/pattern-kanji.patt'
                markerParameters.markersAreaEnabled = false
            } else if (_this.data.preset === 'area') {
                markerParameters.type = 'barcode'
                markerParameters.barcodeValue = 1001
                markerParameters.markersAreaEnabled = true
            } else if (_this.data.type === 'barcode') {
                markerParameters = {
                    type: _this.data.type,
                    changeMatrixMode: 'modelViewMatrix',
                    barcodeValue: _this.data.barcodeValue,
                    markersAreaEnabled: false
                }
            } else if (_this.data.type === 'pattern') {
                markerParameters.type = _this.data.type
                markerParameters.patternUrl = _this.data.patternUrl;
                markerParameters.markersAreaEnabled = false
            }

            markerParameters.smooth = _this.data.smooth;
            markerParameters.smoothCount = _this.data.smoothCount;
            markerParameters.smoothTolerance = _this.data.smoothTolerance;
            markerParameters.smoothThreshold = _this.data.smoothThreshold;

            //////////////////////////////////////////////////////////////////////////////
            //		create arAnchor
            //////////////////////////////////////////////////////////////////////////////

            var arSession = arjsSystem._arSession
            var arAnchor = _this._arAnchor = new ARjs.Anchor(arSession, markerParameters)

            // it is now considered isReady
            _this.isReady = true

            //////////////////////////////////////////////////////////////////////////////
            //		honor .debugUIEnabled
            //////////////////////////////////////////////////////////////////////////////
            if (arjsSystem.data.debugUIEnabled) {
                // get or create containerElement
                var containerElement = document.querySelector('#arjsDebugUIContainer')
                if (containerElement === null) {
                    containerElement = document.createElement('div')
                    containerElement.id = 'arjsDebugUIContainer'
                    containerElement.setAttribute('style', 'position: fixed; bottom: 10px; width:100%; text-align: center; z-index: 1; color: grey;')
                    document.body.appendChild(containerElement)
                }
                // create anchorDebugUI
                var anchorDebugUI = new ARjs.AnchorDebugUI(arAnchor)
                containerElement.appendChild(anchorDebugUI.domElement)
            }
        }, 1000 / 60)
    }
    },
    remove: function () {
    },
    update: function () {
    },
    tick: function () {
        var _this = this
        // if not yet isReady, do nothing
        if (this.isReady === false) return

        //////////////////////////////////////////////////////////////////////////////
        //		update arAnchor
        //////////////////////////////////////////////////////////////////////////////
        var arjsSystem = this.el.sceneEl.systems.arjs || this.el.sceneEl.systems.artoolkit
        this._arAnchor.update()

        //////////////////////////////////////////////////////////////////////////////
        //		honor pose
        //////////////////////////////////////////////////////////////////////////////
        var arWorldRoot = this._arAnchor.object3d
        arWorldRoot.updateMatrixWorld(true)
        arWorldRoot.matrixWorld.decompose(this.el.object3D.position, this.el.object3D.quaternion, this.el.object3D.scale)

        //////////////////////////////////////////////////////////////////////////////
        //		honor visibility
        //////////////////////////////////////////////////////////////////////////////
        if (_this._arAnchor.parameters.changeMatrixMode === 'modelViewMatrix') {
            var wasVisible = _this.el.object3D.visible
            _this.el.object3D.visible = this._arAnchor.object3d.visible
        } else if (_this._arAnchor.parameters.changeMatrixMode === 'cameraTransformMatrix') {
            var wasVisible = _this.el.sceneEl.object3D.visible
            _this.el.sceneEl.object3D.visible = this._arAnchor.object3d.visible
        } else console.assert(false)

        // emit markerFound markerLost
        if (_this._arAnchor.object3d.visible === true && wasVisible === false) {
            _this.el.emit('markerFound')
        } else if (_this._arAnchor.object3d.visible === false && wasVisible === true) {
            _this.el.emit('markerLost')
        }
    }
})

//////////////////////////////////////////////////////////////////////////////
//                define some primitives shortcuts
//////////////////////////////////////////////////////////////////////////////

AFRAME.registerPrimitive('a-anchor', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
    defaultComponents: {
        'arjs-anchor': {},
        'arjs-hit-testing': {},
    },
    mappings: {
        'type': 'arjs-anchor.type',
        'size': 'arjs-anchor.size',
        'url': 'arjs-anchor.patternUrl',
        'value': 'arjs-anchor.barcodeValue',
        'preset': 'arjs-anchor.preset',
        'min-confidence': 'arjs-anchor.minConfidence',
        'marker-helpers': 'arjs-anchor.markerhelpers',
        'smooth': 'arjs-anchor.smooth',
        'smooth-count': 'arjs-anchor.smoothCount',
        'smooth-tolerance': 'arjs-anchor.smoothTolerance',
        'smooth-threshold': 'arjs-anchor.smoothThreshold',

        'hit-testing-render-debug': 'arjs-hit-testing.renderDebug',
        'hit-testing-enabled': 'arjs-hit-testing.enabled',
    }
}))

AFRAME.registerPrimitive('a-camera-static', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
    defaultComponents: {
        'camera': {},
    },
    mappings: {
    }
}))

//////////////////////////////////////////////////////////////////////////////
//		backward compatibility
//////////////////////////////////////////////////////////////////////////////
// FIXME
AFRAME.registerPrimitive('a-marker', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
    defaultComponents: {
        'arjs-anchor': {},
        'arjs-hit-testing': {},
    },
    mappings: {
        'type': 'arjs-anchor.type',
        'size': 'arjs-anchor.size',
        'url': 'arjs-anchor.patternUrl',
        'value': 'arjs-anchor.barcodeValue',
        'preset': 'arjs-anchor.preset',
        'min-confidence': 'arjs-anchor.minConfidence',
        'marker-helpers': 'arjs-anchor.markerhelpers',
        'smooth': 'arjs-anchor.smooth',
        'smooth-count': 'arjs-anchor.smoothCount',
        'smooth-tolerance': 'arjs-anchor.smoothTolerance',
        'smooth-threshold': 'arjs-anchor.smoothThreshold',

        'hit-testing-render-debug': 'arjs-hit-testing.renderDebug',
        'hit-testing-enabled': 'arjs-hit-testing.enabled',
    }
}))

AFRAME.registerPrimitive('a-marker-camera', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
    defaultComponents: {
        'arjs-anchor': {
            changeMatrixMode: 'cameraTransformMatrix'
        },
        'camera': {},
    },
    mappings: {
        'type': 'arjs-anchor.type',
        'size': 'arjs-anchor.size',
        'url': 'arjs-anchor.patternUrl',
        'value': 'arjs-anchor.barcodeValue',
        'preset': 'arjs-anchor.preset',
        'min-confidence': 'arjs-anchor.minConfidence',
        'marker-helpers': 'arjs-anchor.markerhelpers',
    }
}))
//////////////////////////////////////////////////////////////////////////////
//		arjs-hit-testing
//////////////////////////////////////////////////////////////////////////////
AFRAME.registerComponent('arjs-hit-testing', {
	dependencies: ['arjs', 'artoolkit'],
	schema: {
		enabled : {
			type: 'boolean',
			default: false,
		},
		renderDebug : {
			type: 'boolean',
			default: false,
		},
	},
	init: function () {
		var _this = this
		var arjsSystem = this.el.sceneEl.systems.arjs || this.el.sceneEl.systems.artoolkit

// TODO make it work on cameraTransformMatrix too
//
		_this.isReady = false
		_this._arAnchor = null
		_this._arHitTesting = null

        //LiveCdoing.space fix for editor mode

        if(arjsSystem) {

		// trick to wait until arjsSystem is isReady
		var startedAt = Date.now()
		var timerId = setInterval(function(){
			var anchorEl = _this.el
			var anchorComponent = anchorEl.components['arjs-anchor']
			// wait until anchorComponent is isReady
			if( anchorComponent === undefined || anchorComponent.isReady === false )	return

			clearInterval(timerId)

			//////////////////////////////////////////////////////////////////////////////
			//		create arAnchor
			//////////////////////////////////////////////////////////////////////////////
			var arAnchor = anchorComponent._arAnchor
			var arSession = arjsSystem._arSession
			var renderer = arSession.parameters.renderer

			var hitTesting = _this._arHitTesting = new ARjs.HitTesting(arSession)
			hitTesting.enabled = _this.data.enabled

			_this.isReady = true
        }, 1000/60)
    }
	},
	remove : function(){
	},
	update: function () {
	},
	tick: function(){
		var _this = this
		// if not yet isReady, do nothing
		if( this.isReady === false )	return

		var arjsSystem = this.el.sceneEl.systems.arjs || this.el.sceneEl.systems.artoolkit
		var arSession = arjsSystem._arSession

		var anchorEl = _this.el
		var anchorComponent = anchorEl.components['arjs-anchor']
		var arAnchor = anchorComponent._arAnchor


		var hitTesting = this._arHitTesting
		var camera = arSession.parameters.camera
// console.log(camera.position)
		hitTesting.update(camera, arAnchor.object3d, arAnchor.parameters.changeMatrixMode)
	}
});
///////////////END ARJS/////////

///MIRROR//

THREE.ShaderLib[ 'mirror' ] = {

	uniforms: {
		"mirrorColor": { value: new THREE.Color( 0x7F7F7F ) },
		"mirrorSampler": { value: null },
		"textureMatrix" : { value: new THREE.Matrix4() }
	},

	vertexShader: [

		"uniform mat4 textureMatrix;",

		"varying vec4 mirrorCoord;",

		"void main() {",

			"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
			"vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
			"mirrorCoord = textureMatrix * worldPosition;",

			"gl_Position = projectionMatrix * mvPosition;",

		"}"

	].join( "\n" ),

	fragmentShader: [

		"uniform vec3 mirrorColor;",
		"uniform sampler2D mirrorSampler;",

		"varying vec4 mirrorCoord;",

		"float blendOverlay(float base, float blend) {",
			"return( base < 0.5 ? ( 2.0 * base * blend ) : (1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );",
		"}",

		"void main() {",

			"vec4 color = texture2DProj(mirrorSampler, mirrorCoord);",
			"color = vec4(blendOverlay(mirrorColor.r, color.r), blendOverlay(mirrorColor.g, color.g), blendOverlay(mirrorColor.b, color.b), 1.0);",

			"gl_FragColor = color;",

		"}"

	].join( "\n" )

};

THREE.Mirror = function ( renderer, camera, options ) {

    
	THREE.Object3D.call( this );

	this.name = 'mirror_' + this.id;

	options = options || {};

	this.matrixNeedsUpdate = true;

	var width = options.textureWidth !== undefined ? options.textureWidth : 512;
	var height = options.textureHeight !== undefined ? options.textureHeight : 512;

	this.clipBias = options.clipBias !== undefined ? options.clipBias : 0.0;

	var mirrorColor = options.color !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );

	this.renderer = renderer;
	this.mirrorPlane = new THREE.Plane();
	this.normal = new THREE.Vector3( 0, 0, 1 );
	this.mirrorWorldPosition = new THREE.Vector3();
	this.cameraWorldPosition = new THREE.Vector3();
	this.rotationMatrix = new THREE.Matrix4();
	this.lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
	this.clipPlane = new THREE.Vector4();

	// For debug only, show the normal and plane of the mirror
	var debugMode = options.debugMode !== undefined ? options.debugMode : false;

	if ( debugMode ) {

		var arrow = new THREE.ArrowHelper( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 0, 0, 0 ), 10, 0xffff80 );
		var planeGeometry = new THREE.Geometry();
		planeGeometry.vertices.push( new THREE.Vector3( - 10, - 10, 0 ) );
		planeGeometry.vertices.push( new THREE.Vector3( 10, - 10, 0 ) );
		planeGeometry.vertices.push( new THREE.Vector3( 10, 10, 0 ) );
		planeGeometry.vertices.push( new THREE.Vector3( - 10, 10, 0 ) );
		planeGeometry.vertices.push( planeGeometry.vertices[ 0 ] );
		var plane = new THREE.Line( planeGeometry, new THREE.LineBasicMaterial( { color: 0xffff80 } ) );

		this.add( arrow );
		this.add( plane );

	}

	if ( camera instanceof THREE.PerspectiveCamera ) {

		this.camera = camera;

	} else {

		this.camera = new THREE.PerspectiveCamera();
		console.log( this.name + ': camera is not a Perspective Camera!' );

    }


	this.textureMatrix = new THREE.Matrix4();

	this.mirrorCamera = this.camera.clone();
    this.mirrorCamera.matrixAutoUpdate = true;


	var parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false };

    //this.currentRenderTarget = this.renderer.getRenderTarget();

	this.renderTarget = new THREE.WebGLRenderTarget( width, height, parameters );
	this.renderTarget2 = new THREE.WebGLRenderTarget( width, height, parameters );

	var mirrorShader = THREE.ShaderLib[ "mirror" ];
	var mirrorUniforms = THREE.UniformsUtils.clone( mirrorShader.uniforms );

	this.material = new THREE.ShaderMaterial( {

		fragmentShader: mirrorShader.fragmentShader,
		vertexShader: mirrorShader.vertexShader,
		uniforms: mirrorUniforms

	} );

	this.material.uniforms.mirrorSampler.value = this.renderTarget.texture;
	this.material.uniforms.mirrorColor.value = mirrorColor;
	this.material.uniforms.textureMatrix.value = this.textureMatrix;

	if ( ! THREE.Math.isPowerOfTwo( width ) || ! THREE.Math.isPowerOfTwo( height ) ) {

		this.renderTarget.texture.generateMipmaps = false;
		this.renderTarget2.texture.generateMipmaps = false;

	}

	this.updateTextureMatrix();
    this.render();
    


};

THREE.Mirror.prototype = Object.create( THREE.Object3D.prototype );
THREE.Mirror.prototype.constructor = THREE.Mirror;

THREE.Mirror.prototype.renderWithMirror = function ( otherMirror, aScene ) {

	// update the mirror matrix to mirror the current view
	this.updateTextureMatrix();
	this.matrixNeedsUpdate = false;

	// set the camera of the other mirror so the mirrored view is the reference view
	var tempCamera = otherMirror.camera;
	otherMirror.camera = this.mirrorCamera;

	// render the other mirror in temp texture
	otherMirror.renderTemp(aScene);
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.renderTarget2.texture;

	// render the current mirror
	this.render(aScene);
	this.matrixNeedsUpdate = true;

	// restore material and camera of other mirror
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.renderTarget.texture;
	otherMirror.camera = tempCamera;

	// restore texture matrix of other mirror
	otherMirror.updateTextureMatrix();

};

THREE.Mirror.prototype.updateTextureMatrix = function () {

	this.updateMatrixWorld();
	this.camera.updateMatrixWorld();

	this.mirrorWorldPosition.setFromMatrixPosition( this.matrixWorld );
	this.cameraWorldPosition.setFromMatrixPosition( this.camera.matrixWorld );

	this.rotationMatrix.extractRotation( this.matrixWorld );

	this.normal.set( 0, 0, 1 );
	this.normal.applyMatrix4( this.rotationMatrix );

	var view = this.mirrorWorldPosition.clone().sub( this.cameraWorldPosition );
	view.reflect( this.normal ).negate();
	view.add( this.mirrorWorldPosition );

	this.rotationMatrix.extractRotation( this.camera.matrixWorld );

	this.lookAtPosition.set( 0, 0, - 1 );
	this.lookAtPosition.applyMatrix4( this.rotationMatrix );
	this.lookAtPosition.add( this.cameraWorldPosition );

	var target = this.mirrorWorldPosition.clone().sub( this.lookAtPosition );
	target.reflect( this.normal ).negate();
	target.add( this.mirrorWorldPosition );

	this.up.set( 0, - 1, 0 );
	this.up.applyMatrix4( this.rotationMatrix );
	this.up.reflect( this.normal ).negate();

	this.mirrorCamera.position.copy( view );
	this.mirrorCamera.up = this.up;
	this.mirrorCamera.lookAt( target );

	this.mirrorCamera.updateProjectionMatrix();
	this.mirrorCamera.updateMatrixWorld();
	this.mirrorCamera.matrixWorldInverse.getInverse( this.mirrorCamera.matrixWorld );

	// Update the texture matrix
	this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5,
							0.0, 0.5, 0.0, 0.5,
							0.0, 0.0, 0.5, 0.5,
							0.0, 0.0, 0.0, 1.0 );
	this.textureMatrix.multiply( this.mirrorCamera.projectionMatrix );
	this.textureMatrix.multiply( this.mirrorCamera.matrixWorldInverse );

	// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
	// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
	this.mirrorPlane.setFromNormalAndCoplanarPoint( this.normal, this.mirrorWorldPosition );
	this.mirrorPlane.applyMatrix4( this.mirrorCamera.matrixWorldInverse );

	this.clipPlane.set( this.mirrorPlane.normal.x, this.mirrorPlane.normal.y, this.mirrorPlane.normal.z, this.mirrorPlane.constant );

	var q = new THREE.Vector4();
	var projectionMatrix = this.mirrorCamera.projectionMatrix;

	q.x = ( Math.sign( this.clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
	q.y = ( Math.sign( this.clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
	q.z = - 1.0;
	q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];

	// Calculate the scaled plane vector
	var c = new THREE.Vector4();
	c = this.clipPlane.multiplyScalar( 2.0 / this.clipPlane.dot( q ) );

	// Replacing the third row of the projection matrix
	projectionMatrix.elements[ 2 ] = c.x;
	projectionMatrix.elements[ 6 ] = c.y;
	projectionMatrix.elements[ 10 ] = c.z + 1.0 - this.clipBias;
	projectionMatrix.elements[ 14 ] = c.w;

};

THREE.Mirror.prototype.render = function (aScene) {

	if ( this.matrixNeedsUpdate ) this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

	// Render the mirrored view of the current scene into the target texture
	//var scene = aScene //this;

	// while ( scene.parent !== null ) {

	// 	scene = scene.parent;

	// }

    //this.renderer.setRenderTarget( null );

    if ( aScene !== undefined) //&& scene instanceof THREE.Scene ) 
    {

		// We can't render ourself to ourself
		var visible = this.material.visible;
		this.material.visible = false;

        this.renderer.clear();
        this.renderer.setRenderTarget( this.renderTarget);
        this.renderer.render( aScene.object3D, this.mirrorCamera);
        this.renderer.setRenderTarget(null);
   

		//this.renderer.render( scene, this.mirrorCamera, this.renderTarget, true );

		this.material.visible = visible;

	}

};

THREE.Mirror.prototype.renderTemp = function (aScene) {

	if ( this.matrixNeedsUpdate ) this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

    // Render the mirrored view of the current scene into the target texture
    
	// var scene = this;

	// while ( scene.parent !== null ) {

	// 	scene = scene.parent;

	// }

	if ( aScene !== undefined) //&& scene instanceof THREE.Scene ) {
{
        this.renderer.clear();
        this.renderer.setRenderTarget( this.renderTarget2);
        this.renderer.render( aScene.object3D, this.mirrorCamera );
        this.renderer.setRenderTarget( null );

		//this.renderer.render( scene, this.mirrorCamera, this.renderTarget2, true );

	}

};

AFRAME.registerComponent('mirror', {
    schema:{
        renderothermirror:{default:true},
        camera:{default: 'avatarControl'}
    },
    init: function () {
        var scene = this.el.sceneEl;
        this.cameraID = this.data.camera;
        scene.addEventListener('render-target-loaded',this.OnRenderLoaded()); //this.OnRenderLoaded.bind(this)
    },

    // update: function (old) {
    //     this.cameraID = this.data.camera;
    //     this.OnRenderLoaded();
    // },

    OnRenderLoaded: function()
    {

        var mirrorObj = this.el.getOrCreateObject3D('mesh',THREE.Mesh);
        // var cameraEl = document.querySelector('a-entity[camera]')
        // if(!cameraEl)
        // {
        //     cameraEl = document.querySelector('a-camera');
        // }
        // var camera = cameraEl.components.camera.camera;
        let cameraEl = document.querySelector("[id='" + this.cameraID + "']")
        if (cameraEl){
        let camera = cameraEl.getObject3D('camera'); //document.querySelector('#avatarControl').getObject3D('camera');
        var scene = this.el.sceneEl;
        // this.renderer = new THREE.WebGLRenderer({
        //     antialias: true,
        // });
        this.renderer = scene.renderer;
        this.mirror = new THREE.Mirror( this.renderer, camera, { clipBias: 0.003, textureWidth: window.innerWidth, textureHeight: window.innerHeight, color: 0x777777, debugMode: false} );
        mirrorObj.material = this.mirror.material;
        //mirrorObj.children = [];
        mirrorObj.add(this.mirror);
        }

        //As of A-Frame tick (behaviours) priority issue need to place mirror tick onto upper tick()
        this.el.sceneEl.components['scene-utils'].mirrors[this.el.id] = this;

    },

    remove: function () {
        delete this.el.sceneEl.components['scene-utils'].mirrors[this.el.id]
    },

    mirrorTick: function () {

        //    //this.mirror.render();

            if(!this.data.renderothermirror)
                {
                    this.mirror.render(this.el.sceneEl);
                }
            else
                {
                    var mirrors = [];
                    var mirrorEls = document.querySelectorAll("[mirror]");
                    for(var i=0;i<mirrorEls.length;i++)
                    {
                        if(mirrorEls[i]!=this.el)
                        {
                            mirrors.push(mirrorEls[i].components.mirror.mirror);
                        }   
                    }
                    if(mirrors.length === 0)
                    {
                        this.mirror.render(this.el.sceneEl);
                    }
                    for(var i = 0; i<mirrors.length;i++)
                    {
                        this.mirror.renderWithMirror(mirrors[i], this.el.sceneEl);
                    }
                }
         },

    // tick: function () {

    //  }

    });