﻿/**
 * @fileOverview 엘리먼트의 css style의 변화를 주어 서서히 움직이는 효과를 주는 컴포넌트
 * @author hooriza, modified by senxation
 * @requires nhn.Effect, nhn.Timer
 */

nhn.Transition = jindo.$Class({
	/** @lends nhn.Transition */
	_fps : 15,
	
	_queue : null,
	_timer : null,
	
	_waiting : true, // 큐의 다음 동작을 하길 기다리는 상태
	_playing : false, // 재생되고 있는 상태
	
	/**
	 * Transition 컴포넌트를 생성한다.
	 * @constructs
	 * @param {Object} oOptions 옵션 객체
	 * @requires nhn.Effect
	 * @requires nhn.Timer
	 * @example 
var oTransition = new nhn.Transition().fps(30).attach({
	'start' : function() {
	//Transition이 시작될 때 발생
	},
	'playing' : function(e) {
	//Transition이 진행되는 매 단계에 발생
	//이벤트 객체 e = { 
	//	element : 변화되고있는 객체 
	//}
	}
	'end' : function() {
	//Transition이 끝났을 때 발생
	},
	'abort' : function() {
	//Transition이 중단되었을 때 발생
	},
	'pause' : function() {
	//Transition이 일시정지되었을 때 발생
	},
	'resume' : function() {
	//Transition이 재시작될 때 발생
	}
});
	 */
	$init : function(oOptions) {
		this._queue = [];
		this._timer = new nhn.Timer();
		
		this.option({ effect : nhn.Effect.linear, correction : false });
		this.option(oOptions || {});
	},

	/**
	 * 효과가 재생될 초당 frame rate
	 * @param {Number} nFPS (생략시 현재 frame rate 리턴)
	 * @return {Number} 
	 */
	fps : function(nFPS) {
		if (arguments.length > 0) {
			this._fps = nFPS;
			return this;
		}
		
		return this._fps;
	},
	
	/**
	 * 진행되고 있는 Transition을 중지시킨다.
	 */
	abort : function() {
	
		this._queue = [];
		this._timer.abort();
		
		if (this._playing) this.fireEvent('abort');

		this._waiting = true;
		this._playing = false;
		
		this._now = null;
		
	},
	
	/**
	 * 지정된 Transition을 시작한다.
	 * @example 
oTransition.start(1000,
	jindo.$('foo'), {
		'@left' : nhn.Effect[sEffect](oPos.pageX + 'px')
	},
	
	jindo.$('bar'), {
		'@top' : nhn.Effect[sEffect](oPos.pageY + 'px')
	}
);
	 */
	start : function() {
	
		this.abort();
		return this.precede.apply(this, arguments);
		
	},
	
	/**
	 * 진행되고 있는 Transition을 일시중지시킨다.
	 */
	pause : function() {
	
		if (this._timer.abort())
			this.fireEvent('pause');
		
	},
	
	/**
	 * 일시중지된 Transition을 재시작시킨다.
	 */
	resume : function() {
	
		var self = this;
		if (!this._now) return;
		
		if (this._waiting == false && this._playing == true) this.fireEvent('resume');

		this._goOn();
		
		this._waiting = false;
		this._playing = true;
	
		this._timer.start(function() {
		
			var bEnd = !self._goOn();
			if (bEnd) {
				self._waiting = true;
				setTimeout(function() { self._try(); }, 0);
			}
			
			return !bEnd;
			
		}, this._now.interval);
		
	},
	
	/**
	 * 지정된 Transition이 종료된 이후에 또 다른 Transition 을 수행한다.
	 * @example 
oTransition.precede(1000,
	jindo.$('foo'), {
		'@left' : nhn.Effect[sEffect](oPos.pageX + 'px')
	},
	
	jindo.$('bar'), {
		'@top' : nhn.Effect[sEffect](oPos.pageY + 'px')
	}
);
	 */
	precede : function(nDuration, oEl) {
		
		if (typeof nDuration == 'function') {
			
			this._queue.push(nDuration);
			
		} else {
			
			var oStuff = { duration : nDuration, lists : [] };
			
			for (var oArg = arguments, nLen = oArg.length, i = 1; i < nLen - 1; i += 2) {
			
				var oValues = [];
				jindo.$H(oArg[i + 1]).forEach(function(sEnd, sKey) {
				
					if (/^(@|style\.)(\w+)/i.test(sKey))
						oValues.push([ 'csses', RegExp.$2, sEnd ]);
					else
						oValues.push([ 'attrs', sKey, sEnd ]);
				
				});
				
				oStuff.lists.push({
					element : 'tagName' in oArg[i] ? jindo.$Element(oArg[i]) : oArg[i],
					values : oValues
				});
			}
			
			this._queue.push(oStuff);
			
		}
		
		this._try();
		return this;

	},
	
	_dequeue : function() {
	
		var oStuff = this._queue.shift();
		if (!oStuff) return;
		
		if (typeof oStuff == 'function')
			return oStuff;
		
		var aLists = oStuff.lists;

		for (var i = 0, nLen = aLists.length; i < nLen; i++) {
		
			var oEl = aLists[i].element;

			for (var j = 0, aValues = aLists[i].values, nJLen = aValues.length; j < nJLen; j++) {
				
				var sType = aValues[j][0];
				var fpFunc = aValues[j][2];
				
				if (typeof fpFunc != 'function') {
					if (fpFunc instanceof Array) fpFunc = this.option('effect')(fpFunc[0], fpFunc[1]);
					else fpFunc = this.option('effect')(fpFunc);
				}
				
				if (fpFunc.setStart) {
					
					if (oEl instanceof jindo.$Element) {
					
						switch (sType) {
						case 'csses':
							fpFunc.setStart(oEl.css(aValues[j][1]));
							break;
							
						case 'attrs':
							fpFunc.setStart(oEl.$value()[aValues[j][1]]);
							break;
						}
					
					} else {
						fpFunc.setStart(oEl.getter(aValues[j][1]));	
					}
					
				}
					
				aValues[j][2] = fpFunc;

			}
			
		}
		
		return oStuff;
		
	},
	
	_try : function() {
	
		var self = this;
		if (!this._waiting) return false;
		
		var oStuff;
		
		do {
		
			oStuff = this._dequeue();
			
			if (!oStuff) {
				if (this._playing) {
					this.fireEvent('end');
					this._playing = false;	
					this.abort();
				}
				return false;
			}
			
			if (!this._playing) this.fireEvent('start');
			
			if (typeof oStuff == 'function') {
				this._playing = true;
				oStuff.call(this);
			}
		} while (typeof oStuff == 'function');
		
		var nInterval = 1000 / this._fps;
		
		this._now = {
			lists : oStuff.lists,
			ratio : 0,
			interval : nInterval,
			step : nInterval / oStuff.duration
		};
		
		this.resume();

		return true;
		
	},
	
	_goOn : function() {
	
		var oNow = this._now;
		var nRatio = oNow.ratio;
		var aLists = oNow.lists;
		
		var oEq = {};
		
		nRatio = parseFloat(nRatio.toFixed(5));
		if (nRatio > 1) nRatio = 1;
		
		var bCorrection = this.option('correction');
		
		for (var i = 0, nLen = aLists.length; i < nLen; i++) {
		
			var oEl = aLists[i].element;

			for (var j = 0, aValues = aLists[i].values, nJLen = aValues.length; j < nJLen; j++) {
				
				if (oEl instanceof jindo.$Element) {
					
					var sKey = aValues[j][1];
					var sValue = aValues[j][2](nRatio);
						
					if (bCorrection) {

						var sUnit = /[0-9]([^0-9]*)$/.test(sValue) && RegExp.$1 || '';
						
						if (sUnit) {

							var nValue = parseFloat(sValue);
							var nFloor;
	
							var a = nValue;
							
							nValue += oEq[sKey] || 0;
							nValue = parseFloat(nValue.toFixed(5));
							
							if (i == nLen - 1) {
								
								sValue = Math.round(nValue) + sUnit;
								
							} else {
								
								nFloor = parseFloat(/(\.[0-9]+)$/.test(nValue) && RegExp.$1 || 0);
								sValue = parseInt(nValue) + sUnit;
	
								oEq[sKey] = nFloor;
							
							}
							
						}
				
					}
				
					switch (aValues[j][0]) {
					case 'csses':
						oEl.css(sKey, sValue);
						break;
						
					case 'attrs':
						oEl.$value()[sKey] = sValue;
						break;
					}
					
				} else {
				
					oEl.setter(aValues[j][1], aValues[j][2](nRatio));
					
				}
				
				this.fireEvent("playing", {element : oEl});
			}
		
		}
		
		oNow.ratio += oNow.step;
		
		return nRatio != 1;
	
	}

}).extend(nhn.Component);
