var Timeline=Class.create({
	initialize:function(options){
		this.options={
			fps:50,
			loop:false,
			autostart:false,
			autostop:true
		};
		this.eventhandlers=[];
		this.time={
			start:false,
			prevstep:false,
			currstep:false
		};
		Object.extend(this.options, options || { });
		this.timer=null;
	}
});

Timeline.addMethods({
	addKeyframe:function(element, styles, options){
		var element=P$(element);
		this.addEvent(function(){
			element.setStyle(styles);
		}.bind(this),options);
	},
	addTweening:function(element, styles, options){
		var r=this.addTweenings([[element,styles,options]]);
		return r[0];
	},
	addTweenings:function(tweenings){
		var t=[];
		for (var a=0; a < tweenings.length; a++)
		{
			var element=P$(tweenings[a][0]);
			var o={
				transition:'quadOut',
				duration:400,
				start:'auto',
				styles:tweenings[a][1],
				loop:false,
				element:element
			};
			Object.extend(o, tweenings[a][2] || { });
			if (Object.isString(o.transition) && this.transitions[o.transition]) o.transition=this.transitions[o.transition];
			t[t.length]=o;
		}
		var s='';
		for (var a=0; a < t.length; a++)
			s+=(a > 0 ? ', ' : '')+'t['+a+']';
		eval('this.eventhandlers.push('+s+');');

		if (this.options.autostart) this.start();

		return t;
	},
	addEvent:function(eventhandler,options){
		var o={
			start:(this.time.currstep > 0 ? this.time.currstep+1 : 0),
			duration:0,
			event:eventhandler
		};
		Object.extend(o, options || { });
		this.eventhandlers.push(o);

		if (this.options.autostart) this.start();
	},
	removeTweening:function(element, styles){
		this.removeTweenings([[element,styles]]);
	},
	removeTweenings:function(tweenings){
		for (var a=0; a < tweenings.length; a++)
		{
			var element=P$(tweenings[a][0]);
			for (var b=0; b < this.eventhandlers.length; b++)
			{
				if (element == this.eventhandlers[b].element)
				{
					if (tweenings[a][1]){
						for(style in tweenings[a][1])
							if (this.eventhandlers[b].styles[style])
								delete this.eventhandlers[b].styles[style];

						if ((Object.keys(this.eventhandlers[b].styles)).length == 0 && this.eventhandlers.splice(b,1))
							b--;
					}
					else if (this.eventhandlers.splice(b,1))
						b--;
				}
			}
		}
	},
	removeEvent:function(eventhandler){
		for (var b=0; b < this.eventhandlers.length; b++)
		{
			if (eventhandler == this.eventhandlers[b].event)
				this.eventhandlers.splice(b,1);
		}
	},
	start:function(){
		if (this.timer === null)
			this.time={
				start:(new Date()).getTime()
			};
		if (!this.timer)
		{
			this.step();
			this.timer=setInterval(this.step.bind(this), Math.round(1000/this.options.fps));
		}
	},
	stop:function(){
		clearInterval(this.timer);
		this.timer=false;
	},
	goto:function(frame){

	},
	step:function(){
		var prev=this.time.prevstep=(this.time.currstep >= 0 ? this.time.currstep : false);
		var curr=this.time.currstep=(new Date()).getTime()-this.time.start;
		var hand=this.eventhandlers;
		var execute=[];
		var lastexecution=0;

		for (var a=0; a < hand.length; a++) {
			if (hand[a].start == 'auto') hand[a].start=curr;

			// detect last execution time
			if (hand[a].start+hand[a].duration > lastexecution) lastexecution=hand[a].start+hand[a].duration;

			// detect tweening start frame if not exist
			if (prev === false && hand[a].start == 0 || prev >= 0 && hand[a].start > prev && hand[a].start <= curr) {
				if (hand[a].element && hand[a].styles && !hand[a].oldstyles) {
					hand[a].oldstyles={};
					for (var b in hand[a].styles)
					{
						if (b == 'scrollleft') hand[a].oldstyles['scrollleft']=(hand[a].element == window ? document.viewport.getScrollOffsets().left : (hand[a].element.scrollLeft || 0));
						else if (b == 'scrolltop') hand[a].oldstyles['scrolltop']=(hand[a].element == window ? document.viewport.getScrollOffsets().top : (hand[a].element.scrollTop || 0));
						else hand[a].oldstyles[b]=(hand[a].element.getStyle(b) || 0);
					}
				}
			}

			if (curr >= hand[a].start && prev <= hand[a].start+hand[a].duration) {
				var d=hand[a].duration;
				var t=curr-hand[a].start;
				if (t >= d) t=d;

				var e=execute.find(function(e){
					if (hand[a].element == e.element || hand[a].event == e.event) return true;
					return false;
				});

				if (hand[a] && e && hand[a].element == e.element)
					e.element=hand[a].element;

				if (!e) var e=execute[execute.length]={
					element:(hand[a].element || false),
					currentstyles:{},
					event:(hand[a].event || false)
				};

				for (var stylename in hand[a].styles)
				{
					var s=(Object.isFunction(hand[a].styles[stylename]) ? hand[a].styles[stylename]() : hand[a].styles[stylename]).toString();
					var os=hand[a].oldstyles[stylename].toString();

					if (['opacity','scrollleft','scrolltop'].indexOf(stylename) === false){
						if (s === '0') s='0px';
						if (os === '0') os='0px';
					}

					s=s.replace(/\#(?:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}))/,function(h,r,g,b){
						return 'rgb('+parseInt(r,16)+','+parseInt(g,16)+','+parseInt(b,16)+')';
					});
					s=s.replace(/\#(?:([0-9a-f])([0-9a-f])([0-9a-f]))/,function(h,r,g,b){
						return 'rgb('+parseInt(r+r,16)+','+parseInt(g+g,16)+','+parseInt(b+b,16)+')';
					});
					os=os.replace(/\#(?:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}))/,function(h,r,g,b){
						return 'rgb('+parseInt(r,16)+','+parseInt(g,16)+','+parseInt(b,16)+')';
					});
					os=os.replace(/\#(?:([0-9a-f])([0-9a-f])([0-9a-f]))/,function(h,r,g,b){
						return 'rgb('+parseInt(r+r,16)+','+parseInt(g+g,16)+','+parseInt(b+b,16)+')';
					});

					var to=s.match(/rgb\s*\(\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9]+)\s*\)/);

					if (to !== null)
					{
						e.currentstyles[stylename]=os.replace(/rgb\s*\(\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9]+)\s*\)/g, function(h,r,g,b){
							return 'rgb('+
								Math.round(hand[a].transition(t, parseInt(r), parseInt(to[1])-parseInt(r), d))+','+
								Math.round(hand[a].transition(t, parseInt(g), parseInt(to[2])-parseInt(g), d))+','+
								Math.round(hand[a].transition(t, parseInt(b), parseInt(to[3])-parseInt(b), d))+
							')';
						});
						e.currentstyles[stylename]=e.currentstyles[stylename].replace(/\#(?:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}))/g, function(h,r,g,b){
							return 'rgb('+
								Math.round(hand[a].transition(t, parseInt(r,16), parseInt(to[1])-parseInt(r,16), d))+','+
								Math.round(hand[a].transition(t, parseInt(g,16), parseInt(to[2])-parseInt(g,16), d))+','+
								Math.round(hand[a].transition(t, parseInt(b,16), parseInt(to[3])-parseInt(b,16), d))+
							')';
						});
					}

					var to=s.match(/(-?[0-9\.]+)(pt|pc|in|mm|cm|px|em|ex|\%)/);
					if (to !== null)
					{
						e.currentstyles[stylename]=os.replace(/(-?[0-9\.]+)(pt|pc|in|mm|cm|px|em|ex|\%)/g, function(h,num,suffix){
							var r=(Math.round(hand[a].transition(t, parseFloat(num), parseFloat(to[1])-parseFloat(num), d) * 1000000) / 1000000);
							if (suffix == 'px') r=Math.round(r);
							return r+suffix;
						});
					}

					if (stylename == 'opacity')
						e.currentstyles[stylename]=(Math.round(hand[a].transition(t, parseFloat(os), parseFloat(s)-parseFloat(os), d) * 1000000) / 1000000);

					if (stylename == 'scrollleft' || stylename == 'scrolltop')
						e.currentstyles[stylename]=Math.round(hand[a].transition(t, parseInt(os), parseInt(s)-parseInt(os), d));

/*
					var pf=os.match(/\s*(-?[0-9\.]+)(.*)/);
					var pt=s.match( /\s*(-?[0-9\.]+)(.*)/);

					b=parseFloat(pf[1]);
					c=parseFloat(pt[1])-parseFloat(pf[1]);

					var v=hand[a].transition(t, b, c, d);

					// round
					v=(Math.round(v * 1000000) / 1000000);

					e.currentstyles[stylename]=v+pt[2];
*/
				}
			}

			if (hand[a].loop && curr > hand[a].start+hand[a].duration) {
				hand[a].start=prev+1;
			}

			if (prev > hand[a].start+hand[a].duration)
			{
				hand.splice(a,1);
				a--;
				continue;
			}
		}

		// execute tweenings and events
		for (var a=0; a < execute.length; a++)
		{
			if (execute[a].element && execute[a].currentstyles)
			{
				if (execute[a].element == window && (execute[a].currentstyles.scrollleft !== undefined || execute[a].currentstyles.scrolltop !== undefined)){
					if (execute[a].currentstyles.scrollleft === undefined) execute[a].currentstyles.scrollleft=document.viewport.getScrollOffsets().left;
					if (execute[a].currentstyles.scrolltop === undefined) execute[a].currentstyles.scrolltop=document.viewport.getScrollOffsets().top;
					window.scrollTo(execute[a].currentstyles.scrollleft, execute[a].currentstyles.scrolltop);
					execute[a].currentstyles.scrollleft=undefined;
					execute[a].currentstyles.scrolltop=undefined;
				}
				else
				{
					if (execute[a].currentstyles.scrollleft) execute[a].element.scrollLeft=parseInt(execute[a].currentstyles.scrollleft);
					if (execute[a].currentstyles.scrolltop) execute[a].element.scrollTop=parseInt(execute[a].currentstyles.scrolltop);
				}

				if (execute[a].element.setStyle)
					execute[a].element.setStyle(execute[a].currentstyles);
			}
			if (execute[a].event) execute[a].event();
		}

		if (lastexecution <= curr)
		{
			if (this.options.loop)
				this.time={
					start:(new Date().getTime())
				};
			else if(this.options.autostop)
				if (this.timer) this.stop();
		}
	}
});

Timeline.addMethods({
	transitions: {
		linear: function(t, b, c, d){
			return c*t/d + b;
		},

		sinus: function(t, b, c, d){
			return Math.sin(t/d*2*Math.PI) * c + b;
		},

		cosinus: function(t, b, c, d){
			return Math.cos(t/d*2*Math.PI) * c + b;
		},

		quadIn: function(t, b, c, d){
			return c*(t/=d)*t + b;
		},

		quadOut: function(t, b, c, d){
			return -c *(t/=d)*(t-2) + b;
		},

		quadInOut: function(t, b, c, d){
			if ((t/=d/2) < 1) return c/2*t*t + b;
			return -c/2 * ((--t)*(t-2) - 1) + b;
		},

		cubicIn: function(t, b, c, d){
			return c*(t/=d)*t*t + b;
		},

		cubicOut: function(t, b, c, d){
			return c*((t=t/d-1)*t*t + 1) + b;
		},

		cubicInOut: 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;
		},

		quartIn: function(t, b, c, d){
			return c*(t/=d)*t*t*t + b;
		},

		quartOut: function(t, b, c, d){
			return -c * ((t=t/d-1)*t*t*t - 1) + b;
		},

		quartInOut: 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;
		},

		quintIn: function(t, b, c, d){
			return c*(t/=d)*t*t*t*t + b;
		},

		quintOut: function(t, b, c, d){
			return c*((t=t/d-1)*t*t*t*t + 1) + b;
		},

		quintInOut: 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;
		},

		sineIn: function(t, b, c, d){
			return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
		},

		sineOut: function(t, b, c, d){
			return c * Math.sin(t/d * (Math.PI/2)) + b;
		},

		sineInOut: function(t, b, c, d){
			return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
		},

		expoIn: function(t, b, c, d){
			return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
		},

		expoOut: function(t, b, c, d){
			return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
		},

		expoInOut: 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;
			return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
		},

		circIn: function(t, b, c, d){
			return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
		},

		circOut: function(t, b, c, d){
			return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
		},

		circInOut: 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;
		},

		elasticIn: function(t, b, c, d, a, p){
			if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (!a) a = 1;
			if (a < Math.abs(c)){ a=c; var s=p/4; }
			else var 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;
		},

		elasticOut: function(t, b, c, d, a, p){
			if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (!a) a = 1;
			if (a < Math.abs(c)){ a=c; var s=p/4; }
			else var 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;
		},

		elasticInOut: 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); if (!a) a = 1;
			if (a < Math.abs(c)){ a=c; var s=p/4; }
			else var 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;
		},

		backIn: function(t, b, c, d, s){
			if (!s) s = 1.70158;
			return c*(t/=d)*t*((s+1)*t - s) + b;
		},

		backOut: function(t, b, c, d, s){
			if (!s) s = 1.70158;
			return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
		},

		backInOut: 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;
		},

		bounceIn: function(t, b, c, d){
			return c - Timeline.prototype.transitions.bounceOut(d-t, 0, c, d) + b;
		},

		bounceOut: 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;
			}
		},

		bounceInOut: function(t, b, c, d){
			if (t < d/2) return Timeline.prototype.transitions.bounceIn(t*2, 0, c, d) * .5 + b;
			return Timeline.prototype.transitions.bounceOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
		}

		/*
		 *
		 * TERMS OF USE - EASING EQUATIONS
		 *
		 * Open source under the BSD License.
		 *
		 * Copyright Â© 2001 Robert Penner
		 * All rights reserved.
		 *
		 * Redistribution and use in source and binary forms, with or without modification,
		 * are permitted provided that the following conditions are met:
		 *
		 * Redistributions of source code must retain the above copyright notice, this list of
		 * conditions and the following disclaimer.
		 * Redistributions in binary form must reproduce the above copyright notice, this list
		 * of conditions and the following disclaimer in the documentation and/or other materials
		 * provided with the distribution.
		 *
		 * Neither the name of the author nor the names of contributors may be used to endorse
		 * or promote products derived from this software without specific prior written permission.
		 *
		 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
		 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
		 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
		 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
		 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
		 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
		 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
		 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
		 * OF THE POSSIBILITY OF SUCH DAMAGE.
		 *
		 */
	}
});

Timeline.autoGlobalTimeline=new Timeline({loop:false,autostart:true,autostop:true});

Timeline.addKeyframe=function(a,b,c){return this.autoGlobalTimeline.addKeyframe(a,b,c);};
Timeline.addTweening=function(a,b,c){return this.autoGlobalTimeline.addTweening(a,b,c);};
Timeline.addTweenings=function(a){return this.autoGlobalTimeline.addTweenings(a);};
Timeline.addEvent=function(a,b){return this.autoGlobalTimeline.addEvent(a,b);};
//Timeline.addSequence
//Timeline.addSequences
Timeline.removeTweening=function(a,b){return this.autoGlobalTimeline.removeTweening(a,b);};
Timeline.removeTweenings=function(a){return this.autoGlobalTimeline.removeTweenings(a);};
Timeline.start=function(){return this.autoGlobalTimeline.start();};
Timeline.stop=function(){return this.autoGlobalTimeline.stop();};
Timeline.goto=function(a){return this.autoGlobalTimeline.goto(a);};
Timeline.step=function(){return this.autoGlobalTimeline.step();};

Element.addMethods({
	tween: function(element, styles, options) {
		Timeline.autoGlobalTimeline.addTweening(element, styles, options);
		return Timeline.autoGlobalTimeline;
	},
	//playSequence:,
	//stopSequence:,
	removeTweening: function(element, styles) {
		return Timeline.autoGlobalTimeline.removeTweening(element, styles);
	}
});


