/// <reference path="../../stats/stats.d.ts" />
/// <reference path="../physijs.d.ts" />
/// <reference path="../../threejs/three-projector.d.ts" />

Physijs.scripts.worker = '../physijs_worker.js';
Physijs.scripts.ammo = 'examples/js/ammo.js';

var initScene, initEventHandling, render, createTower,
    renderer, render_stats, physics_stats, scene, dir_light, am_light, camera,
    table, blocks = [], table_material, block_material, intersect_plane, _i,
    selected_block = null, mouse_pos = new THREE.Vector3(), block_offset = new THREE.Vector3(), _v3 = new THREE.Vector3();

initScene = function() {
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.shadowMapEnabled = true;
    renderer.shadowMapSoft = true;
    document.getElementById( 'viewport' ).appendChild( renderer.domElement );

    render_stats = new Stats();
    render_stats.domElement.style.position = 'absolute';
    render_stats.domElement.style.top = '1px';
    render_stats.domElement.style.zIndex = 100;
    document.getElementById( 'viewport' ).appendChild( render_stats.domElement );

    physics_stats = new Stats();
    physics_stats.domElement.style.position = 'absolute';
    physics_stats.domElement.style.top = '50px';
    physics_stats.domElement.style.zIndex = 100;
    document.getElementById( 'viewport' ).appendChild( physics_stats.domElement );

    scene = new Physijs.Scene({ fixedTimeStep: 1 / 120 });
    scene.setGravity(new THREE.Vector3( 0, -30, 0 ));
    scene.addEventListener(
        'update',
        function() {

            if ( selected_block !== null ) {

                _v3.copy( mouse_pos ).add( block_offset ).sub( selected_block.position ).multiplyScalar( 5 );
                _v3.y = 0;
                selected_block.setLinearVelocity( _v3 );

                // Reactivate all of the blocks
                _v3.set( 0, 0, 0 );
                for ( _i = 0; _i < blocks.length; _i++ ) {
                    blocks[_i].applyCentralImpulse( _v3 );
                }
            }

            scene.simulate( undefined, 1 );
            physics_stats.update();
        }
    );

    camera = new THREE.PerspectiveCamera(
        35,
        window.innerWidth / window.innerHeight,
        1,
        1000
    );
    camera.position.set( 25, 20, 25 );
    camera.lookAt(new THREE.Vector3( 0, 7, 0 ));
    scene.add( camera );

    // ambient light
    am_light = new THREE.AmbientLight( 0x444444 );
    scene.add( am_light );

    // directional light
    dir_light = new THREE.DirectionalLight( 0xFFFFFF );
    dir_light.position.set( 20, 30, -5 );
    dir_light.target.position.copy( scene.position );
    dir_light.castShadow = true;
    dir_light.shadowCameraLeft = -30;
    dir_light.shadowCameraTop = -30;
    dir_light.shadowCameraRight = 30;
    dir_light.shadowCameraBottom = 30;
    dir_light.shadowCameraNear = 20;
    dir_light.shadowCameraFar = 200;
    dir_light.shadowBias = -.001
    dir_light.shadowMapWidth = dir_light.shadowMapHeight = 2048;
    dir_light.shadowDarkness = .5;
    scene.add( dir_light );

    // Materials
    table_material = Physijs.createMaterial(
        new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture( 'images/wood.jpg' ), ambient: 0xFFFFFF }),
        .9, // high friction
        .2 // low restitution
    );
    table_material.map.wrapS = table_material.map.wrapT = THREE.RepeatWrapping;
    table_material.map.repeat.set( 5, 5 );

    block_material = Physijs.createMaterial(
        new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture( 'images/plywood.jpg' ), ambient: 0xFFFFFF }),
        .4, // medium friction
        .4 // medium restitution
    );
    block_material.map.wrapS = block_material.map.wrapT = THREE.RepeatWrapping;
    block_material.map.repeat.set( 1, .5 );

    // Table
    table = new Physijs.BoxMesh(
        new THREE.BoxGeometry(50, 1, 50),
        table_material,
        0
    );
    table.position.y = -.5;
    table.receiveShadow = true;
    scene.add( table );

    createTower();

    intersect_plane = new THREE.Mesh(
        new THREE.PlaneGeometry( 150, 150 ),
        new THREE.MeshBasicMaterial({ opacity: 0, transparent: true })
    );
    intersect_plane.rotation.x = Math.PI / -2;
    scene.add( intersect_plane );

    initEventHandling();

    requestAnimationFrame( render );
    scene.simulate();
};

render = function() {
    requestAnimationFrame( render );
    renderer.render( scene, camera );
    render_stats.update();
};

createTower = (function() {
    var block_length = 6, block_height = 1, block_width = 1.5, block_offset = 2,
        block_geometry = new THREE.BoxGeometry( block_length, block_height, block_width );

    return function() {
        var i, j, rows = 16,
            block;

        for ( i = 0; i < rows; i++ ) {
            for ( j = 0; j < 3; j++ ) {
                block = new Physijs.BoxMesh( block_geometry, block_material );
                block.position.y = (block_height / 2) + block_height * i;
                if ( i % 2 === 0 ) {
                    block.rotation.y = Math.PI / 2.01; // #TODO: There's a bug somewhere when this is to close to 2
                    block.position.x = block_offset * j - ( block_offset * 3 / 2 - block_offset / 2 );
                } else {
                    block.position.z = block_offset * j - ( block_offset * 3 / 2 - block_offset / 2 );
                }
                block.receiveShadow = true;
                block.castShadow = true;
                scene.add( block );
                blocks.push( block );
            }
        }
    }
})();

initEventHandling = (function() {
    var _vector = new THREE.Vector3,
        projector = new THREE.Projector(),
        handleMouseDown, handleMouseMove, handleMouseUp;

    handleMouseDown = function( evt ) {
        var ray, intersections;

        _vector.set(
            ( evt.clientX / window.innerWidth ) * 2 - 1,
            -( evt.clientY / window.innerHeight ) * 2 + 1,
            1
        );

        projector.unprojectVector( _vector, camera );

        ray = new THREE.Raycaster( camera.position, _vector.sub( camera.position ).normalize() );
        intersections = ray.intersectObjects( blocks );

        if ( intersections.length > 0 ) {
            selected_block = intersections[0].object;

            _vector.set( 0, 0, 0 );
            selected_block.setAngularFactor( _vector );
            selected_block.setAngularVelocity( _vector );
            selected_block.setLinearFactor( _vector );
            selected_block.setLinearVelocity( _vector );

            mouse_pos.copy( intersections[0].point );
            block_offset.subVectors( selected_block.position, mouse_pos );

            intersect_plane.position.y = mouse_pos.y;
        }
    };

    handleMouseMove = function( evt ) {

        var ray, intersection,
            i, scalar;

        if ( selected_block !== null ) {

            _vector.set(
                ( evt.clientX / window.innerWidth ) * 2 - 1,
                -( evt.clientY / window.innerHeight ) * 2 + 1,
                1
            );
            projector.unprojectVector( _vector, camera );

            ray = new THREE.Raycaster( camera.position, _vector.sub( camera.position ).normalize() );
            intersection = ray.intersectObject( intersect_plane );
            mouse_pos.copy( intersection[0].point );
        }

    };

    handleMouseUp = function( evt ) {

        if ( selected_block !== null ) {
            _vector.set( 1, 1, 1 );
            selected_block.setAngularFactor( _vector );
            selected_block.setLinearFactor( _vector );

            selected_block = null;
        }

    };

    return function() {
        renderer.domElement.addEventListener( 'mousedown', handleMouseDown );
        renderer.domElement.addEventListener( 'mousemove', handleMouseMove );
        renderer.domElement.addEventListener( 'mouseup', handleMouseUp );
    };
})();

window.onload = initScene;
