function AppleAnimator (duration, interval, optionalFrom, optionalTo, optionalCallback)
{	
	this.startTime = 0;
	this.duration = duration;
	this.interval = interval;
	this.animations = new Array;
	this.timer = null;
	this.oncomplete = null;
	this._firstTime = true;
	
	var self = this;
	
	this.animate = function (self) {
		
		function limit_3 (a, b, c) {
    		return a < b ? b : (a > c ? c : a);
		}
		
		var T, time;
		var ease;
		var time  = (new Date).getTime();
		var dur = self.duration;
		var done;
				
		T = limit_3(time - self.startTime, 0, dur);
		time = T / dur;
		ease = 0.5 - (0.5 * Math.cos(Math.PI * time));
		
		done = T >= dur;
		
		var array = self.animations;
		var c = array.length;
		var first = self._firstTime;
		
		for (var i = 0; i < c; ++i)
		{
			array[i].doFrame (self, ease, first, done, time);
		}
		
		if (done)
		{
			self.stop();
			if  (self.oncomplete != null)
			{
				self.oncomplete();
			}
		}
		
		self._firstTime = false;
	}

	if (optionalFrom !== undefined && optionalTo !== undefined && optionalCallback !== undefined)
	{
		this.addAnimation(new AppleAnimation (optionalFrom, optionalTo, optionalCallback));
	}
}

AppleAnimator.prototype.start = function () {
	if (this.timer == null)
	{
		var timeNow = (new Date).getTime();
		var interval = this.interval;
		
		this.startTime = timeNow - interval; // see it back one frame
		
		this.timer = setInterval (this.animate, interval, this);
	}
}

AppleAnimator.prototype.stop = function () {
	if (this.timer != null)
	{
		clearInterval(this.timer);
		this.timer = null;
	}
}

AppleAnimator.prototype.addAnimation = function (animation) {

	this.animations[this.animations.length] = animation;
}

//
// Animation class
//

function AppleAnimation (from, to, callback)
{
	this.from = from;
	this.to = to;
	this.callback = callback;
	this.now = from;
	this.ease = 0;
	this.time = 0;
}

AppleAnimation.prototype.doFrame = function (animation, ease, first, done, time) {
	
	var now;
	
	if (done)
		now = this.to;
	else
		now = this.from + (this.to - this.from) * ease;
	
	this.now = now;
	this.ease = ease;
	this.time = time;
	this.callback (animation, now, first, done);
}

//
// RectAnimation class
//

function AppleRectAnimation (from, to, callback)
{
	this.from = from;
	this.to = to;
	this.callback = callback;
	
	this.now = from;
	this.ease = 0;
	this.time = 0;
}

AppleRectAnimation.prototype = new AppleAnimation (0, 0, null);

AppleRectAnimation.prototype.doFrame = function (animation, ease, first, done, time) {
	
	var now;
	
	function computeNextRectangle (from, to, ease)
	{
		return addRects (from, timesRect (minusRects(to, from), ease));
	}

	if (done)
		now = this.to;
	else
		now = AppleRect.add (this.from, AppleRect.multiply (AppleRect.subtract (this.to, this.from), ease));
		
		//computeNextRectangle (this.from, this.to, ease);
	this.now = now;
	this.ease = ease;
	this.time = time;
	this.callback (animation, now, first, done);
}

//
// AppleRect class
//

function AppleRect (left, top, right, bottom) {
	this.left = left;
	this.top = top;
	this.right = right;
	this.bottom = bottom;
}


AppleRect.add = function (a, b) {
	return new AppleRect (a.left + b.left,
						  a.top + b.top,
						  a.right + b.right,
						  a.bottom + b.bottom);
}

AppleRect.subtract = function (a, b) {
	return new AppleRect (a.left - b.left,
						  a.top - b.top,
						  a.right - b.right,
						  a.bottom - b.bottom);
}

AppleRect.multiply = function (a, multiplier) {
	return new AppleRect (a.left * multiplier,
						  a.top * multiplier,
						  a.right * multiplier,
						  a.bottom * multiplier);
}

// generic applier, pass methods like Math.floor/round/ceil
AppleRect.prototype.apply = function (func) {
	this.left = func(this.left);
	this.top = func(this.top);
	this.right = func(this.right);
	this.bottom = func(this.bottom);
	return this;
}

AppleRect.prototype.toString = function () {
	return "{left:" + this.left + ", top:" + this.top + ", right:" + this.right + ", bottom:" + this.bottom + "}";
}