/**
 * vxJS.fx simple effects library
 * @author Gregor Kofler
 * @version 0.2.7 2009-08-20
 * 
 * entries in register {
 * 	element:	DOM object reference,
 * 	fx:			function object,
 * 	param:		object containing effects parameter and status
 * }
 * @todo fade update
 */
/*global vxJS */

if(!vxJS) { throw new Error("fx: vxJS core missing."); }

vxJS.fx = function() {
	var	im,
		timeoutId,
		delay = 33,
		registry = vxJS.element.registry,
		lastAdded;
		irqActive = false;

	var	di = function() {
		window.clearTimeout(timeoutId);
		im = false;
	};

	var ei = function() {
		if(!im) {
			im = true;
			irq();
		}
	};

	var add = function(obj, fx, param, queued) {
		if(!obj.effects) {
			obj.effects = {};
			queued = false;
		}
		if(queued) {
			if(!lastAdded.queue) {
				lastAdded.queue = {};
			}
			lastAdded.queue[fx] = param;
			lastAdded = lastAdded.queue[fx];
			return;
		}
		if(!obj.effects[fx]) {
			if(vxJS.fx[fx].init) {
				if(vxJS.fx[fx].init.apply(obj, [param])) {
					obj.effects[fx] = param;
				}
			}
			else {
				obj.effects[fx] = param;
			}
			lastAdded = obj.effects[fx];
		}
		else {
			if(vxJS.fx[fx].update) {
				vxJS.fx[fx].update.apply(obj, [obj.effects[fx], param]);
			}
			else {
				obj.effects[fx] = vxJS.merge(obj.effects[fx], param);
			}
		}
		if(!im) {
			ei();
		}
	};
	
	var irq = function() {
		var i, l, p, r, e, t = new Date().getTime(), d, q, cont = false, servedFx = [], servedQueue = [];

		for(i = registry.length; i--;) {
			r = registry[i];

			if((e = r.effects)) {

				for(p in e) {
					if(e.hasOwnProperty(p)) {
						if(vxJS.fx[p].callback.apply(r, [e[p]])) {
							cont = true;
							continue;
						}
						if(e[p].queue) {
							for(q in e[p].queue) {
								if(e[p].queue.hasOwnProperty(q)) {
									add(r, q, e[p].queue[q]);
									cont = true;
								}
							}
						}
						servedFx.push( {element: r, eventObj: vxJS.merge(e[p], { fx: p}) } );
						delete e[p];
					}
				}
				if(vxJS.isEmpty(e)) {
					delete r.effects;
					servedQueue.push( { element: r } );
				}
			}
		}

		if((im = cont)) {
			d = new Date().getTime() - t;
			window.setTimeout(irq, delay - d > delay/2 ? delay - d : delay/2);
		}

		for(i = 0, l = servedFx.length; i < l; ++i) { 
			vxJS.event.serve(servedFx[i].element, "fxFinished", servedFx[i].eventObj);
		}
		for(i = 0, l = servedQueue.length; i < l; ++i) { 
			vxJS.event.serve(servedQueue[i].element, "fxQueueFinished");
		}
	}; 
	
	return {
		setDelay: function(d) {
			delay = parseInt(d, 10) || 33;
		},
		getDelay: function() {
			return delay;
		},
		add: add,
		ei: ei,
		di: di
	};
}();

vxJS.fx.transition = function(){
	var pi = Math.PI;

	return {
		none: function(add) {
			return add;
		},
		accel: function(add) {
			return add * add * add;
		},
		easeInOut: function(add) {
			return (Math.atan(add * pi - pi/2) + 1) / 2;
		},
	    boing : function(add) {
	        return 1 - (Math.cos(add * 4.5 * pi) * Math.exp(-add * 6)); 
	    }
	};
}();

vxJS.fx.moveRelative = {
	init: function(p) {
		var s = this.element.style;

		if(!p.to) {
			p.to = {};
		}
		if(!p.to.x || isNaN(+p.to.x)) {
			p.to.x = 0;
		} 
		if(!p.to.y || isNaN(+p.to.y)) {
			p.to.y = 0;
		} 
		if(!+p.duration) {
			p.duration = 1;
		}
		if(s.position !== "absolute") {
			s.position = "relative";
		}
		p.from = vxJS.dom.getElementPosition(this.element);

		p._inc = 1/(p.duration * vxJS.fx.getDelay());
		p._add = 0;
		return true;
	},

	update: function(p, change) {
		if(change.to && change.to.x && !isNaN(change.to.x)) {
			p.to.x = +change.to.x;
		}
		if(change.to && change.to.y && !isNaN(change.to.y)) {
			p.to.y = +change.to.y;
		}
		if(change.transition && !vxJS.fx.transition[change.transition]) {
			p.transition = change.transition;
		}
		if(+change.duration) {
			p.duration = change.duration;
			p._inc = p.duration/vxJS.fx.getDelay();
		}
	},

	callback: function(p) {
		var c = {}, t;

		p._add += p._inc;

		if(p._add >= 1) {
			c = p.to;
		}
		else {
			t = vxJS.fx.transition[p.transition](p._add);
			c.x = p.to.x * t;
			c.y = p.to.y * t;
		}
		vxJS.dom.setElementPosition(this.element, p.from.add(c));
		return p._add < 1;
	}
};

vxJS.fx.fade = {
	init: function(p) {
		if(isNaN(+p.to)) {
			p.to = 1;
		}
		if(isNaN(+p.from)) {
			p.from = 1-p.to;	
		}
		if(!+p.duration) {
			p.duration = 1;
		}
		p._inc = 1/(p.duration * vxJS.fx.getDelay());
		p._add = 0;
		return true;
	},

	callback: function(p) {
		var o;

		p._add += p._inc;

		o = p._add >= 1 ? p.to : p.from + (p.to - p.from) * p._add;

		this.element.style.display = o > 0 ? "" : "none";
		vxJS.dom.setOpacity(this.element, o);

		return p._add < 1;
	}
};

vxJS.fx.tweenColor = {
	init: function(p) {
		p.what = p.what !== "backgroundColor" ? "color" : "backgroundColor";

		if(!p.to || p.to.constructor !== Color) {
			p.to = new Color(p.to);
		}
		
		p.from = new Color(vxJS.dom.getStyle(this.element, p.what));

		if(!+p.duration) {
			p.duration = 1;
		}
		p._inc = 1/(p.duration * vxJS.fx.getDelay());
		p._add = 0;

		return true;
	},

	callback: function(p) {
		var c;

		p._add += p._inc;

		c = p._add >= 1 ?
			p.to : {
				r: p.from.r + (p.to.r - p.from.r) * p._add,
				g: p.from.g + (p.to.g - p.from.g) * p._add,
				b: p.from.b + (p.to.b - p.from.b) * p._add
			};

		this.element.style[p.what] = Color.prototype.toHex.apply(c);
		return p._add < 1;
	}
};

vxJS.fx.roll = {
	init: function(p) {

		if(!p.direction || !p.direction.match(/^(up|down)$/)) { 
			p.direction = "down";
		}
		if(!+p.duration) {
			p.duration = 1;
		}

		this.element.style.overflow = "hidden";
		this.element.style.display = "";
		this.element.style.height = "";

		if(p.direction === "down") {
			p.from = 0;
			p.to = vxJS.dom.getElementSize(this.element).y;
		}
		else {
			p.from = vxJS.dom.getElementSize(this.element).y;
			p.to = 0;
		}

		this.element.style.height = p.from + "px";

		if(!p.transition || !vxJS.fx.transition[p.transition]) {
			p.transition = "none";
		}

		p._inc = 1/(p.duration * vxJS.fx.getDelay());
		p._add = 0;

		return true;
	},

	callback: function(p) {
		var r, t;

		p._add += p._inc;

		if(p._add >= 1) {
			r = p.to;
		}
		else {
			t = vxJS.fx.transition[p.transition](p._add);
			r = p.from + (p.to - p.from) * t;
		}
		this.element.style.height = r + "px";
		return p._add < 1;
	}
};