/**
 * Timeline
 *
 * @version: 0.alpha.6
 * @author: Helmut Wandl (wandls.net)
 */

//TODO: split into Timeline (only Events) and Tweening (which using the Timeline)
//TODO: allow multiple Timelines

var Timeline;
(function(){
	var timeline,
	    interval=null,
	    allTweenings=[],
	    tasks=[],
	    styleHandler,
	    getColor;

	/*getColor = function(color){
		var m,
		    r = null;

		m=color.match(/\#(?:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}))/);
		if (m) r = [parseInt(m[1],16),parseInt(m[2],16),parseInt(m[3],16)];

		m=color.match(/\#(?:([0-9a-f])([0-9a-f])([0-9a-f]))/);
		if (m) r = [parseInt(m[1]+m[1],16),parseInt(m[2]+m[2],16),parseInt(m[3]+m[3],16)];

		return r;
	};*/

	styleHandler = function(){
		var a,
		    stylename,
		    s,
		    os,
		    to,
		    now = (new Date()).getTime();

		// calculate new styles
		for (a = 0; a < allTweenings.length; a++){
			if (allTweenings[a].options.duration > now-allTweenings[a].started)
			{
				for (stylename in allTweenings[a].newstyles){

				//TODO: you can make it better
					s = (Object.isFunction(allTweenings[a].newstyles[stylename]) ? allTweenings[a].newstyles[stylename]() : allTweenings[a].newstyles[stylename]).toString();
					os = allTweenings[a].oldstyles[stylename].toString();

					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)+')';
					});

					to=s.match(/rgb\s*\(\s*([0-9]+)\s*\,\s*([0-9]+)\s*\,\s*([0-9]+)\s*\)/);

					if (to !== null)
					{
						allTweenings[a].curstyles[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(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseInt(r), parseInt(to[1])-parseInt(r), allTweenings[a].options.duration))+','+
								Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseInt(g), parseInt(to[2])-parseInt(g), allTweenings[a].options.duration))+','+
								Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseInt(b), parseInt(to[3])-parseInt(b), allTweenings[a].options.duration))+
							')';
						});
						/*allTweenings[a].curstyles[stylename]=allTweenings[a].curstyles[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))+
							')';
						});*/
					}

					to=allTweenings[a].newstyles[stylename].match(/(-?[0-9\.]+)(pt|pc|in|mm|cm|px|em|ex|\%)/);
					if (to !== null)
					{
						allTweenings[a].curstyles[stylename]=os.replace(/(-?[0-9\.]+)(pt|pc|in|mm|cm|px|em|ex|\%)/g, function(h,num,suffix){
							var r;
							r = (Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseFloat(num), parseFloat(to[1])-parseFloat(num), allTweenings[a].options.duration) * 1000000) / 1000000);
							if (suffix == 'px') r = Math.round(r);
							r = r + suffix;
							return r;
						});
					}

					if (stylename == 'z-index' || stylename == 'zIndex')
						allTweenings[a].curstyles[stylename]=Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseFloat(allTweenings[a].oldstyles[stylename]), parseFloat(allTweenings[a].newstyles[stylename])-parseFloat(allTweenings[a].oldstyles[stylename]), allTweenings[a].options.duration));

					if (stylename == 'opacity')
						allTweenings[a].curstyles[stylename]=(Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseFloat(allTweenings[a].oldstyles[stylename]), parseFloat(allTweenings[a].newstyles[stylename])-parseFloat(allTweenings[a].oldstyles[stylename]), allTweenings[a].options.duration) * 1000000) / 1000000);

					if (stylename == 'scrollleft' || stylename == 'scrolltop')
						allTweenings[a].curstyles[stylename]=Math.round(transitions[allTweenings[a].options.transition](now-allTweenings[a].started, parseInt(allTweenings[a].oldstyles[stylename]), parseInt(allTweenings[a].newstyles[stylename])-parseInt(allTweenings[a].oldstyles[stylename]), allTweenings[a].options.duration));
				}
			} else {
				allTweenings[a].curstyles = allTweenings[a].newstyles;
			}
		}
		// render new styles
		for (a = 0; a < allTweenings.length; a++){
			//TODO: you should collect by element before
			allTweenings[a].element.setStyle(allTweenings[a].curstyles);
		}
		// remove finished tweenings
		for (a = 0; a < allTweenings.length; a++){
			if (allTweenings[a].started + allTweenings[a].options.duration < now){
				allTweenings.splice(a, 1);
				a--;
			}
		}

		return (allTweenings.length ? true : false);
	};

	timeline = function(options){
		this.options = Object.extend({
			fps: 50
		}, options);
	};

	timeline.prototype = {
		options:{},
		addKeyframe: function(){
		},
		addTweening: function(element, styles, options){
			return this.addTweenings([[element, styles, options]])[0];
		},
		addTweenings: function(tweenings){
			var t = [],
			    a,
			    stylename,
			    now = (new Date()).getTime();

			for (a = 0; a < tweenings.length; a++){
				t[a] = {
					started: now,
					element: $(tweenings[a][0]),
					newstyles: tweenings[a][1],
					curstyles: {},
					oldstyles: {},
					options: Object.extend({
						transition:'quadOut',
						duration:400
					}, tweenings[a][2])
				};

				for (stylename in t[a].newstyles)
				{
					if (!Object.isFunction(t[a].newstyles[stylename])){
						t[a].newstyles[stylename] = t[a].newstyles[stylename].toString();
						if (['opacity','scrollleft','scrolltop'].indexOf(stylename) === false && t[a].newstyles[stylename] === '0')
							t[a].newstyles[stylename] = '0px';
					}

					if (stylename == 'scrollleft'){
						t[a].oldstyles['scrollleft'] = (t[a].element == window ? document.viewport.getScrollOffsets().left : (t[a].element.scrollLeft || 0));
					}
					else if (stylename == 'scrolltop'){
						t[a].oldstyles['scrolltop'] = (t[a].element == window ? document.viewport.getScrollOffsets().top : (t[a].element.scrollTop || 0));
					}
					else {
						t[a].oldstyles[stylename] = (t[a].element.getStyle(stylename) || (stylename == 'opacity' ? '0' : '0px'));
					}
				}

				t[a].curstyles = Object.clone(t[a].oldstyles);
			}

			allTweenings = allTweenings.concat(t);

			this.addEvent(styleHandler);

			return t;
		},
		addEvent: function(handler, options){
			return this.addEvents([[handler, options]])[0];
		},
		addEvents: function(events){
			var t = [],
			    now = (new Date()).getTime();

			events.each(function(event){
				t[t.length] = {
					start: now,
					handler: event[0],
					options: Object.extend({
						loop: false
					},event[1])
				}
			});
			tasks = tasks.concat(t);
			start(this);

			return t;
		},
		removeTweening: function(){
		},
		removeTweenings: function(){
		},
		removeEvent: function(task){
			var a;
			for (a = 0; tasks[a]; a++){
				if (tasks[a] == task){
					tasks.splice(a, 1);
				}
			}
			if (!tasks.length)
				stop();
		}
	};

	Timeline = new timeline({});

	var start = function(timeline){
		if (!interval){
			interval = setInterval(step.bind(timeline), Math.round(1000/timeline.options.fps));
			//console.info('Timeline started');
			step.bind(timeline)();
		}
	};

	var stop = function(){
		if (interval){
			clearInterval(interval);
			interval = null;
			//console.info('Timeline stopped');
		}
	};

	var step = function(){
		var a;

		for (a = 0; a < tasks.length; a++){
			if (!tasks[a].handler(tasks[a])){
				this.removeEvent(tasks[a]);
			}
		}
	};

	var 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 - 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 transitions.bounceIn(t*2, 0, c, d) * .5 + b;
			return transitions.bounceOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
		}

		/*
		 *
		 * TERMS OF USE - EASING EQUATIONS
		 *
		 * Open source under the BSD License.
		 *
		 * Copyright A‚Â© 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.
		 *
		 */
	};
})();

Element.addMethods({
	tween: function(element, styles, options) {
		return Timeline.addTweenings([[element, styles, options]]);
	},
	//playSequence:,
	//stopSequence:,
	removeTweening: function(element, styles) {
		return Timeline.removeTweening(element, styles);
	}
});

