// animation.js
// Written by S.Wingrove 2009
// 
// Each object can have the following objects within (each with the following fields) left, top, width, height and opacity.
//	 init - The starting conditions (stored when SetWanted was called)
//	 wanted - the desired position
//	 trans - the type of transition to use for each parameter   
// Custom transitions can be added by the caller as a function or as an array. For example
//	function : animationCtrl.transltions.myTransition = 'obj.step / numSteps';
//	array : animationCtrl.transltions.myTransition = new Array(0, 0.2, 0.5, 0.8, 1);
//		In the case of using an arrays the value taken = step % arrayLength
//	In both cases 0 = init positon and 1 = final position.

animationCtrl = new Animation();

function Animation()
{
	this.timerID = 0;			// Indicates if the setTimeout is running and hence there is animation
	this.callBack = null; // The function to call when the animation is finished. 
	this.objs = new Array();	// The list of objects currently being animated. As objects reach their final position they are removed from this list.
	
	this.transitions = new Object;
	this.transitions.linear = 'obj.step / obj.numSteps'; 
	this.transitions.fast = 'Math.pow(obj.step / obj.numSteps, 0.5)'; 
	this.transitions.slow = 'Math.pow(obj.step / obj.numSteps, 2.0)'; 
	this.transitions.smooth = '0.5 + (Math.sin((obj.step / (obj.numSteps / Math.PI)) + (3 * Math.PI / 2))  / 2)'; //new Array();
	this.SetWanted = ANI_SetWanted;	  
	this.Revert = ANI_Revert;			// Put the object back to its starting position...
	this.MoveObjs = ANI_MoveObjs; // Steps the animation (normally called internally)
	this.Start = ANI_Start;
	this.CalcVal = ANI_CalcVal;
	this.SetTransition = ANI_SetTransition;
	
	this.std = new Object;
	this.std.trans = new Object;
	this.std.trans.left = this.std.trans.top = 'smooth';
	this.std.trans.width = this.std.trans.height = this.std.trans.opacity = 'linear';
	this.std.numSteps = 40; // The number of steps an object will take from start to finish
}

function ANI_ProcessMeasure(obj, field, d)
{
	if (typeof(d) == 'number') return d;
	var dir = d.substr(0,1);
	var distance = parseInt(d.substr(1));
	var s = parseInt(obj.style[field]);
	if (d.substr(d.length - 1, 1) == '%') distance = s * (distance / 100); 
	if (dir == '+') ret = s + distance;
	if (dir == '-') ret = s - distance;
	return Math.round(ret);
}

function ANI_SetTransition(id, l, t, w, h, o) // Pass id = null to set the default transition for all objects added
{	
	var obj = this.std;
	if (id != null) {
		obj = document.getElementById(id);
		if (obj == null) return null;
	}
	if (!obj.trans) obj.trans = new Object;
	if (l != null) obj.trans.left = l;
	if (t != null) obj.trans.top = t;
	if (w != null) obj.trans.width = w;
	if (h != null) obj.trans.height = h;
	if (o != null) obj.trans.opacity = o;
}

function ANI_SetWanted(id, l, t, w, h, o, callBack)  // o (opacity) range = 0 .. 1
// Example animateCtrl.SetWanted('myobjid', 100, null, '+30%', '-10%', 1, DelObject);
{
	var obj = document.getElementById(id);
	if (obj == null) return null;
	obj.wanted = new Object;
	if (l != null) obj.wanted.left = ANI_ProcessMeasure(obj, 'left', l);
	if (t != null) obj.wanted.top = ANI_ProcessMeasure(obj, 'top', t);
	if (w != null) obj.wanted.width = ANI_ProcessMeasure(obj, 'width', w);
	if (h != null) obj.wanted.height = ANI_ProcessMeasure(obj, 'height', h);
	if (o != null) obj.wanted.opacity = ANI_ProcessMeasure(obj, 'opacity', o);
	
	var inList = false;
	for (var x = 0; x < this.objs.length; x++) 
		if (this.objs[x].id == id) inList = true; // This object is already in the list, it will be redirected to the new location...
		
	if (!inList) this.objs[this.objs.length] = obj; // Add this object to the animation.
	
	if (!obj.trans) {
		obj.trans = new Object();
		for (var field in this.std.trans)
			obj.trans[field] = this.std.trans[field]; 
		obj.init = new Object;
	}
	obj.step = 0;
	obj.numSteps = this.std.numSteps; 
	obj.callBack = callBack;

	// Store the starting position
	l != null ? obj.init.left = parseInt(obj.style.left) : obj.init.left = null;
	t != null ? obj.init.top = parseInt(obj.style.top) : obj.init.top = null;
	w != null ? obj.init.width = parseInt(obj.style.width): obj.init.width = null;
	h != null ? obj.init.height = parseInt(obj.style.height) : obj.init.height = null;	 
	o != null ? obj.init.opacity = parseFloat(obj.style.opacity) : obj.init.opacity = null;
	if (obj.init.opacity != null) obj.style.display = 'block'; // If the obj's opacity is going to change make sure its visible during this time
	
	if (!obj.startPos) {
		// Store where this object originated from for Revert function.
		// To reset the starting position set the obj.startPos = null before calling SetWanted
		obj.startPos = new Object;
		obj.style.left ? obj.startPos.left = parseInt(obj.style.left) : obj.startPos.left = null;
		obj.style.top ? obj.startPos.top = parseInt(obj.style.top) : obj.startPos.top = null;
		obj.style.width ? obj.startPos.width = parseInt(obj.style.width) : obj.startPos.width = null;
		obj.style.height ? obj.startPos.height = parseInt(obj.style.height) : obj.startPos.height = null;
		obj.style.opacity ? obj.startPos.opacity = parseInt(obj.style.opacity) : obj.startPos.opacity = null;
	} 
	return obj;

}

function ANI_Revert(id, callBack)
{
	var obj = document.getElementById(id);
	if (obj == null) return;
	if (!obj.init) return;
	return this.SetWanted(id, obj.startPos.left, obj.startPos.top, obj.startPos.width, obj.startPos.height, obj.startPos.opacity, callBack); 
}

function ANI_IF_MoveObjs() { window.animationCtrl.MoveObjs(); }


function ANI_Start() { if (this.timerID == 0) this.MoveObjs(); }

function ANI_MoveObjs()
{
	this.timerID = 1;
	var more = false;
	for (var x = 0; x < this.objs.length; x++) {
		var obj = this.objs[x];
		obj.step++;
		
		this.CalcVal(obj, 'left');
		this.CalcVal(obj, 'top');
		this.CalcVal(obj, 'width');
		this.CalcVal(obj, 'height');
		this.CalcVal(obj, 'opacity');
			
		if (obj.step == obj.numSteps) {
			if (this.objs[x].wanted.opacity != null && this.objs[x].wanted.opacity == 0) this.objs[x].style.display = 'none'; // Hide the object if its its opacpity is zero
			for (var i = x; i < this.objs.length - 1; i++) 
				this.objs[i] = this.objs[i + 1];
			this.objs.length--;
			x--;
			if (obj.callBack != null) obj.callBack(obj); // Let the program know this object has finished its animation
		} else more = true; // Need another call.
	}
	if (more) this.timerID = setTimeout(ANI_IF_MoveObjs, 50);
	else {
		this.timerID = 0;
		if (this.callBack != null) this.callBack();
	}
}	

function ANI_CalcVal(obj, field)
{
	if (obj.wanted[field] == null) return; // No change because no movement
	var d = obj.wanted[field] - obj.init[field]; // The distance to travel.
	if (typeof(this.transitions[obj.trans[field]]) == 'string') eval('d *= ' + this.transitions[obj.trans[field]]);
	else {
		// Assumes the transition information is coming from an array.
		var trans = this.transitions[obj.trans[field]]; 
		d *= trans[obj.step % trans.length];   
	}
	if (field == 'opacity') {
		obj.style.filter = 'alpha(opacity=' + Math.round((d + obj.init.opacity) * 100) + ')';
		obj.style.opacity = d + obj.init.opacity;
	} else obj.style[field] = Math.round(d + obj.init[field]) + 'px';
}
