Animation = Class.create();
Animation.prototype = 
{
	initialize : function(curve, duration, accel, repeatCount, rate)
	{
		if(curve instanceof Array || typeof curve == "array") 
		{
			curve = new Math.Curves.Line(curve[0], curve[1]);
		}
		this.curve       = curve;
		this.duration    = duration;
		this.repeatCount = repeatCount || 0;
		this.rate = rate || 25;
		if(accel) 
		{
			if(accel.getValue instanceof Function || typeof accel.getValue == "function") 
			{
				this.accel = accel;
			} 
			else 
			{
				var i = 0.35*accel+0.5;	// 0.15 <= i <= 0.85
				this.accel = new Math.Curves.CatmullRom([[0], [i], [1]], 0.45);
			}
		}
		
		this.onBegin   = new Event.Custom("begin");
		this.onAnimate = new Event.Custom("animate");
		this.onEnd     = new Event.Custom("end");
		this.onPlay    = new Event.Custom("play");
		this.onPause   = new Event.Custom("pause");
		this.onStop    = new Event.Custom("stop");
	},
	
	// public properties
	curve:       null,
	duration:    0,
	repeatCount: 0,
	accel:       null,

	// "private" properties
	_animSequence:     null,
	_startTime:        null,
	_endTime:          null,
	_lastFrame:        null,
	_timer:            null,
	_percent:          0,
	_active:           false,
	_paused:           false,
	_startRepeatCount: 0,

	// public methods
	play: function(gotoStart) 
	{
		if (gotoStart) 
		{
			clearTimeout(this._timer);
			this._active = false;
			this._paused = false;
			this._percent = 0;
		} 
		else if (this._active && !this._paused) 
		{
			return;
		}

		this._startTime = new Date().valueOf();
		if (this._paused) 
		{
			this._startTime -= (this.duration * this._percent / 100);
		}
		this._endTime = this._startTime + this.duration;
		this._lastFrame = this._startTime;

		var e = new AnimationEvent(this, null, this.curve.getValue(this._percent),
			this._startTime, this._startTime, this._endTime, this.duration, this._percent, 0);

		this._active = true;
		this._paused = false;

		if (this._percent == 0) 
		{
			if (!this._startRepeatCount) 
			{
				this._startRepeatCount = this.repeatCount;
			}
			e.type = "begin";
			//if(typeof this.handler == "function") { this.handler(e); }
			//if(typeof this.onBegin == "function") { this.onBegin(e); }
			//Event.broadcast(this, "begin", e);
			this.onBegin.fire(e);
		}

		e.type = "play";
		//if(typeof this.handler == "function") { this.handler(e); }
		//if(typeof this.onPlay == "function") { this.onPlay(e); }
		//Event.broadcast(this, "play", e);
		this.onPlay.fire(e);

		if(this._animSequence) 
		{ 
			this._animSequence._setCurrent(this); 
		}

		this._cycle();
	},

	pause: function() 
	{
		clearTimeout(this._timer);
		if (!this._active) 
		{ 
			return; 
		}
		this._paused = true;
		var e = new AnimationEvent(this, "pause", this.curve.getValue(this._percent),
			this._startTime, new Date().valueOf(), this._endTime, this.duration, this._percent, 0);
		//if(typeof this.handler == "function") { this.handler(e); }
		//if(typeof this.onPause == "function") { this.onPause(e); }
		//Event.broadcast(this, "pause", e);
		this.onPause.fire(e);
	},

	playPause: function() 
	{
		if (!this._active || this._paused) 
		{
			this.play();
		} 
		else 
		{
			this.pause();
		}
	},

	gotoPercent: function(pct, andPlay) 
	{
		clearTimeout(this._timer);
		this._active = true;
		this._paused = true;
		this._percent = pct;
		if (andPlay) 
		{ 
			this.play(); 
		}
	},

	stop: function(gotoEnd) 
	{
		clearTimeout(this._timer);
		var step = this._percent / 100;
		if (gotoEnd) 
		{
			step = 1;
		}
		var e = new AnimationEvent(this, "stop", this.curve.getValue(step),
			this._startTime, new Date().valueOf(), this._endTime, this.duration, this._percent, Math.round(fps));
		//if(typeof this.handler == "function") { this.handler(e); }
		//if(typeof this.onStop == "function") { this.onStop(e); }
		//Event.broadcast(this, "stop", e);
		this.onStop.fire(e);
		
		this._active = false;
		this._paused = false;
	},

	status: function() 
	{
		if (this._active) 
		{
			return this._paused ? "paused" : "playing";
		} 
		else 
		{
			return "stopped";
		}
	},

	// "private" methods
	_cycle: function() 
	{
		clearTimeout(this._timer);
		if (this._active) 
		{
			var curr = new Date().valueOf();
			var step = (curr - this._startTime) / (this._endTime - this._startTime);
			fps = 1000 / (curr - this._lastFrame);
			this._lastFrame = curr;

			if (step >= 1) 
			{
				step = 1;
				this._percent = 100;
			} 
			else 
			{
				this._percent = step * 100;
			}
			
			// Perform accelleration
			if (this.accel && this.accel.getValue) 
			{
				step = this.accel.getValue(step);
			}

			var e = new AnimationEvent(this, "animate", this.curve.getValue(step),
				this._startTime, curr, this._endTime, this.duration, this._percent, Math.round(fps));

			//if(typeof this.handler == "function") { this.handler(e); }
			//if(typeof this.onAnimate == "function") { this.onAnimate(e); }
			//Event.stop(this, "animate", e);
			this.onAnimate.fire(e);

			if (step < 1) 
			{
				//this._timer = setTimeout(dojo.lang.hitch(this, "_cycle"), this.rate);
				this._timer = setTimeout(this._cycle.bind(this), this.rate);
			} 
			else 
			{
				e.type = "end";
				this._active = false;
				//if(typeof this.handler == "function") { this.handler(e); }
				//if(typeof this.onEnd == "function") { this.onEnd(e); }
				//Event.broadcast(this, "end", e);
				this.onEnd.fire(e);

				if (this.repeatCount > 0) 
				{
					this.repeatCount--;
					this.play(true);
				} 
				else if (this.repeatCount == -1) 
				{
					this.play(true);
				} 
				else 
				{
					if (this._startRepeatCount) 
					{
						this.repeatCount = this._startRepeatCount;
						this._startRepeatCount = 0;
					}
					if (this._animSequence) 
					{
						this._animSequence._playNext();
					}
				}
			}
		}
	}
}


AnimationSequence = function(repeatCount)
{
	this._anims      = [];
	this.repeatCount = repeatCount || 0;
	this.onBegin     = new Event.Custom("begin");
	this.onEnd       = new Event.Custom("end");
	this.onNext      = new Event.Custom("next");
}

Object.extend(AnimationSequence, {
	repeateCount: 0,

	_anims:    [],
	_currAnim: -1,

	add: function() 
	{
		for (var i = 0; i < arguments.length; i++) 
		{
			this._anims.push(arguments[i]);
			arguments[i]._animSequence = this;
		}
	},

	remove: function(anim) 
	{
		for (var i = 0; i < this._anims.length; i++) 
		{
			if (this._anims[i] == anim) 
			{
				this._anims[i]._animSequence = null;
				this._anims.splice(i, 1);
				break;
			}
		}
	},

	removeAll: function() 
	{
		for (var i = 0; i < this._anims.length; i++) 
		{
			this._anims[i]._animSequence = null;
		}
		this._anims = [];
		this._currAnim = -1;
	},

	clear: function() 
	{
		this.removeAll();
	},

	play: function(gotoStart) 
	{
		if (this._anims.length == 0) 
		{ 
			return; 
		}
		if (gotoStart || !this._anims[this._currAnim]) 
		{
			this._currAnim = 0;
		}
		if (this._anims[this._currAnim]) 
		{
			if (this._currAnim == 0) 
			{
				var e = {type: "begin", animation: this._anims[this._currAnim]};
				//if(typeof this.handler == "function") { this.handler(e); }
				//if(typeof this.onBegin == "function") { this.onBegin(e); }
				//Event.broadcast(this, "begin", e);
				this.onBegin.fire(e);
			}
			this._anims[this._currAnim].play(gotoStart);
		}
	},

	pause: function() 
	{
		if (this._anims[this._currAnim]) 
		{
			this._anims[this._currAnim].pause();
		}
	},

	playPause: function() 
	{
		if (this._anims.length == 0) 
		{ 
			return; 
		}
		if (this._currAnim == -1) 
		{ 
			this._currAnim = 0; 
		}
		if (this._anims[this._currAnim]) 
		{
			this._anims[this._currAnim].playPause();
		}
	},

	stop: function() 
	{
		if (this._anims[this._currAnim]) 
		{
			this._anims[this._currAnim].stop();
		}
	},

	status: function() 
	{
		if (this._anims[this._currAnim]) 
		{
			return this._anims[this._currAnim].status();
		} 
		else 
		{
			return "stopped";
		}
	},

	_setCurrent: function(anim) 
	{
		for (var i = 0; i < this._anims.length; i++) 
		{
			if (this._anims[i] == anim) 
			{
				this._currAnim = i;
				break;
			}
		}
	},

	_playNext: function() 
	{
		if (this._currAnim == -1 || this._anims.length == 0) 
		{ 
			return; 
		}
		this._currAnim++;
		if (this._anims[this._currAnim]) 
		{
			var e = {type: "next", animation: this._anims[this._currAnim]};
			//if(typeof this.handler == "function") { this.handler(e); }
			//if(typeof this.onNext == "function") { this.onNext(e); }
			//Event.broadcast(this, "next", e);
			this.onNext.fire(e);
			this._anims[this._currAnim].play(true);
		} 
		else 
		{
			var e = {type: "end", animation: this._anims[this._anims.length-1]};
			//if(typeof this.handler == "function") { this.handler(e); }
			//if(typeof this.onEnd == "function") { this.onEnd(e); }
			//Event.broadcast(this, "end", e);
			this.onEnd.fire(e);
			if (this.repeatCount > 0) 
			{
				this._currAnim = 0;
				this.repeatCount--;
				this._anims[this._currAnim].play(true);
			} 
			else if (this.repeatCount == -1) 
			{
				this._currAnim = 0;
				this._anims[this._currAnim].play(true);
			} 
			else 
			{
				this._currAnim = -1;
			}
		}
	}
});

AnimationEvent = function(anim, type, coords, sTime, cTime, eTime, dur, pct, fps)
{
	this.type = type;
	this.animation = anim;
	
	this.coords = coords;
	this.x = coords[0];
	this.y = coords[1];
	this.z = coords[2];
	
	this.startTime = sTime;
	this.currentTime = cTime;
	this.endTime = eTime;
	
	this.duration = dur;
	this.percent = pct;
	this.fps = fps;
}

Object.extend(AnimationEvent.prototype,
{
	coordsAsInts : function()
	{
		var cints = new Array(this.coords.length);
		for (var i=0; i<this.coords.length; i++)
		{
			cints[i] = Math.round(this.coords[i]);
		}
		return cints;
	}
});