/*
 | Tweener
 | ------
 |   - this is a javascript port of the flash project Tweener, http://code.google.com/p/tweener/.  
 | 
 | author
 | ------
 |   - mike macmillan(mikejmacmillan@gmail.com)
*/
Tweener = function() {
	var tweenList = [];

	function getTime() {
		return new Date()*1;
	}

	function getTweenByIndex(idx) {
		if(tweenList.length==0) return;

		//** find the tween object at the given index
		for(var i=0;i<tweenList.length;i++)
			if(i==idx)
				return tweenList[i];
	}

	var tweener = {
		Running:false,
		FrameRate:1000/50, //** (50 frames per second by defaul by default...)

		Initialize:function() {
			if(!tweenList)
				tweenList = [];
		},

		AddTween:function(obj, properties, options) {
			options = options||{};

			//** if the object to tween is an id reference, get the object by its id
			if(typeof(obj) === 'string')
				obj = document.getElementById(obj);

			if(!obj || !properties || properties.length==0)
				return;

			//** create the tween object
			var tweenObj = {
				Id:new Date()*1,
				Scope:obj,
				ScopeClean: {},
				Properties:[],

				//** time scale properties
				TimeStart:0,
				TimeComplete:0,
				TimePaused:0,

				//** animation settings
				Transition:tweener.Transitions.EaseOutExpo,
				Delay:0,
				Duration:0,

				//** listeners
				OnStart:null,
				OnUpdate:null,
				OnComplete:null
			};

			//** verify the properties to tween are valid
			var propObj, start, complete;
			for(var prop in properties) {
				if(typeof(obj[prop]) === 'undefined')
					continue;

				//** determine our starting and ending values
				start = parseInt(obj[prop]);
				complete = parseInt(properties[prop]); 

				//** create the property object
				propObj = {
					Name:prop,
					Unit:"px",

					Start:start,
					Complete:complete,
					Change:complete-start,

					//** the current value being used to update the object, and the last value used...helps for eliminating unecessary updates do to the transition returning duplicate values
					Current:0,
					Last:0
				};

				//** add the valid property object to the list
				tweenObj.Properties.push(propObj);
			}

			//** iterate our options object, overwriting any tweenObj properties if necessary
			for(var prop in options) {
				if(typeof(tweenObj[prop]) !== 'undefined' && options[prop])
					tweenObj[prop] = options[prop];
			}


			//** add the tween to the animation loop 
			setTimeout(function() {
				//** timestamp it...
				tweenObj.TimeStart = getTime();
				tweenObj.TimeComplete = tweenObj.TimeStart+tweenObj.Duration;

				//** if there is a start listener, fire it
				if(tweenObj.OnStart)
					tweenObj.OnStart(tweenObj);
				
				//** add the tween to the list
				tweenList.push(tweenObj);
				
				//** start the animation loop
				tweener.Start();

			}, tweenObj.Delay);

			return tweenObj;
		},

		PauseTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			if(!tweenObj || tweenObj.TimePaused>0) return;

			//** set the state of the tween to paused, and record the time which it was paused
			tweenObj.TimePaused = getTime();
		},

		PauseAllTweens:function() {
			if(!this.Running) return;

			//** pause each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.PauseTween(i);
		},

		ResumeTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			if(!tweenObj || tweenObj.TimePaused==0) return;

			//** set the state of the tween to resumed, and adjust the start/complete times accordingly
			var time = getTime();
			tweenObj.TimeStart += time - tweenObj.TimePaused;
			tweenObj.TimeComplete += time - tweenObj.TimePaused;
			tweenObj.TimePaused = 0;
		},

		ResumeAllTweens:function() {
			if(!this.Running) return;

			//** resume each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.ResumeTween(i);
		},

		RemoveTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			//** remove the tween if it exists
			if(tweenObj) tweenList.splice(idx, 1);
		},

		RemoveAllTweens:function() {
			//** pause each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.RemoveTween(i);
		},

		Stop:function() {
			tweenList = null;
			this.Running = false;
		},

		Start:function() {
			//** if the tweening engine isn't running, start it
			if(!this.Running) {
				this.Initialize();
				this.Run();
			}
		},

		Run:function() {
			var tweenObj, runTime, duration, current, property;
	
			function updateProperties(obj, time, dur, transition) {
				var property, value;
	
				//** update the value of each transition-property for the object given with the value given
				for(var i=0;i<obj.Properties.length;i++) {
					property = obj.Properties[i];
	
					//** get the current value for this point the timescale either using the transition algorithm given, or the expected end value
					property.Current = transition?transition(time, property.Start, property.Change, dur):property.Complete;
	
					//** fire the update event before we set the propertys value
					if (obj.OnUpdate) {
						obj.OnUpdate(obj);
					}
	
					//** persist the last used value against the property.  certain transition algorithms might update more than once, 
					//** but not actually change the value of the property they're updating (ex: exponential algo when close to zero).  by
					//** persisting the last used value, objects who handle update can know if the value has changed by comparing it to the
					//** last used value...
					property.Last = property.Current;
	
					//** lastly, set the property's value
					obj.Scope[property.Name] = parseInt(property.Current) + property.Unit;
					obj.ScopeClean[property.Name] = property.Current;
				}
			}
			
			if(!tweenList || tweenList.length == 0) return;
	
			//** in case of any outside updates, every iteration, update the engine status
			this.Running = true;

			//** get the current time in ticks
			var now = getTime();
	
			//** iterate our tween objects and update them based on our current time
			for(var i=0;i<tweenList.length;i++) {
				tweenObj = tweenList[i];
	
				//** if the current animation is paused, skip it
				if(tweenObj.TimePaused>0) continue;
	
				//** get the animations state
				runTime = parseInt(now) - parseInt(tweenObj.TimeStart);
				duration = parseInt(tweenObj.TimeComplete) - parseInt(tweenObj.TimeStart);

	
				//** if the tween is complete, remove our tween object from the collection
				if(runTime >= duration) {
					//** update the tween with its end value
					updateProperties(tweenObj, runTime, duration);
	
					//** remove the tween from the list
					tweenList.splice(i, 1);
	
					//** finally, fire the complete event if implemented
					if(tweenObj.OnComplete)
						tweenObj.OnComplete(tweenObj);
				} else {
					//** update the animation with the current value, given the timeline, and the transition algorith
					updateProperties(tweenObj, runTime, duration, tweenObj.Transition);
				}
			}
	
			if(tweenList.length>0)
				//** if there are more tween objects, keep the animation loop running at the engines framerate
				setTimeout(function() { tweener.Run(); }, tweener.FrameRate);
			else
				//** otherwise, there is nothing left to animate, so flag the engine as not running
				tweener.Running = false;
		},



		Transitions: {
			EaseNone:function(t, b, c, d) {
				return c * t/d + b;
			},
		
		
			EaseInQuad:function(t, b, c, d) {
				return c * (t/=d) * t + b;
			},
		
			EaseOutQuad:function(t, b, c, d) {
				return -c * (t/=d) * (t-2) + b;
			},
		
			EaseInOutQuad:function(t, b, c, d) {
				if((t/=d/2) < 1) 
					return c/2 * t * t + b;
		
				return -c/2 * ((--t) * (t-2) - 1) + b;
			}, 
		
			EaseOutInQuad:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuad(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuad((t*2)-2, b+c/2, c/2, d);
			}, 
		
		
			EaseInCubic:function(t, b, c, d) {
				return c * (t/=d) * t * t + b;
			},
		
			EaseOutCubic:function(t, b, c, d) {
				return c * ((t=t/d-1) * t * t + 1) + b;
			},
		
			EaseInOutCubic:function(t, b, c, d) {
				if((t/=d/2) < 1) 
					return c/2 * t * t * t + b;
		
				return c/2 * ((t-=2) * t * t + 2) + b;
			}, 
		
			EaseOutInCubic:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutCubic(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInCubic((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInQuart:function(t, b, c, d) {
				return c * (t/=d) * t * t * t + b;
			},
		
			EaseOutQuart:function(t, b, c, d) {
				return -c * ((t=t/d-1) * t * t * t - 1) + b;
			},
		
			EaseInOutQuart:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return c/2 * t * t * t * t + b;
		
				return -c/2 * ((t-=2) * t * t * t - 2) + b;
			}, 
		
			EaseOutInQuart:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuart(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuart((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInQuint:function(t, b, c, d) {
				return c * (t/=d) * t * t * t * t + b;
			},
		
			EaseOutQuint:function(t, b, c, d) {
				return c * ((t=t/d-1) * t * t * t * t + 1) + b;
			},
		
			EaseInOutQuint:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return c/2 * t * t * t * t * t + b;
		
				return c/2 * ((t-=2) * t * t * t * t + 2) + b;
			}, 
		
			EaseOutInQuint:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuint(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuint((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInSine:function(t, b, c, d) {
				return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
			},
		
			EaseOutSine:function(t, b, c, d) {
				return -c * Math.sin(t/d * (Math.PI/2)) + b;
			},
		
			EaseInOutSine:function(t, b, c, d) {
				return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
			}, 
		
			EaseOutInSine:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutSine(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInSine((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInExpo:function(t, b, c, d) {
				return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b - c * 0.001;
			},
		
			EaseOutExpo:function(t, b, c, d) {
				return(t==d) ? b+c : c * 1.001 *(-Math.pow(2, -10 * t/d) + 1) + b;
			},
		
			EaseInOutExpo:function(t, b, c, d) {
				if (t==0) return b;
				if (t==d) return b+c;
				if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b - c * 0.0005;
				return c/2 * 1.0005 * (-Math.pow(2, -10 * --t) + 2) + b;
			},
		
			EaseOutInExpo:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutExpo(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInExpo((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInCirc:function(t, b, c, d) {
				return -c * (Math.sqrt(1 - (t/=d) * t) - 1) + b;
			},
		
			EaseOutCirc:function(t, b, c, d) {
				return c * Math.sqrt(1 - (t=t/d-1) * t) + b;
			},
		
			EaseInOutCirc:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return -c/2 * (Math.sqrt(1 - t * t) - 1) + b;
		
				return c/2 * (Math.sqrt(1 - (t-=2) * t) + 1) + b;
			},
		
			EaseOutInCirc:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutCirc(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInCirc((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d)==1) return b + c;
				if(!p) p = d * .3;
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				return -(a * Math.pow(2,10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
			},
		
			EaseOutElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d)==1) return b + c;
				if(!p) p = d * .3;
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				return (a * Math.pow(2,-10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
			},
		
			EaseInOutElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d/2)==2) return b + c;
				if(!p) p = d * (.3 * 1.5);
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				if (t < 1) 
					return -.5 * (a * Math.pow(2,10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
		
				return a * Math.pow(2,-10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
			},
		
			EaseOutInElastic:function(t, b, c, d, a, p) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutElastic(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInElastic((t*2)-d, b+c/2, c/2, d);
			},
		
		
		
			EaseInBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				return c * (t/=d) * t * ((s+1) * t - s) + b;
			},
		
			EaseOutBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				return c * ((t=t/d-1) * t * ((s+1) * t + s) + 1) + b;
			},
		
			EaseInOutBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				if ((t/=d/2) < 1) 
					return c / 2 * (t * t * (((s*=(1.525)) + 1) * t - s)) + b;
		
				return c / 2 * ((t-=2) * t * (((s*=(1.525)) + 1) * t + s) + 2) + b;
			},
		
			EaseOutInBack:function(t, b, c, d, s) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutBack(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInBack((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInBounce:function(t, b, c, d) {
				return c - Tweener.Transitions.EaseOutBounce(d-t, 0, c, d) + b;
			},
		
			EaseOutBounce:function(t, b, c, d) {
				if ((t/=d) < (1/2.75))
					return c * (7.5625 * t * t) + b;
				else if (t < (2/2.75))
					return c * (7.5625 * (t-=(1.5/2.75)) * t + .75) + b;
				else if (t < (2.5/2.75))
					return c * (7.5625 * (t-=(2.25/2.75)) * t + .9375) + b;
				else
					return c * (7.5625 * (t-=(2.625/2.75)) * t + .984375) + b;
			},
		
			EaseInOutBounce:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseInBounce(t*2, 0, c, d) * .5 + b;
		
				return Tweener.Transitions.EaseOutBounce(t*2-d, 0, c, d) * .5 + c * .5 + b;
			},
		
			EaseOutInBounce:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutBounce(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInBounce((t*2)-d, b+c/2, c/2, d);
			}
			
		}
	}

	return tweener;
}();