//=====================================================================
// Accel[erated] [an]imation object
// change a property of an object over time in an accelerated fashion
//=====================================================================
// obj  : reference to the object whose property you'd like to animate
// prop : property you would like to change eg: "left"
// to   : final value of prop
// time : time the animation should take to run
// zip	: optional. specify the zippiness of the acceleration. pick a 
//		  number between -1 and 1 where -1 is full decelerated, 1 is 
//		  full accelerated, and 0 is linear (no acceleration). default
//		  is 0.
// unit	: optional. specify the units for use with prop. default is 
//		  "px".
//=====================================================================

function Accelimation(obj, prop, to, time, zip, unit) {
	if (typeof zip  == "undefined") zip  = 0;
	if (typeof unit == "undefined") unit = "px";

	if (zip > 2 || zip <= 0)
		throw new Error("Illegal value for zip. Must be less than or equal to 2 and greater than 0.");

	this.obj	= obj;
	this.prop	= prop;
	this.x1		= to;
	this.dt		= time;
	this.zip	= zip;
	this.unit	= unit;

	this.x0		= parseInt(this.obj[this.prop]);
	this.D		= this.x1 - this.x0;
	this.A		= this.D / Math.abs(Math.pow(time, this.zip));
	this.id		= Accelimation.instances.length;
	this.onend	= null;
}



//=====================================================================
// public methods
//=====================================================================

// after you create an accelimation, you call this to start it-a runnin'
Accelimation.prototype.start = function() {
	this.t0 = new Date().getTime();
	this.t1 = this.t0 + this.dt;
	var dx	= this.x1 - this.x0;
	Accelimation._add(this);
}

// and if you need to stop it early for some reason...
Accelimation.prototype.stop = function() {
	Accelimation._remove(this);
}



//=====================================================================
// private methods
//=====================================================================

// paints one frame. gets called by Accelimation._paintAll.
Accelimation.prototype._paint = function(time) {
	if (time < this.t1) {
		var elapsed = time - this.t0;
		this.obj[this.prop] = Math.abs(Math.pow(elapsed, this.zip)) * this.A + this.x0 + this.unit;
	}
	else this._end();
}

// ends the animation
Accelimation.prototype._end = function() {
	Accelimation._remove(this);
	this.obj[this.prop] = this.x1 + this.unit;
	this.onend();
}




//=====================================================================
// static methods (all private)
//=====================================================================

// add a function to the list of ones to call periodically
Accelimation._add = function(o) {
	var index = this.instances.length;
	this.instances[index] = o;
	// if this is the first one, start the engine
	if (this.instances.length == 1) {
		this.timerID = window.setInterval("Accelimation._paintAll()", this.targetRes);
	}
}

// remove a function from the list
Accelimation._remove = function(o) {
	for (var i = 0; i < this.instances.length; i++) {
		if (o == this.instances[i]) {
			this.instances = this.instances.slice(0,i).concat( this.instances.slice(i+1) );
			break;
		}
	}
	// if that was the last one, stop the engine
	if (this.instances.length == 0) {
		window.clearInterval(this.timerID);
		this.timerID = null;
	}
}

// "engine" - call each function in the list every so often
Accelimation._paintAll = function() {
	var now = new Date().getTime();
	for (var i = 0; i < this.instances.length; i++) {
		// small chance that this accelimation could get dropped in the queue in the middle
		// of a run. That means that it could have a t0 greater than "now", which means that
		// elapsed would be negative.
		this.instances[i]._paint(Math.max(now, this.instances[i].t0));
	}
}


//=====================================================================
// static properties
//=====================================================================

Accelimation.instances = [];
Accelimation.targetRes = 10;
Accelimation.timerID = null;
