/* 
 * James Hilverda
 * Final Project
 * Code is based on this sample: http://mrdoob.github.com/three.js/examples/webgl_interactive_voxelpainter.html
 */

// Takes 25 animations to get b/w current and target position (controlled by variable moveRenders)
// Has a skybox, which is currently commented out
// Press shift to angle the screen once you've hit run

// Detect if WebGL is supported
if ( !Detector.webgl ) Detector.addGetWebGLMessage();

var container, stats;
var camera, scene, renderer;
var projector, plane, cube;
var mouse2D, mouse3D, ray,
rollOveredFace, isShiftDown,
isCtrlDown, theta = 0;

var objectPosition = new THREE.Vector3(), tmpVec = new THREE.Vector3();
var sandGeo, sandMaterial;
var i, intersector;

var agentModel, agentSkin;
var enemyModel, enemySkin;

var agents = [];
var agentMeshes = [];
var agentSkins = [];
var enemies = [];
var enemyMeshes = [];
var enemySkins = [];
var enemySkin;
var sands = [];
var skyboxMesh;

var baseX = 0;
var baseY = 0;
var squareWidth = 50;

var colladaLoader;
var jsonLoader;

var time = 0;
var endTime = 50;
var begun = false;
var animationCount = 0;
var moveRenders = 25;   // How many renders it takes for an object to move 1 square

var duration        = 1000; // milliseconds to complete animation
var keyframes       = 20;   // total number of animation frames
var interpolation   = duration / keyframes; // milliseconds per frame
var lastKeyframe    = 0;    // previous keyframe
var currentKeyframe = 0;

var timeChanged = false;

var t = 0;
var t1 = 0;
var clock = new THREE.Clock();

function View( controller )
{
	this.controller = controller;
}

/**
 * Begins the game
 */
View.prototype.play = function()
{
	endTime = this.controller.getLength();
	
	// Reload the agents
	if (!begun) {
		begun = true;
	
		agents = this.controller.getAgents();
		
		loadAgents();
		
		updateAgents();
        updateEnemies();
		
		// Skybox
		//var skyBoxMaterial = new THREE.MeshBasicMaterial( { color: 0x75CB40, depthWrite: false, side: THREE.BackSide } );
		//skyboxMesh = new THREE.Mesh( new THREE.CubeGeometry( 10000, 10000, 10000 ), skyBoxMaterial );
		//scene.add( skyboxMesh );
	}

};

/********************** VV Code that needs to be object-ified ***/

function init() {
	//$('#runButton').bind('click', View.prototype.play);

	// Get the level
	var level = this.controller.getLevel();
	sands = level.sand;
	agents = level.startingAgent;
	enemies = level.enemies;

	container = $( '<div id="GraphicsContainer"/>' );
	$( 'body' ).prepend( container );

	camera = new THREE.PerspectiveCamera( 50, 1, 1, 10000 );
	camera.position.z = 1210;
	camera.target = new THREE.Vector3( 0, 150, 0 );

	scene = new THREE.Scene();

	colladaLoader = new THREE.ColladaLoader();
	colladaLoader.options.convertUpAxis = true;
	
	jsonLoader = new THREE.JSONLoader();

	//Sands
	sandGeo = new THREE.CubeGeometry( squareWidth, squareWidth, squareWidth );
	sandMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, ambient: 0x00ff80, shading: THREE.FlatShading, map: THREE.ImageUtils.loadTexture( "textures/SquareTexture.png" ) } );
	sandMaterial.color.setHSV( 0.1, 0.7, 1.0 );
	sandMaterial.ambient = sandMaterial.color;

	// Load the starting agent
	loadAgents();

	// Load the enemies
	loadEnemies();
	
	// Load the walls
	loadSands();

	projector = new THREE.Projector();

	// Create grid
	plane = new THREE.Mesh( new THREE.PlaneGeometry( squareWidth * level.x, squareWidth * level.x, level.x, level.y ), 
		new THREE.MeshBasicMaterial( { color: 0x555555, wireframe: true } ) );
		
	// Set the base position so that grid coordinates (0,0) are top left)
	baseX = -squareWidth * level.x * 0.5 + (squareWidth / 2);
	baseY = squareWidth * level.y * 0.5 + (squareWidth / 2);
	
	scene.add( plane );

	mouse2D = new THREE.Vector3( 0, 10000, 0.5 );

	// Lights
	var ambientLight = new THREE.AmbientLight( 0x606060 );
	scene.add( ambientLight );

	var directionalLight = new THREE.DirectionalLight( 0xffffff );
	directionalLight.position.set( 1, 0.75, 0.5 ).normalize();
	scene.add( directionalLight );

	renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } );
	renderer.setSize( 700, 700 );

	container.append( renderer.domElement );

	/*
	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.bottom = '0px';
	container.append( stats.domElement );
	*/
	document.addEventListener( 'mousemove', onDocumentMouseMove, false );
	document.addEventListener( 'mousedown', onDocumentMouseDown, false );
	document.addEventListener( 'keydown', onDocumentKeyDown, false );
	document.addEventListener( 'keyup', onDocumentKeyUp, false );
}

function reset()
{
   for ( var i = 0; i < agents.length; i++ )
   {
        var position = agents[i].getStatus( 0 );
        agentMeshes[i].position.x = Math.floor( position.x ) * squareWidth + baseX;
        agentMeshes[i].position.y = -Math.floor( position.y + 1 ) * squareWidth + baseY;
	}
	
	for ( var i = 0; i < enemies.length; i++ )
    {
		var position = enemies[i].getPosition( 0 );
		enemyMeshes[i].position.x = Math.floor( position.x ) * squareWidth + baseX;
		enemyMeshes[i].position.y = -Math.floor( position.y ) * squareWidth + baseY;
	}
	
	t = 0;
	t1 = 0;
	time = 0;
	camera.lookAt( scene.position );
    renderer.render( scene, camera );
}

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseMove( event ) {
	event.preventDefault();

	mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}

function onDocumentMouseDown( event ) {
	event.preventDefault();

	mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}

function onDocumentKeyDown( event ) {
	switch( event.keyCode ) {
		case 16: isShiftDown = true; break;
		case 17: isCtrlDown = true; break;
	}
}

function onDocumentKeyUp( event ) {
	switch( event.keyCode ) {
		case 16: isShiftDown = false; break;
		case 17: isCtrlDown = false; break;
	}
}

function animate() {
	requestAnimationFrame( animate );

	render();
	// stats.update();
}

function render() {
    // Animate the agents and enemies once the game has begun
    if (begun) {
		// Control camera angling
		if ( isShiftDown ) {
			theta += 2;
		}
	
		camera.position.x = 150 * Math.sin( theta * Math.PI / 360 );
	
		var delta = clock.getDelta();
		
		for (var i = 0; i < agents.length; i++) {
			if (agents[i].getStatus( time ).isAlive) {
				// Animate the agent
				var time2 = new Date().getTime() % duration;
				keyframe = Math.floor( time2 / interpolation );
				if ( keyframe != currentKeyframe ) 
				{
					agentMeshes[i].morphTargetInfluences[ lastKeyframe ] = 0;
					agentMeshes[i].morphTargetInfluences[ currentKeyframe ] = 1;
					agentMeshes[i].morphTargetInfluences[ keyframe ] = 0;
					lastKeyframe = currentKeyframe;
					currentKeyframe = keyframe;
				}
				agentMeshes[i].morphTargetInfluences[ keyframe ] = 
					( time2 % interpolation ) / interpolation;
				agentMeshes[i].morphTargetInfluences[ lastKeyframe ] = 
					1 - agentMeshes[i].morphTargetInfluences[ keyframe ];
				
				// Move the agent
				var directionX = agents[i].targetX - agentMeshes[i].position.x;
				var directionY = agents[i].targetY - agentMeshes[i].position.y;
				
				var moveX = ( directionX ) / (moveRenders - animationCount);
				var moveY = ( directionY ) / (moveRenders - animationCount);
				
				agentMeshes[i].position.x += moveX;
				agentMeshes[i].position.y += moveY;
				
				// Determine the x facing of the agent
				if (directionX > 0) {
					if (directionY > 0) {
						agentMeshes[i].rotation.y = -45;
					} else if (directionY < 0) {
						agentMeshes[i].rotation.y = 45;
					} else {
						agentMeshes[i].rotation.y = 89.5;
					}
				} else if (directionX < 0) {
					if (directionY > 0) {
						agentMeshes[i].rotation.y = -134.5;
					} else if (directionY < 0) {
						agentMeshes[i].rotation.y = 134.5;
					} else {
						agentMeshes[i].rotation.y = -89.5;
					}
				} else {
					// Determine y facing without any x facing
					if (directionY > 0) {
						agentMeshes[i].rotation.y = 0;
					} else {
						agentMeshes[i].rotation.y = 88;
					}
				}
			}
        }
		
        for (var i = 0; i < enemies.length; i++) {
			// Animate the enemy
			enemyMeshes[i].updateAnimation( 1000 * delta );
		
			// Move each enemy toward its target
			var directionX = enemies[i].targetX - enemyMeshes[i].position.x;
			var directionY = enemies[i].targetY - enemyMeshes[i].position.y;
			
			var moveX = ( directionX ) / (moveRenders - animationCount);
			var moveY = ( directionY ) / (moveRenders - animationCount);
			
			enemyMeshes[i].position.x += moveX;
			enemyMeshes[i].position.y += moveY;
			
			// Determine the x facing of the enemy
			if (directionX > 0) {
				if (directionY > 0) {
					enemyMeshes[i].rotation.y = -44.5;
				} else if (directionY < 0) {
					enemyMeshes[i].rotation.y = 44.5;
				} else {
					enemyMeshes[i].rotation.y = 0;
				}
			} else if (directionX < 0) {
				if (directionY > 0) {
					enemyMeshes[i].rotation.y = -134.5;
				} else if (directionY < 0) {
					enemyMeshes[i].rotation.y = 134.5;
				} else {
					enemyMeshes[i].rotation.y = 179.3;
				}
			} else {
				// Determine y facing without any x facing
				if (directionY > 0) {
					enemyMeshes[i].rotation.y = 89.5;
				} else {
					enemyMeshes[i].rotation.y = -89.5;
				}
			}
        }
		
		// Increment the time and update object headings every set number of renders
		if (++animationCount >= moveRenders) {
            animationCount = 0;
            // Increment the time, and stop animation if it's reached the end
            if (time++ == endTime || controller.isFinished( time )) {
                begun = false;
				finished();
            } else {
                updateAgents();
                updateEnemies();
            }
        }
    }
    
	camera.lookAt( scene.position );
	
    renderer.render( scene, camera );
}

// Creates and positions a mesh for each sand
function loadSands() {
/*
    for (var i = 0; i < sands.length; i++) {
        mesh = new THREE.Mesh( sandGeo, sandMaterial );
        mesh.position.x = Math.floor( sands[i].x ) * squareWidth + baseX - 20;
        mesh.position.y = -Math.floor( sands[i].y + 1 ) * squareWidth + baseY - 20;
        mesh.position.z = 0;
        mesh.matrixAutoUpdate = false;
        mesh.updateMatrix();
        scene.add( mesh );
    }
	*/
}

// Loads the model for the agent, and creates and positions a mesh for each agent
function loadAgents() {
	var genFunction = function( i )
	{
		return function( geometry, materials ) {
			for (var j = 0; j < materials.length; j++)
				materials[j].morphTargets = true;

			var material = new THREE.MeshFaceMaterial( materials );
			agentMeshes[i] = new THREE.Mesh( geometry, material );
			agentMeshes[i].scale.set( 5, 5, 5 );
			agentMeshes[i].rotation.x = 90;
			agentMeshes[i].updateMatrix();
			
			var status = agents[i].getStatus( time );
			
			agentMeshes[i].position.x = agents[i].targetX = Math.floor( status.x ) * squareWidth + baseX;
			agentMeshes[i].position.y = agents[i].targetY = -Math.floor( status.y ) * squareWidth + baseY;
			agentMeshes[i].position.z = 0;
			scene.add ( agentMeshes[i] );
		}
	}

	for (var i = 0; i < agents.length; i++) {
		// Load the model
		jsonLoader.load( './models/droid/droid.js', genFunction( i ) );
	}
}

// Updates the target position of each agent
function updateAgents() {
    for (var i = 0; i < agents.length; i++) {
        var status = agents[i].getStatus( time );
    
        // Remove the mesh for the agent if he is dead
        if (status.isAlive) {
            agentMeshes[i].position.x = agents[i].targetX;
            agentMeshes[i].position.y = agents[i].targetY;
  
            var position = agents[i].getStatus( time );
            agents[i].targetX = Math.floor( position.x ) * squareWidth + baseX;
            agents[i].targetY = -Math.floor( position.y + 1 ) * squareWidth + baseY;
        }
    }
}

// Loads the model for the enemy, and creates and positions a mesh for each enemy
function loadEnemies() {
	var genFunction = function( i )
	{
		return function( geometry, materials ) {
			enemySkin = materials[ 0 ];
			enemySkin.morphTargets = true;
			
			var faceMaterial = new THREE.MeshFaceMaterial( materials );
			
			enemyMeshes[i] = new THREE.MorphAnimMesh( geometry, faceMaterial );
			enemyMeshes[i].duration = 1000;
			enemyMeshes[i].time = 1000 * Math.random();
			enemyMeshes[i].scale.set( 0.035, 0.035, 0.035 );
			enemyMeshes[i].rotation.x = 90;
			enemyMeshes[i].rotation.y = 89.5;
			enemyMeshes[i].updateMatrix();
			
			var position = enemies[i].getPosition( time );
			enemyMeshes.push(enemyMeshes[i]);
			var x = enemies[i].targetX = Math.floor( position.x ) * squareWidth + baseX;
			var y = enemies[i].targetY = -Math.floor( position.y + 1) * squareWidth + baseY;
			enemyMeshes[i].position.set(x, y, 0);
			scene.add ( enemyMeshes[i] );
		}
    }
	for (var i = 0; i < enemies.length; i++) {
		jsonLoader.load( './models/monster/monster.js', genFunction( i ) );
	}
}

// Updates the target position of each enemy
function updateEnemies() {
  for (var i = 0; i < enemies.length; i++) {
    enemyMeshes[i].x = enemies[i].targetX;
    enemyMeshes[i].y = enemies[i].targetY;
  
    var position = enemies[i].getPosition( time );
    enemies[i].targetX = Math.floor( position.x ) * squareWidth + baseX;
    enemies[i].targetY = -Math.floor( position.y ) * squareWidth + baseY;
  }
}