var camera, scene, renderer;
var handleScale = 20;
var dragControls;
var textureLoader;
var handleTexture;
var socket;

// Here we define a few data types related to ofxPiMapper.
// This is done to be able to read the structure better.
function Vertex (x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

function TexCoord (x, y) {
    this.x = x;
    this.y = y;
}

function Source (type, name) {
    this.type = type;
    this.name = name;
}

function Surface(vertices, texCoords, source){
    this.vertices = vertices;
    this.texCoords = texCoords;
    this.source = source;
}

// Array containing multiple surface objects. 
var surfaces = [];

init();

function opmOnLoaded ( data ) {
    var parser = new DOMParser();
    var doc = parser.parseFromString(data, 'application/xml');
    console.log('Parsing XML data.');
    
    // Loop through surfaces and create our data structures.
    var numSurfaces = doc.getElementsByTagName('surface').length;
    for ( var i = 0; i < numSurfaces; i++ ) {
        var domSurface = doc.getElementsByTagName('surface')[i];
        
        var vertices = [];
        for ( var j = 0; j < domSurface.getElementsByTagName('vertex').length; j++ ) {
            var vertex = new Vertex(
                parseFloat(domSurface.getElementsByTagName('vertex')[j].getElementsByTagName('x')[0].firstChild.nodeValue),
                parseFloat(domSurface.getElementsByTagName('vertex')[j].getElementsByTagName('y')[0].firstChild.nodeValue),
                0.0
            );
            vertices.push(vertex);
        }
        
        var texCoords = [];
        for ( var j = 0; j < domSurface.getElementsByTagName('texCoord').length; j++ ) {
            var texCoord = new TexCoord(
                parseFloat(domSurface.getElementsByTagName('texCoord')[j].getElementsByTagName('x')[0].firstChild.nodeValue),
                parseFloat(domSurface.getElementsByTagName('texCoord')[j].getElementsByTagName('y')[0].firstChild.nodeValue)
            );
            texCoords.push(texCoord);
        }
        
        var source = new Source(
            domSurface.getElementsByTagName('source-type')[0].firstChild.nodeValue,
            domSurface.getElementsByTagName('source-name')[0].firstChild.nodeValue
        );

        // Now, create a Surface object for each surface tag in the XML file.

        var surface = new Surface(vertices, texCoords, source);
        surfaces.push(surface);
    }
    console.log(surfaces);
    console.log('Done parsing XML.');

    // Done parsing. We can create surfaces now.
    create();

    // Establish WebSockets connection 
    var protocol = 'ws://';
    var path = document.URL.substr(7);

    if (path.substring(0, 5) == 'https') {
        protocol = 'wss://';
        path = document.URL.substr(8);
    }

    path = path.split('/');
    path = path[0].split(':');
    path = path[0];
    socket = new WebSocket('ws://' + path + ':9092');

    try {
        socket.onopen = function() {
            console.log('WebSocket connection opened.');
        } 

        // received message
        socket.onmessage =function got_packet(msg) {
            console.log('WebSocket message received: ' + msg);
        }

        socket.onclose = function(){
            console.log('WebSocket connection closed.');
        }
    } catch(exception) {
        console.log('WebSocket connection exception');
    }
}

function opmOnProgress ( xhr ) {
    console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
}

function opmOnError ( err ) {
    console.error( 'Failed to load project.' );
}

// This creates 3D geometry out of the surfaces array.
function create() {

    // Create and add a mesh for each surface.
    for ( var i = 0; i < surfaces.length; i++ ) {
        var surface = surfaces[i];
        
        var vertices = [];
        for ( var j = 0; j < surface.vertices.length; j++ ) {
            vertices.push(surface.vertices[j].x);
            vertices.push(surface.vertices[j].y);
            vertices.push(0);
        }
        var verts = new Float32Array(vertices);

        var texCoords = [];
        for ( var j = 0; j < surface.texCoords.length; j++ ) {
            texCoords.push(surface.texCoords[j].x);
            texCoords.push(surface.texCoords[j].y);
        }
        var uvs = new Float32Array(texCoords);

        // TODO: adjust this depending on how many surface vertices there are.
        indices = new Uint32Array( [
            0, 1, 2, 
            2, 3, 0
        ] );

        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute( 'position', new THREE.BufferAttribute( verts, 3 ) );
        geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
        geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );

        // Load the texture asynchronously
        var texture = textureLoader.load('textures/gene-nsynthesis-image-a.jpg', function (texture){
            console.log('Texture loaded.');
        }, undefined, function (err) {
            console.error('Texture not loaded.', err)
        } );

        material = new THREE.MeshBasicMaterial( {map: texture, side: THREE.BackSide} );
    
        // Create mesh and add it to the scene
        var mesh = new THREE.Mesh( geometry, material );
        scene.add(mesh);

        // Begin adding handles
        var handles = [];
        var handleMaterial = new THREE.SpriteMaterial( { map: handleTexture, color: 0xffffff } );
        
        for(var j = 0; j < verts.length; j += 3){
            var x = verts[j];
            var y = verts[j + 1];
            var z = 0;
    
            var handleSprite = new THREE.Sprite( handleMaterial );
            handleSprite.position.set(x, y, z);
            handleSprite.scale.addScalar(handleScale);
            
            handleSprite.vertexMapping = {};
            handleSprite.vertexMapping.surface = i;
            handleSprite.vertexMapping.vertex = j / 3;
            
            scene.add( handleSprite );
            handles.push(handleSprite);
        }
        // End adding handles

        // Create drag controls for the handles
        dragControls = new THREE.DragControls( handles, camera, renderer.domElement );
        dragControls.addEventListener( 'drag', function( event ){
            var mSurface = event.object.vertexMapping.surface;
            var mVertex = event.object.vertexMapping.vertex;
            
            var position = event.object.position;
            var byX = position.x - surfaces[mSurface].vertices[mVertex].x;
            var byY = position.y - surfaces[mSurface].vertices[mVertex].y;
            surfaces[mSurface].vertices[mVertex].x = position.x;
            surfaces[mSurface].vertices[mVertex].y = position.y;
            var vertices = [];
            for ( var j = 0; j < surfaces[mSurface].vertices.length; j++ ) {
                vertices.push(surfaces[mSurface].vertices[j].x);
                vertices.push(surfaces[mSurface].vertices[j].y);
                vertices.push(0);
            }
            var verts = new Float32Array(vertices);
            geometry.addAttribute( 'position', new THREE.BufferAttribute( verts, 3 ) );
        
            // Send message via WebSockets
            var obj = {};
            obj.surface = mSurface;
            obj.vertex = mVertex;
            obj.x = position.x;
            obj.y = position.y;
            /*
            pack = JSON.stringify(obj);
            */

            var pack = '<act>';
            pack += '<surface>' + mSurface + '</surface>';
            pack += '<vertex>' + mVertex + '</vertex>';
            pack += '<x>' + byX + '</x>';
            pack += '<y>' + byY + '</y>';
            pack += '</act>';
            socket.send(pack);
        });
        // Over 
    }
}

function init() {

    // Create flobal object for loading textures.
    textureLoader = new THREE.TextureLoader();
    handleTexture = textureLoader.load('textures/handle.png', function (texture){
        console.log('Handle texture loaded.');
    }, undefined, function (err) {
        console.error('Handle texture not loaded.', err)
    });

    // left, right, bottom, top, near, far
    // Since the Y axis is flipped, 
    // we use 0 for the bottom frustrum border
    // and negative height for the top frustrum border.
    camera = new THREE.OrthographicCamera( 
        0, window.innerWidth, 
        0, -window.innerHeight,
        0, 1 );

    // Flip the Y coordinate by rotating the camera around X axis by 180deg.
    camera.rotation.x = Math.PI;

    scene = new THREE.Scene();
    
    // Create renderer and add it to HTML DOM>
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.render(scene, camera);

    document.body.appendChild( renderer.domElement );
    window.addEventListener( 'resize', onWindowResize, false );
    animate();

    // Load ofxPiMapper settings file
    var loader = new THREE.FileLoader();
    loader.load(
        'ofxpimapper.xml',
        opmOnLoaded,
        opmOnProgress,
        opmOnError
    );
}

function animate() {
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
}