// Copyright 2001, Aaron Boodman
// This code is public domain. Please use it for good, not evil.

//=====================================================================
// 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 0.1 and 2 where 0.1 is full decelerated, 2 is 
//		  full accelerated, and 1 is linear (no acceleration). default
//		  is 1.
// 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;

    if (this.onend) {
      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;
