
var Animator = Class({
	
	timerID: null,
	timers:	 [],
	
	_initialize: function ()
	{
	},
	
	Animate: function ( type, elem, prop, unit, from, to, duration, easing,
						callback, engine )
	{
		var obj = {
				"type":		 type,
				"startTime": this.Now(),
				"start":	 from,
				"end":		 to,
				"unit":		 unit,
				"now":		 from,
				"pos":		 0,
				"state":	 0,
				"duration":	 duration * 1000,
				"elem":		 elem,
				"prop":		 prop.replace( /-([a-z])/ig, this.CamelCase ),
				"easing":	 easing || "linear",
				"callback":	 callback,
				"engine":	 engine || null
			};
		
		this.timers.push( this.Step.bind( this, obj ) );
		
		if ( type == "dom" && ( prop == "width" || prop == "height" ) )
			this.AddClass( obj.elem, "animating" );
		
		/* If this.animTim is null it means the setInterval is not ticking
		and so we should start it */
		if ( !this.timerID )
			this.timerID = setInterval( this.Tick.bind( this ), 13 );
	},
	
	Step: function ( obj )
	{
		var t = this.Now(),
			n = t - obj.startTime,
			pos,
			value,
			end = ( t >= obj.duration + obj.startTime );
		
		obj.state = end ? 1 : n / obj.duration;
		obj.pos = this.Easing[ obj.easing ](
				obj.state, n, 0, 1, obj.duration
			);
		obj.now = obj.start + ( ( obj.end - obj.start) * obj.pos );
		
		if ( obj.type == "dom" )
			value = ( ( obj.prop === "width"  ||
						obj.prop === "height" ||
						obj.prop === "opacity" )
						? Math.max( 0, obj.now ) : obj.now );
		else
			value = obj.now;
		
		if ( obj.type != "dom" )
		{
			obj.elem[ obj.prop ] = value;
			obj.engine.onInterval();
		}
		else if ( this.IsIE() && obj.prop == "opacity" )
			obj.elem.style.filter = "alpha(opacity=" + ( value * 100 ) + ")";
		else
			obj.elem.style[ obj.prop ] = value + obj.unit;
		
		if ( end && obj.callback )
		{
			if ( obj.type == "dom" )
				this.RemoveClass( obj.elem, 'animating' );
			obj.callback();
		}
		
		return !end;
	},
	
	/**
	 * Quad
	 * Sine
	 * Cubic
	 * Circ
	 * Quart
	 * Quint
	 * Expo
	 * Elastic
	 * Back
	 * Bounce
	 * http://gsgd.co.uk/sandbox/jquery/easing
	 */
	Easing: {
		
		linear: function( p, n, firstNum, diff /*, duration */ )
		{ return firstNum + diff * p; },
		
		swing: function( p, n, firstNum, diff /*, duration */ )
		{ return ( ( -Math.cos( p * Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; },
		
		easeInQuad: function (x, t, b, c, d) {
			return c*(t/=d)*t + b;
		},
		easeOutQuad: function (x, t, b, c, d) {
			return -c *(t/=d)*(t-2) + b;
		},
		easeInOutQuad: function (x, t, b, c, d) {
			if ((t/=d/2) < 1) return c/2*t*t + b;
			return -c/2 * ((--t)*(t-2) - 1) + b;
		},
		easeInCubic: function (x, t, b, c, d) {
			return c*(t/=d)*t*t + b;
		},
		easeOutCubic: function (x, t, b, c, d) {
			return c*((t=t/d-1)*t*t + 1) + b;
		},
		easeInOutCubic: function (x, 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;
		},
		easeInQuart: function (x, t, b, c, d) {
			return c*(t/=d)*t*t*t + b;
		},
		easeOutQuart: function (x, t, b, c, d) {
			return -c * ((t=t/d-1)*t*t*t - 1) + b;
		},
		easeInOutQuart: function (x, 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;
		},
		easeInQuint: function (x, t, b, c, d) {
			return c*(t/=d)*t*t*t*t + b;
		},
		easeOutQuint: function (x, t, b, c, d) {
			return c*((t=t/d-1)*t*t*t*t + 1) + b;
		},
		easeInOutQuint: function (x, 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;
		},
		easeInSine: function (x, t, b, c, d) {
			return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
		},
		easeOutSine: function (x, t, b, c, d) {
			return c * Math.sin(t/d * (Math.PI/2)) + b;
		},
		easeInOutSine: function (x, t, b, c, d) {
			return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
		},
		easeInExpo: function (x, t, b, c, d) {
			return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
		},
		easeOutExpo: function (x, t, b, c, d) {
			return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
		},
		easeInOutExpo: function (x, 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;
		},
		easeInCirc: function (x, t, b, c, d) {
			return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
		},
		easeOutCirc: function (x, t, b, c, d) {
			return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
		},
		easeInOutCirc: function (x, 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;
		},
		easeInElastic: function (x, t, b, c, d) {
			var s=1.70158;var p=0;var a=c;
			if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
			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;
		},
		easeOutElastic: function (x, t, b, c, d) {
			var s=1.70158;var p=0;var a=c;
			if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
			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;
		},
		easeInOutElastic: function (x, t, b, c, d) {
			var s=1.70158;var p=0;var a=c;
			if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
			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;
		},
		easeInBack: function (x, t, b, c, d, s) {
			if (s == undefined) s = 1.70158;
			return c*(t/=d)*t*((s+1)*t - s) + b;
		},
		easeOutBack: function (x, t, b, c, d, s) {
			if (s == undefined) s = 1.70158;
			return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
		},
		easeInOutBack: function (x, t, b, c, d, s) {
			if (s == undefined) 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;
		},
		easeInBounce: function (x, t, b, c, d) {
			return c - this.easeOutBounce (x, d-t, 0, c, d) + b;
		},
		easeOutBounce: function (x, 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 (x, t, b, c, d) {
			if (t < d/2) return this.easeInBounce (x, t*2, 0, c, d) * .5 + b;
			return this.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
		}
		
	},
	
	Stop: function ()
	{
		clearInterval( this.timerID );
		this.timerID = null;
	},
	
	Tick: function ()
	{
		/* Iterate through all animation in the timers Array, calling their
		respective Step function. If they reach their end they will
		return FALSE which means they are finished and should be removed
		from the queue */
		for ( var i = 0; i < this.timers.length; i++ )
			if ( !this.timers[i]() )
				this.timers.splice( i--, 1 );
		
		/* If there are no animations queued in the this.timers Array they stop
		ticking */
		if ( !this.timers.length )
			this.Stop();
	},
	
	Now: function ()
	{ return ( new Date ).getTime(); },
	
	RGBtoHex: function ( color )
	{
		return this.ToHex( color[0] )
			 + this.ToHex( color[1] )
			 + this.ToHex( color[2] );
	},
	
	HexToRGB: function ( color )
	{
		color = color.replace( /^#/, "" );
		
		color = ( color.length == 3 )
			?  color.substring( 0, 1 ) + color.substring( 0, 1 )
			 + color.substring( 1, 2 ) + color.substring( 1, 2 )
			 + color.substring( 2, 3 ) + color.substring( 2, 3 )
			: color;
		
		return [
				this.ToRGB( color.substring( 0, 2 ) ),
				this.ToRGB( color.substring( 2, 4 ) ),
				this.ToRGB( color.substring( 4, 6 ) )
			];
	},
	
	ToHex: function ( N )
	{
		if ( N == null ) return "00";
		N = parseInt( N );
		if ( N == 0 || isNaN( N ) ) return "00";
		N = Math.round( Math.max( 0, Math.min( N, 255 ) ) );
		return "0123456789ABCDEF".charAt( ( N - ( N % 16 ) ) / 16 )
			+  "0123456789ABCDEF".charAt( N % 16 );
	},
	
	ToRGB: function ( H )
	{
		return parseInt( H, 16 );
	},
	
	AddClass: function ( el, str )
	{
		var rx = new RegExp( '(^|\\s)' + str + '(\\s|$)', 'g' );
		if ( !rx.test( el.className ) )
			el.className += ( el.className == '' ) ? str : ' ' + str;
	},
	
	RemoveClass: function ( el, str )
	{
		var rx = new RegExp( '(^|\\s)' + str + '(\\s|$)', 'g' );
		el.className = el.className.replace( rx, '$1$2' );
	},
	
	CamelCase: function ( str )
	{
		return str.replace(
				/\-(\w)/g,
				function( all, letter ) { return letter.toUpperCase(); }
			);
	},
	
	IsIE: function ()
	{ return !( document.getElementById && !document.all ); }
	
});
