if(window.Asteroids == null)
	window.Asteroids= {};

///////////////////////////////////////////////////////////////////////////////
// Asteroids.SceneObject

Asteroids.getRandomEdgeCoordinates= function(edgeNumber)
{
	switch(edgeNumber)
	{
	case 0: // left
		return { x: LEFT_EDGE, y: Math.floor(Math.random() * EDGE_HEIGHT) + TOP_EDGE };
	case 1: // top
		return { x: Math.floor(Math.random() * EDGE_WIDTH) + LEFT_EDGE, y: TOP_EDGE };
	case 2: // right
		return { x: RIGHT_EDGE, y: Math.floor(Math.random() * EDGE_HEIGHT) + TOP_EDGE };
	case 3: // bottom
		return { x: Math.floor(Math.random() * EDGE_WIDTH) + LEFT_EDGE, y: BOTTOM_EDGE };
	}
}

Asteroids.SceneObject= function(scene, element, translateTransformName)
{
	var t= this;
	t.scene= scene;
	t.element= element || { findName: function() {} };
	t.translateTransform= t.element.findName(translateTransformName);
	t.isVisible= false;
	t.x= { valueOf: function() { return t.translateTransform.x; } };
	t.y= { valueOf: function() { return t.translateTransform.y; } };
	t.radius= t.dx= t.dy= noval;
	// dx and dy are the object's X and Y speeds, respectively.
	// Where applicable, ux and uy are the object's X and Y direction vector
	// components, respectively.
}

Asteroids.SceneObject.prototype.getCollisionTime= function(that)
{
	var d= this.radius + that.radius;
	var m= this.x - that.x, n= this.y - that.y;
	if((d *= d) >= m * m + n * n)
		return 0; // The objects are already overlapping.
	var u= this.dx - that.dx, v= this.dy - that.dy;
	var a= u * u + v * v, b= 2 * (u * m + v * n), c= m * m + n * n - d;
	d= b * b - 4 * a * c;
	if(d < 0)
		return Number.POSITIVE_INFINITY; // They never meet.
	a *= -2;
	d= Math.sqrt(d);
	var t1= (b + d) / a;
	var t2= (b - d) / a;
	// I don't need to check between them; they're both in either the future or
	// the past because the objects are not overlapping.
	var t= Math.min(t1, t2);
	if(t >= 0)
		return t;
	return Number.POSITIVE_INFINITY; // They previously met.
}

Asteroids.SceneObject.prototype.hasCollidedWith= function(that)
{
	if(this.isVisible && that.isVisible)
	{
		var d= this.radius + that.radius;
		var m= this.x - that.x, n= this.y - that.y;
		return d * d >= m * m + n * n;
	}
}

Asteroids.SceneObject.prototype.hide= function()
{
	this.element.visibility= "Collapsed";
	this.isVisible= false;
}

Asteroids.SceneObject.prototype.pause= function()
{
	this.storyboard.pause();
}

Asteroids.SceneObject.prototype.resume= function()
{
	this.storyboard.resume();
}

Asteroids.SceneObject.prototype.show= function()
{
	this.element.visibility= "Visible";
	this.isVisible= true;
}

Asteroids.SceneObject.prototype.wrapPath= function()
{
	var x= this.translateTransform.x, y= this.translateTransform.y, dx= this.dx, dy= this.dy;
	if(dx < 0 && x <= LEFT_EDGE)
		x += EDGE_WIDTH;
	else if(dx > 0 && x >= RIGHT_EDGE)
		x -= EDGE_WIDTH;
	if(dy < 0 && y <= TOP_EDGE)
		y += EDGE_HEIGHT;
	else if(dy > 0 && y >= BOTTOM_EDGE)
		y -= EDGE_HEIGHT;
	if(this.translateTransform.x != x || this.translateTransform.y != y)
	{
		this.translateTransform.x= x;
		this.translateTransform.y= y;
		return true;
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////////
// Asteroids.Alien

Asteroids.Alien= function(scene, changedHandler, shotClockHandler)
{
	Asteroids.SceneObject.call(this, scene, scene.getElement("alien"), "alienTranslateTransform");
	this.changedHandler= changedHandler;
	this.shotClock= this.element.findName("alienShotClock");
	this.shotClock.addEventListener("Completed", shotClockHandler);
	this.soundClock= this.element.findName("alienSoundClock");
	this.soundClock.addEventListener("Completed", $delegate(this, this.handleSoundCompleted));
	this.storyboard= this.element.findName("alienStoryboard");
	this.storyboard.addEventListener("Completed", $delegate(this, this.handleStoryboardCompleted));
	this.xAnimation= this.storyboard.children.getItem(0);
	this.yAnimation= this.storyboard.children.getItem(1);
	this.smallSound= this.element.findName("smallSaucerSound");
	this.largeSound= this.element.findName("largeSaucerSound");
}

Asteroids.Alien.prototype= new Asteroids.SceneObject();
Asteroids.Alien.prototype.constructor= Asteroids.Alien;

Asteroids.Alien.prototype.handleSoundCompleted= function(sender)
{
	if(!this.isVisible)
		return;
	this.sound.source= this.sound.source;
	this.sound.autoPlay= true;
	sender.begin();
}

Asteroids.Alien.prototype.handleStoryboardCompleted= function(sender)
{
	if(--this.part > 0)
	{
		var newLevel= Math.max(0, Math.min(this.level + Math.floor(Math.random() * ALIEN_MOVEMENT_GRID_SIZE) - 2, 4));
		var levelChange= newLevel - this.level;
		this.yAnimation.to= (this.level= newLevel) / ALIEN_MOVEMENT_GRID_SIZE * EDGE_HEIGHT + TOP_EDGE + EDGE_HEIGHT / ALIEN_MOVEMENT_GRID_SIZE / 2;
		this.dy= levelChange / ALIEN_MOVEMENT_GRID_SIZE * EDGE_HEIGHT / this.time;
		this.storyboard.begin();
		this.changedHandler(this);
	}
	else
	{
		this.shotClock.stop();
		this.soundClock.stop();
		this.sound.stop();
		this.hide();
	}
}

Asteroids.Alien.prototype.pause= function()
{
	if(!this.isVisible)
		return;
	Asteroids.SceneObject.prototype.pause.call(this);
	this.shotClock.pause();
	this.soundClock.pause();
}

Asteroids.Alien.prototype.resume= function()
{
	if(!this.isVisible)
		return;
	Asteroids.SceneObject.prototype.resume.call(this);
	this.shotClock.resume();
	this.soundClock.resume();
}

Asteroids.Alien.prototype.start= function(wantsSmall)
{
	this.storyboard.stop();
	this.sound= wantsSmall ? this.smallSound : this.largeSound;
	this.radius= (this.isSmall= wantsSmall) ? SMALL_ALIEN_RADIUS : LARGE_ALIEN_RADIUS;
	var alienScaleTransform= this.element.findName("alienScaleTransform");
	alienScaleTransform.scaleX= wantsSmall ? SMALL_ALIEN_SCALEX : 1;
	alienScaleTransform.scaleY= wantsSmall ? SMALL_ALIEN_SCALEY : 1;
	var goingLeft= Math.random() < 0.5;
	this.translateTransform.x= goingLeft ? RIGHT_EDGE : LEFT_EDGE;
	this.level= Math.floor(Math.random() * ALIEN_MOVEMENT_GRID_SIZE);
	this.part= ALIEN_MOVEMENT_GRID_SIZE;
	this.translateTransform.y= this.level / ALIEN_MOVEMENT_GRID_SIZE * EDGE_HEIGHT + TOP_EDGE + EDGE_HEIGHT / ALIEN_MOVEMENT_GRID_SIZE / 2;
	this.xAnimation.by= goingLeft ? -EDGE_WIDTH / ALIEN_MOVEMENT_GRID_SIZE : EDGE_WIDTH / ALIEN_MOVEMENT_GRID_SIZE;
	this.time= ALIEN_TIME;
	this.xAnimation.duration.seconds= this.yAnimation.duration.seconds= this.time;
	this.yAnimation.to= this.translateTransform.y;
	this.dx= this.xAnimation.by / this.time;
	this.dy= 0;
	this.shotClock.begin();
	this.soundClock.duration.seconds= wantsSmall ? SMALL_ALIEN_SOUND_DURATION : LARGE_ALIEN_SOUND_DURATION;
	this.storyboard.begin();
	this.show();
	this.handleSoundCompleted(this.soundClock);
}

Asteroids.Alien.prototype.stop= function()
{
	this.soundClock.stop();
	this.hide();
}

///////////////////////////////////////////////////////////////////////////////
// Asteroids.Bullet

Asteroids.Bullet= function(scene, bulletIndex, wrappedHandler, completedHandler)
{
	Asteroids.SceneObject.call(this, scene, scene.getElement("bullet" + bulletIndex), "bulletTranslateTransform" + bulletIndex);
	this.index= bulletIndex;
	this.radius= 0;
	this.storyboard= this.element.findName("bulletStoryboard" + this.index);
	this.storyboard.addEventListener("Completed", $delegate(this, this.handleStoryboardCompleted));
	this.xAnimation= this.storyboard.children.getItem(0);
	this.yAnimation= this.storyboard.children.getItem(1);
	this.time= BULLET_TIME;
	this.wrappedHandler= wrappedHandler || function() {};
	this.completedHandler= completedHandler || function() {};
}

Asteroids.Bullet.prototype= new Asteroids.SceneObject();
Asteroids.Bullet.prototype.constructor= Asteroids.Bullet;

Asteroids.Bullet.prototype.handleStoryboardCompleted= function(sender)
{
	if(this.timeLeft > 0)
	{
		this.wrapPath();
		this.setAnimations(this.translateTransform.x, this.translateTransform.y, this.dx, this.dy);
		this.wrappedHandler(this);
	}
	else
	{
		this.completedHandler(this);
		this.hide();
	}
}

Asteroids.Bullet.prototype.setAnimations= function(x0, y0, dx, dy)
{
	var t= this.timeLeft;
	var x= x0 + t * dx;
	var y= y0 + t * dy;
	if(dy < 0 && y < TOP_EDGE)
	{
		t *= (TOP_EDGE - y0) / (y - y0);
		x= x0 + t * dx;
		y= TOP_EDGE;
	}
	else if(dy > 0 && y > BOTTOM_EDGE)
	{
		t *= (BOTTOM_EDGE - y0) / (y - y0);
		x= x0 + t * dx;
		y= BOTTOM_EDGE;
	}
	if(dx < 0 && x < LEFT_EDGE)
	{
		t *= (LEFT_EDGE - x0) / (x - x0);
		x= LEFT_EDGE;
		y= y0 + t * dy;
	}
	else if(dx > 0 && x > RIGHT_EDGE)
	{
		t *= (RIGHT_EDGE - x0) / (x - x0);
		x= RIGHT_EDGE;
		y= y0 + t * dy;
	}
	this.xAnimation.to= x;
	this.yAnimation.to= y;
	this.xAnimation.duration.seconds= this.yAnimation.duration.seconds= t;
	this.timeLeft -= t;
	this.storyboard.begin();
}

Asteroids.Bullet.prototype.start= function(x0, y0, ux, uy, dx, dy)
{
	if(x0 < LEFT_EDGE)
		x0 += EDGE_WIDTH;
	else if(x0 > RIGHT_EDGE)
		x0 -= EDGE_WIDTH;
	if(y0 < TOP_EDGE)
		y0 += EDGE_HEIGHT;
	else if(y0 > BOTTOM_EDGE)
		y0 -= EDGE_HEIGHT;
	this.timeLeft= this.time;
	this.setAnimations(this.translateTransform.x= x0, this.translateTransform.y= y0,
		this.dx= dx += BULLET_SPEED * ux, this.dy= dy += BULLET_SPEED * uy);
	this.show();
}

Asteroids.Bullet.prototype.stop= function()
{
	this.hide();
	this.storyboard.stop();
}

///////////////////////////////////////////////////////////////////////////////
// Asteroids.Rock

Asteroids.Rock= function(scene, rockIndex, wrappedHandler)
{
	Asteroids.SceneObject.call(this, scene, scene.getElement("rock" + rockIndex), "rockTranslateTransform" + rockIndex);
	this.index= rockIndex;
	this.rotateStoryboard= this.element.findName("rockRotateStoryboard" + this.index);
	this.translateStoryboard= this.element.findName("rockTranslateStoryboard" + this.index);
	this.translateStoryboard.addEventListener("Completed", $delegate(this, this.handleStoryboardCompleted));
	this.xAnimation= this.translateStoryboard.children.getItem(0);
	this.yAnimation= this.translateStoryboard.children.getItem(1);
	this.wrappedHandler= wrappedHandler || function() {};
}

Asteroids.Rock.PARAMETERS= [
	{ minimumSpeed: .2, speedRange: .1, scale: .6, value: 10, fragments: noval },	// large
	{ minimumSpeed: .3, speedRange: .2, scale: .4, value: 20, fragments: noval },	// medium
	{ minimumSpeed: .5, speedRange: .4, scale: .2, value: 50, fragments: 0 },	// small
];

Asteroids.Rock.prototype= new Asteroids.SceneObject();
Asteroids.Rock.prototype.constructor= Asteroids.Rock;

Asteroids.Rock.prototype.handleStoryboardCompleted= function(sender)
{
	this.wrapPath();
	this.setDestination();
	var distance= Math.sqrt(this.xd * this.xd + this.yd * this.yd);
	var time= distance / this.speed;
	this.xAnimation.duration.seconds= this.yAnimation.duration.seconds= time;
	this.translateStoryboard.begin();
	this.wrappedHandler(this);
}

Asteroids.Rock.prototype.pause= function()
{
	this.rotateStoryboard.pause();
	this.translateStoryboard.pause();
}

Asteroids.Rock.prototype.resume= function()
{
	this.rotateStoryboard.resume();
	this.translateStoryboard.resume();
}

Asteroids.Rock.prototype.setDestination= function()
{
	var x= this.translateTransform.x, y= this.translateTransform.y;
	var dx= this.dx, dy= this.dy, m= dy / dx;
	var b= y - m * x;
	var x1, y1;
	if(isFinite(y1= m * (x1= dx < 0 ? LEFT_EDGE : dx > 0 ? RIGHT_EDGE : x) + b))
	{
		var w= dx / dy;
		var d= x - w * y;
		if(dy < 0 && y1 < TOP_EDGE)
			x1= w * (y1= TOP_EDGE) + d;
		else if(dy > 0 && y1 > BOTTOM_EDGE)
			x1= w * (y1= BOTTOM_EDGE) + d;
	}
	else
		y1= dy < 0 ? TOP_EDGE : BOTTOM_EDGE;
	this.xd= x1 - x;
	this.yd= y1 - y;
	this.xAnimation.to= x1;
	this.yAnimation.to= y1;
}

Asteroids.Rock.prototype.start= function(rockSizeIndex, x, y)
{
	this.sizeIndex= rockSizeIndex;
	this.rotateStoryboard.stop();
	this.translateStoryboard.stop();
	var rotateTransform= this.element.findName("rockRotateTransform" + this.index);
	rotateTransform.angle= 360 * Math.random();
	var scaleTransform= this.element.findName("rockScaleTransform" + this.index);
	var parameters= Asteroids.Rock.PARAMETERS[rockSizeIndex];
	var scale= parameters.scale;
	this.radius= ROCK_BASE_RADIUS * scale;
	scaleTransform.scaleX= scaleTransform.scaleY= scale;
	this.element.strokeThickness= 1.0 / scale;
	var angleAnimation= this.rotateStoryboard.children.getItem(0);
	if(Math.random() < 0.5)
		angleAnimation.by *= -1;
	angleAnimation.duration.seconds= 3 + 2 * Math.random();
	var startingEdge= Math.floor(Math.random() * 4);
	var origin= isNaN(x) ? Asteroids.getRandomEdgeCoordinates(startingEdge) : { x: x, y: y };
	this.translateTransform.x= origin.x;
	this.translateTransform.y= origin.y;
	var destination= Asteroids.getRandomEdgeCoordinates(Math.floor(startingEdge + 1 + Math.random() * 3) % 4);
	this.xAnimation.to= destination.x;
	this.yAnimation.to= destination.y;
	this.speed= ROCK_BASE_SPEED * (Math.random() * parameters.speedRange + parameters.minimumSpeed);
	this.xd= destination.x - origin.x;
	this.yd= destination.y - origin.y;
	var distance= Math.sqrt(this.xd * this.xd + this.yd * this.yd);
	var time= distance / this.speed;
	this.dx= this.xd / time;
	this.dy= this.yd / time;
	this.xAnimation.duration.seconds= this.yAnimation.duration.seconds= time;
	this.rotateStoryboard.begin();
	this.translateStoryboard.begin();
	this.show();
}

Asteroids.Rock.prototype.stop= function()
{
	this.hide();
	this.rotateStoryboard.stop();
	this.translateStoryboard.stop();
}

///////////////////////////////////////////////////////////////////////////////
// Asteroids.Ship

Asteroids.Ship= function(scene, changedHandler, movedHandler)
{
	Asteroids.SceneObject.call(this, scene, scene.getElement("ship"), "shipTranslateTransform");
	this.radius= SHIP_RADIUS;
	this.rotateStoryboard= this.element.findName("shipRotateStoryboard");
	this.angleAnimation= this.rotateStoryboard.children.getItem(0);
	this.translateStoryboard= this.element.findName("shipTranslateStoryboard");
	this.translateStoryboard.addEventListener("Completed", $delegate(this, this.handleStoryboardCompleted));
	this.xAnimation= this.translateStoryboard.children.getItem(0);
	this.yAnimation= this.translateStoryboard.children.getItem(1);
	this.time= this.xAnimation.duration.seconds;
	this.rotateTransform= this.element.findName("shipRotateTransform");
	this.thrusting= this.isBallistic= false;
	this.thrustClock= this.element.findName("thrustClock");
	this.thrustClock.addEventListener("Completed", $delegate(this, this.handleThrustCompleted));
	this.thrustSound= this.element.findName("thrustSound");
	this.exhaustStoryboard= this.element.findName("exhaustStoryboard");
	this.changedHandler= changedHandler || function() {};
	this.movedHandler= movedHandler || function() {};
}

Asteroids.Ship.prototype= new Asteroids.SceneObject();
Asteroids.Ship.prototype.constructor= Asteroids.Ship;

Asteroids.Ship.prototype.getUnits= function()
{
	var angle= this.rotateTransform.angle * Math.PI / 180;
	return { x: Math.sin(angle), y: -Math.cos(angle) };
}

Asteroids.Ship.prototype.handleStoryboardCompleted= function(sender)
{
	var changed= this.wrapPath();
	if(this.thrusting)
	{
		var u= this.getUnits();
		var dx= this.xAnimation.by + u.x * SHIP_ACCELERATION;
		var dy= this.yAnimation.by + u.y * SHIP_ACCELERATION;
		var speed= dx * dx + dy * dy;
		if(speed > MAX_SHIP_SPEED_SQUARED)
		{
			speed= Math.sqrt(MAX_SHIP_SPEED_SQUARED / speed);
			dx *= speed;
			dy *= speed;
		}
		if(this.xAnimation.by != dx || this.yAnimation.by != dy)
		{
			this.xAnimation.by= dx;
			this.yAnimation.by= dy;
			this.dx= dx / this.time;
			this.dy= dy / this.time;
			this.setBallistic(changed= false);
			this.movedHandler(this);
		}
		else
			changed |= this.setBallistic(true);
	}
	else
		changed |= this.setBallistic(true);
	sender.begin();
	if(changed)
		this.changedHandler(this);
}

Asteroids.Ship.prototype.handleThrustCompleted= function(sender)
{
	this.thrustSound.source= this.thrustSound.source;
	this.thrustSound.autoPlay= true;
	sender.begin();
}

Asteroids.Ship.prototype.jump= function()
{
	this.hide();
	this.thrustClock.stop();
	this.thrustSound.stop();
	this.exhaustStoryboard.stop();
	this.translateStoryboard.stop();
	this.translateTransform.x= EDGE_WIDTH * Math.random() + LEFT_EDGE;
	this.translateTransform.y= EDGE_HEIGHT * Math.random() + TOP_EDGE;
	this.dx= this.dy= 0;
	this.isBallistic= true;
	this.xAnimation.by= this.yAnimation.by= 0;
	this.show();
	this.movedHandler(this);
}

Asteroids.Ship.prototype.pause= function()
{
	this.thrustClock.stop();
	this.thrustSound.stop();
	this.exhaustStoryboard.stop();
	this.rotateStoryboard.pause();
	this.translateStoryboard.pause();
}

Asteroids.Ship.prototype.resume= function()
{
	this.rotateStoryboard.resume();
	this.translateStoryboard.resume();
}

Asteroids.Ship.prototype.setBallistic= function(isBallistic)
{
	var changed= this.isBallistic != isBallistic;
	this.isBallistic= isBallistic;
	return changed;
}

Asteroids.Ship.prototype.start= function(x, y)
{
	this.thrustClock.stop();
	this.thrustSound.stop();
	this.exhaustStoryboard.stop();
	this.rotateStoryboard.stop();
	this.translateStoryboard.stop();
	this.angleAnimation.by= this.xAnimation.by= this.yAnimation.by= 0;
	this.rotateTransform.angle= 0;
	this.translateTransform.x= x;
	this.translateTransform.y= y;
	this.direction= 0;
	this.dx= this.dy= 0;
	this.isBallistic= true;
	this.show();
}

Asteroids.Ship.prototype.stop= function()
{
	this.thrustClock.stop();
	this.thrustSound.stop();
	this.thrusting= this.isBallistic= false;
	this.hide();
	this.rotateStoryboard.stop();
	this.translateStoryboard.stop();
}

Asteroids.Ship.prototype.thrust= function(thrusting)
{
	if(this.thrusting != thrusting && this.isVisible)
	{
		if(this.thrusting= thrusting)
		{
			this.translateStoryboard.begin();
			this.handleThrustCompleted(this.thrustClock);
			this.exhaustStoryboard.begin();
		}
		else
		{
			this.thrustClock.stop();
			this.exhaustStoryboard.stop();
			this.changedHandler(this);
		}
	}
}

Asteroids.Ship.prototype.turn= function(direction)
{
	if(this.direction != direction && this.isVisible)
	{
		this.angleAnimation.by= direction * 360;
		this.rotateStoryboard.begin();
		this.direction= direction;
	}
}

