// JavaScript Document

var framerate = 40;
var framedelay = 1000 / framerate;
var firstdelay = 500;
var framenr = 0;
var currentAnimators = new AnimatorList();
var saveElemStateCache = new Object();

var frametimer = window.setTimeout("renderFrame();", firstdelay);    
   
function debugMsg(msg) {
	document.getElementById("debugInfo").innerHTML += msg + "<br>\n";
}
   
function renderFrame() {
	framenr++;
	//debugMsg("Frame "+framenr+" --- "+currentAnimators.size()+" animators running.");
	for (var i=0; i<currentAnimators.size(); i++) {
		var animator = currentAnimators.get(i);
		if (!animator.finished) {
			animator.animate();
		}
	}
	// set timer for next frame
	frametimer = window.setTimeout("renderFrame();", framedelay);
}

function startAnimator(elem, animator, params) {
	var id = elem.id;
	if (!id) {
		alert("Elements without id cannot be animated.");
		return false;
	}
	currentAnimators.removeById( id ); // remove existing animator for the same element
	currentAnimators.add( eval("new "+animator+"(elem, id, params)") );
}

function stopAllAnimators() {
	currentAnimators.removeAll();
}

function saveElemState(id) {
	var elem = document.getElementById(id);
	saveElemStateCache[id] = new Object();
	saveElemStateCache[id]["x"] = elem.style.marginLeft;
	saveElemStateCache[id]["y"] = elem.style.marginTop;
	saveElemStateCache[id]["w"] = elem.style.width;
	saveElemStateCache[id]["h"] = elem.style.height;
	saveElemStateCache[id]["opacity"] = getOpacity(elem);
}

function restoreElemState(id) {
	var elem = document.getElementById(id);
	elem.style.marginLeft = saveElemStateCache[id]["x"];
	elem.style.marginTop = saveElemStateCache[id]["y"];
	elem.style.width = saveElemStateCache[id]["w"];
	elem.style.height = saveElemStateCache[id]["h"];
	setOpacity(elem, saveElemStateCache[id]["opacity"]);
}

function AnimatorList() {
	this.animators = new Array();
	
	this.size = function() {
		return this.animators.length;
	}
	
	this.add = function(animator) {
		this.animators.push(animator);
	}
	
	this.get = function(i) {
		return this.animators[i];
	}
	
	this.getById = function(id) {
		var i = this.find(id);
		if (i != -1) {
			return this.get(i);
		} else {
			return null;
		}
	}

	this.find = function(id) {
		for (var i=0; i<this.animators.length; i++) {
			var animator = this.animators[i];
			if (animator.id == id) {
				return i;
			}
		}
		return -1;
	}
			
	this.remove = function(i) {
		var animatorsNew = new Array();
		for (var j=0; j<this.animators.length; j++) {
			if (j != i) {
				animatorsNew.push(this.animators[j]);
			}
		}
		this.animators = animatorsNew;
	}

	this.removeById = function(id) {
		var i = this.find(id);
		if (i != -1) {
			this.remove(i);
		}
	}
	
	this.removeAll = function() {
		while(this.animators.length > 0) {
			this.remove(this.animators.length-1);
		}
	}
}


////
//// CSS helper functions
////

function getOpacity(elem) {
	var value = elem.style.opacity;
	if (!value || value == "") {
		return 1.0;
	} else {
		return value;
	}
}

function setOpacity(elem, value) {
	if (value == 0.0) {
		elem.style.visibility = 'hidden';
	} else {
		elem.style.visibility = 'visible';
	}
	elem.style.opacity = value;
	elem.style.filter = 'alpha(opacity=' + value*100 + ')';
}

////
//// Animators
////

/**
 * Abstract Animator.
 */
function AbstractAnimator(elem, id, params) {
	this.elem = elem;
	this.id = id;
	this.params = params;
	// getParam funtion
	this.getParam = function(name) {
		var paramsArray = this.params.split(",");
		for (var i=0; i<paramsArray.length; i++) {
			var parArray = paramsArray[i].split("=");
			var parName = parArray[0];
			if (parName == name) {
				var parValue = parArray[1];
				return parValue;
			}
		}
		return null;
	}
	// read parameters
	this.x = this.getParam("x");
	this.y = this.getParam("y");
	this.w = this.getParam("w");
	this.h = this.getParam("h");
	this.opacity = this.getParam("opacity");
	this.t = this.getParam("t");
	this.onfinish = this.getParam("onfinish");
	this.alert = this.getParam("alert");
	this.onalert = this.getParam("onalert");
	// set other values
	this.finished = false;
	this.startX = parseInt(this.elem.style.marginLeft);
	this.startY = parseInt(this.elem.style.marginTop);
	this.startW = parseInt(this.elem.style.width);
	this.startH = parseInt(this.elem.style.height);
	this.startOpacity = getOpacity(this.elem);
	this.currentX = this.startX;
	this.currentY = this.startY;
	this.currentW = this.startW;
	this.currentH = this.startH;
	this.currentOpacity = this.startOpacity;
	this.xChange = this.x - this.startX;
	this.yChange = this.y - this.startY;
	this.wChange = this.w - this.startW;
	this.hChange = this.h - this.startH;
	this.opacityChange = this.opacity - this.startOpacity;
	this.currentFrame = 0;
	this.numberOfFrames = Math.round(this.t / framedelay);

	/**
	 * Render one frame.
	 */
	this.animate = function() {
		this.currentFrame++;
		if (this.currentFrame >= this.numberOfFrames) {
			this.finish();
		} else {
			// calculate coordinates
			this.currentX = this.ease(this.currentFrame, this.startX, this.xChange, this.numberOfFrames);
			this.currentY = this.ease(this.currentFrame, this.startY, this.yChange, this.numberOfFrames);
			if (this.w != null) {
				this.currentW = this.ease(this.currentFrame, this.startW, this.wChange, this.numberOfFrames);
			}
			if (this.h != null) {
				this.currentH = this.ease(this.currentFrame, this.startH, this.hChange, this.numberOfFrames);
			}
			if (this.opacity != null) {
				this.currentOpacity = this.ease(this.currentFrame, this.startOpacity, this.opacityChange, this.numberOfFrames);
			}
			// update element
			this.elem.style.marginLeft = this.currentX + "px";
			this.elem.style.marginTop = this.currentY + "px";
			if (this.w != null) {
				this.elem.style.width = this.currentW + "px";
			}
			if (this.h != null) {
				this.elem.style.height = this.currentH + "px";
			}
			if (this.opacity != null) {
				setOpacity(this.elem, this.currentOpacity);
			}
			// call alert?
			if (this.alert != null) {
				if ((1.0*this.currentFrame)/(1.0*this.numberOfFrames) >= this.alert) {
					this.alert = null;
					eval(this.onalert);
				}
			}
		}
	}
	
	/**
	 * Render last frame.
	 */
	this.finish = function() {
		this.currentFrame++;
		// calculate coordinates
		this.currentX = this.x;
		this.currentY = this.y;
		// update element
		this.elem.style.marginLeft = this.currentX;
		this.elem.style.marginTop = this.currentY;
		// set finished state
		this.finished = true;
		// remove animator
		currentAnimators.removeById(this.id);
		// execute onfinish
		if(this.onfinish != null) {
			eval(this.onfinish);
		}
	}
	
}

/**
 * LinearAnimator, extends AbstractAnimator.
 */
function LinearAnimator(elem, id, params) {

	this.base = AbstractAnimator;
	this.base(elem, id, params);
					
	/**
	 * Easing calculation.
	 * @param t     current time
	 * @param b     beginning value
	 * @param c     change in value
	 * @param d     duration, change in time
	 * @returns     current value
	 */
	this.ease = function(t,b,c,d) {
		var r, changePerTime;
		changePerTime = c / d;
		r = b + (t * changePerTime);
		return r;
	}

}

/**
 * CubicAnimator, extends AbstractAnimator.
 */
function CubicAnimator(elem, id, params) {

	this.base = AbstractAnimator;
	this.base(elem, id, params);
			
	/**
	 * Easing points.
	 * {Mx:0,My:0,Nx:44,Ny:-132,Px:8,Py:13}
	 * {Mx:52,My:-119,Nx:54,Ny:-92,Px:10,Py:26}
	 * {Mx:116,My:-185,Nx:58,Ny:-32,Px:26,Py:17}
	 * {Mx:200, My:-200}
	 */
	this.easingPoints = new Array();
	this.easingPoints[0] = new Object();
	this.easingPoints[0]["Mx"] = 0;
	this.easingPoints[0]["My"] = 0;
	this.easingPoints[0]["Nx"] = 44;
	this.easingPoints[0]["Ny"] = -132;
	this.easingPoints[0]["Px"] = 8;
	this.easingPoints[0]["Py"] = 13;
	this.easingPoints[1] = new Object();
	this.easingPoints[1]["Mx"] = 52;
	this.easingPoints[1]["My"] = -119;
	this.easingPoints[1]["Nx"] = 54;
	this.easingPoints[1]["Ny"] = -92;
	this.easingPoints[1]["Px"] = 10;
	this.easingPoints[1]["Py"] = 26;
	this.easingPoints[2] = new Object();
	this.easingPoints[2]["Mx"] = 116;
	this.easingPoints[2]["My"] = -185;
	this.easingPoints[2]["Nx"] = 58;
	this.easingPoints[2]["Ny"] = -32;
	this.easingPoints[2]["Px"] = 26;
	this.easingPoints[2]["Py"] = 17;
	this.easingPoints[3] = new Object();
	this.easingPoints[3]["Mx"] = 200;
	this.easingPoints[3]["My"] = -200;
	this.easingPoints[3]["Nx"] = 0;
	this.easingPoints[3]["Ny"] = 0;
	this.easingPoints[3]["Px"] = 0;
	this.easingPoints[3]["Py"] = 0;
	
	/**
	 * Easing calculation.
	 * @param t     current time
	 * @param b     beginning value
	 * @param c     change in value
	 * @param d     duration, change in time
	 * @returns     current value
	 */
	this.ease = function(t,b,c,d) {
		var i,r;

		r = 200 * t/d;
		
		for(i=0; r>this.easingPoints[i+1]["Mx"]; i++) {
		}
		
		i = this.easingPoints[i];

		if (i["Px"] != 0) {
			r = (-i["Nx"]+Math.sqrt(i["Nx"]*i["Nx"]-4*i["Px"]*(i["Mx"]-r)))/(2*i["Px"]);
		} else {
			r=-(i["Mx"]-r)/i["Nx"];
		}

		r = b-c*((i["My"]+i["Ny"]*r+i["Py"]*r*r)/200);
		
		return r;
	}

}

