var ui;
$( document ).ready( function() {
	ui = new UI( '#container' );
	$( document ).keydown( { ui: ui }, ui.keydown );
	$( '#timePlus' ).click( function() {
		ui.setTime( ui.time + 1 );
	});
	$( '#timeMinus' ).click( function() {
		ui.setTime( ui.time - 1 );
	});
});

var worldHeight = 30;
var worldWidth = 30;

function UI( container )
{
	this.time = 0;
	this.world = new World( worldHeight, worldWidth );
	this.renderHTML( container );

	console.log( this.world.getState( 0 ) );
	this.update();
}

UI.prototype.renderHTML = function( container )
{
	var genClosure = function( object, x, y )
	{
		return function() {
			object.click( x, y );
		}
	}

	var table = $( "<table class='worldTable'></table>" );

	for ( var i = 0; i < worldHeight; i++ )
	{
		var row = $( "<tr class='worldRow'></tr>" );
		for ( var j = 0; j < worldWidth; j++ )
		{
			var cell = $( "<td class='worldCell'></td>" );
			cell.addClass( this.getCellClass( j, i ) );
			cell.click( genClosure( this, j, i ) );
			row.append( cell );
		}
		table.append( row );
	}

	$( container ).append( table );
}

UI.prototype.click = function( x, y )
{
	console.log( this );
	$( '.worldCell' ).removeClass( 'active' );

	var active_thing = null;

	// Find the thing that is at (x, y ).
	for ( var i = 0; i < this.state.length; i++ )
	{
		if ( this.state[i].x == x && this.state[i].y == y )
			active_thing = this.state[i];
	}

	if ( !active_thing )
	{
		this.active_agent = null;
		return;
	}
	else if ( active_thing.type == 'agent' )
	{
		var cellClass = this.getCellClass( x, y );
		$( '.' + cellClass ).addClass( 'active' );
		this.active_agent = active_thing.agent;
	}
}

UI.prototype.getCellClass = function( x, y )
{
	return "cell_" + x + "_" + y;
}

UI.prototype.update = function()
{
	this.state = this.world.getState( this.time );
	$( '#currentTime' ).html( this.time );

	$( '.worldCell' ).removeClass( 'agent' );
	$( '.worldCell' ).removeClass( 'enemy' );
	$( '.worldCell' ).removeClass( 'active' );
	this.active_agent = null;

	for ( var i = 0; i < this.state.length; i++ )
	{
		var st = this.state[i];
		var cellClass = this.getCellClass( st.x, st.y );
		
		switch ( st.type )
		{
		case "agent":
			$( '.' + cellClass ).addClass( 'agent' );
			break;
		case "enemy":
			$( '.' + cellClass ).addClass( 'enemy' );
			break;

		}
	}
}

UI.prototype.keydown = function( event )
{
	// @this isn't set properly.
	var ui = event.data.ui;

	if ( !ui.active_agent )
		return;

	if ( event.keyCode >= 37 && event.keyCode <= 40 )
	{
		if ( event.keyCode == 37 )
		{
			// left
			xOffset = -1; yOffset = 0;
		}
		else if ( event.keyCode == 38 )
		{
			// up
			xOffset = 0; yOffset = -1;
		}
		else if ( event.keyCode == 39 )
		{
			// right
			xOffset = 1; yOffset = 0;
		}
		else if ( event.keyCode == 40 )
		{
			// down
			xOffset = 0; yOffset = 1;
		}

		// Move
		ui.active_agent.move( ui.time, xOffset, yOffset );

		// Advance time.
		ui.setTime( ui.time + 1 );
	}
	else if ( event.keyCode == 84 )
	{
		// 't' for "time travel".
		ui.active_agent.timeTravel( ui.time );
		ui.update();
//		ui.setTime( ui.time + 1 );
	}
}

UI.prototype.setTime = function( time )
{
	this.time = time;
	this.update();
}

/* World */
function World( height, width )
{
	this.height = height;
	this.width = width;
	this.agents = [];
	this.blocks = [];
	this.enemies = [];

	var linearAgent = new Agent( this, null, 0, 0, 0 );
	this.agents.push( linearAgent );

	var en = new Enemy( this, 20, 20 );
	this.enemies.push( en );
}

World.prototype.getState = function( time )
{
	var state = [];
	
	for ( var i = 0; i < this.agents.length; i++ )
	{
		var agent = this.agents[i];

		var location = agent.getLocation( time );
		if ( location )
		{
			state.push( {
				type: 'agent',
				agent: agent,
				x: location[0],
				y: location[1]
			});
		}
	}

	for ( var i = 0; i < this.enemies.length; i++ )
	{
		var enemy = this.enemies[i];
		var location = enemy.getLocation( time );

		state.push({
			type: 'enemy',
			x: location[0],
			y: location[1]
		});
	}

	return state;
}

/* For enemies. */
World.prototype.nearestAgent = function( time, x, y )
{
	var nearest_distance = 100000;
	var nearest = null;

	for ( var i = 0; i < this.agents.length; i++ )
	{
		var location = this.agents[i].getLocation( time );
		if ( !location )
			continue;

		var distance = ( location[0] - x )*( location[0] - x ) + ( location[1] - y )*( location[1] - y );
		if ( distance < nearest_distance )
		{
			nearest_distance = distance;
			nearest = this.agents[i];
		}
	}

	return nearest;
}

/* Agents and enemies keep track of their own location:
   the world doesn't. */
function Agent( world, parent, startTime, x, y )
{
	this.world = world;
	this.parent = parent;
	this.startTime = startTime;

	this.locations = {};
	this.locations[startTime] = [ x, y ];
}

Agent.prototype.getLocation = function( time )
{
	if ( time in this.locations )
		return this.locations[time];
	else
		return null;
}

Agent.prototype.move = function( time, xOffset, yOffset )
{
	// We can only move one space in x or y direction.
	if ( !( xOffset == 0 || yOffset == 0 ) )
		return;

	if ( Math.abs( xOffset ) > 1 || Math.abs( yOffset > 1 ) )
		return;

	// We have to exist into the time if we didn't already.
	if ( this.endTime < time + 1 )
		this.endTime = time + 1;

	// Maybe: "fix up" the future with minimum modifications
	//  to fit the new command. Until then, kill the future.

	// Invalidate all future locations.
	for ( var i in this.locations )
	{
		if ( !this.locations.hasOwnProperty( i ) )
			continue;

		if ( i > time )
			delete this.locations[i];
	}

	// Invalidate any children begun in the future.
	// XXX: TODO

	// Switch to the new position.
	this.locations[time + 1] = [ this.locations[time][0] + xOffset, this.locations[time][1] + yOffset ];
}

Agent.prototype.timeTravel = function( time )
{
	var location = this.getLocation( time );

	var child = new Agent( this.world, this, time, location[0] + 1, location[1] );
	this.world.agents.push( child );
}

/* Enemies */
// Starts at position x, y.
function Enemy( world, x, y )
{
	this.startX = x;
	this.startY = y;
	this.world = world;
}

// Recursively get location. This would be best with a memoizing cache.
Enemy.prototype.getLocation = function( time )
{
	if ( time == 0 )
		return [ this.startX, this.startY ];

	var previous_location = this.getLocation( time - 1 );
	var nearest_agent = this.world.nearestAgent( time, previous_location[0], previous_location[1] );

	if ( !nearest_agent )
		return previous_location;

	var nearest_location = nearest_agent.getLocation( time );

	var xDist = previous_location[0] - nearest_location[0];
	var yDist = previous_location[1] - nearest_location[1];
	var xOffset = 0;
	var yOffset = 0;

	if ( Math.abs( xDist ) > Math.abs( yDist ) )
		xOffset = - xDist / Math.abs( xDist );
	else
		yOffset = - yDist / Math.abs( yDist );

	return [ previous_location[0] + xOffset, previous_location[1] + yOffset ];
}