Animator = function(
	obj, // object to animate
	styleProp, // a string or function setter. If string, it identifies the style property of the obj to be animated e.g. "left"
	anim /* object containging animation info
			{
				steps - number of steps of the animation
				fps - frames per second (overrides the steps)
				totalTime - total time of the animation in milliseconds
				startVal - start value. Can be a function returning a value
				endVal - start value. Can be a function returning a value
				startAction - callback function fired at the start of animation
				endAction - callback function fired at the end of animation
				transition - function (optional). Determines the type of tweening 
								transition of the animation. Default value is Animator.linearTransition
				dependency - function (optional). Its return value determines whether 
					current animation can be interrupted. Not implemented
			}*/
	){
	
	var _this = this;
	this.anim = anim;
	this.isActive = false;
	this.timer = null;
	this.onComplete = function(){};
	this.onStart = function(){};
	var pointer = 0;
	
	this.setup = function(){
		if(this.isActive)
			return;

		if(this.anim.startVal === undefined && typeof styleProp != 'function'){
			this.anim.startVal = parseFloat(obj.style[styleProp]);
		}
		this.anim.transition = this.anim.transition || Animator.Easing.Back.easeOut;
		this.anim.dependency = this.anim.dependency || function(){return true};
		
		if(this.anim.fps){
			this.timeSlice = Math.round( 1000 * (1 / this.anim.fps) );
			this.anim.steps = Math.round( this.anim.totalTime / this.timeSlice);
		}
		else{
			this.timeSlice = Math.round( this.anim.totalTime / this.anim.steps );
		}
		this.coords = Animator.tween(
			(typeof this.anim.startVal == "function") ? this.anim.startVal() : this.anim.startVal, 
			(typeof this.anim.endVal == "function") ? this.anim.endVal() : this.anim.endVal, 
			this.anim.steps, 
			this.anim.transition);
		return this;
	}

	this.start = function(){
		if(this.isActive)
			return;
		
		if(typeof this.anim.startAction == "function"){
			try{
				this.anim.startAction();
			}
			catch(e){}
		}
			
		this.timer = setInterval(
			function(){
				_this.isActive = true;
				pSetter(_this.coords[pointer]);
				pointer++;
				if(pointer == _this.coords.length){
					_this.stop();
				}
			},
			this.timeSlice
		);
		return this;
	}
	
	this.stop = function(){
		if(this.isActive == false)
			return;
		pointer = 0;
		if(typeof this.anim.endAction == "function"){
			try{
				this.anim.endAction();
			}
			catch(e){}
		}
		clearInterval(this.timer);
		this.isActive = false;
	}
	
	this.enqueue = function(){
		// stores in a queue and later executes animation requests
		// when animation start() cannot be immediately processed
	}
	
	pSetter = function( val){
		if(typeof styleProp == "string")
			obj.style[styleProp] = val;
		else
			styleProp(val);
	}
}

Animator.tween = function(start, end, steps, easing){
	var pos = this.Calculator(steps, easing);
	var normalizer = end - start;
	for(var i=0; i<steps; i++){
		pos[i] = Math.round(pos[i] * normalizer + start);
	}
	return pos;
}

Animator.Calculator = function(steps, easing){
	var s = 0, e = 1, d = 1/steps;
	var pos = [];
	pos.push(s);
	for(var i=1; i<(steps - 1); i++){
		pos.push(easing(i * d, s, e, 1));
	}
	pos.push(e);
	return pos;
}

Animator.Easing = {};
Animator.Easing.Bounce = {
	easeOut : 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;
		}
	},
	
	easeIn : function( t, b, c, d){
		return c - com.robertpenner.easing.Bounce.easeOut (d-t, 0, c, d) + b;
	},
	
	easeInOut : function( t, b, c, d){
		if (t < d/2) 
			return com.robertpenner.easing.Bounce.easeIn (t*2, 0, c, d) * .5 + b;
		else 
			return com.robertpenner.easing.Bounce.easeOut (t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
}

Animator.Easing.Quad = {
	easeIn : function(t, b, c, d){
		return c*(t/=d)*t + b;
	},
	
	easeOut : function(t, b, c, d){
		return -c *(t/=d)*(t-2) + b;
	},
	
	easeInOut : function(t, b, c, d){
		if ((t/=d/2) < 1) 
			return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	}
}


Animator.Easing.Cubic = {
	easeIn : function (t, b, c, d){
		return c*(t/=d)*t*t + b;
	},
	
	easeOut : function (t, b, c, d){
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	
	
	easeInOut : 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;
	}
}

Animator.Easing.Back = {
	easeIn : function(t, b, c, d, s){
		s = s || 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	
	easeOut : function (t, b, c, d, s){
		s = s || 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	
	easeInOut : function (t, b, c, d, s){
		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;
	}
}

Animator.Easing.Expo = {
	easeIn : function(t, b, c, d){
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	
	easeOut : function(t, b, c, d){
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	
	easeInOut : 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;
	}
}

