var Animation = function(element, duration)
{
	// public members
	this.style = new Object;
	this.onfinish = null;
	
	// private members;
	this._fromStyle = new Object;
	this._element = element;
	this._duration = duration;
	this._steps = 0;
	this._endSteps = 0;
	this._mode = 0;
}

// TODO
// Implement this!
// animation speed constants
Animation.LINEAR = 0;		// linear movement (equal speed all the way)
Animation.LOGARITHMIC = 1;	// logarithmic movement (starts fast and slows down)
Animation.BELL_CURVE = 2;	// bell curve (starts slow, accelerates, decelerates)

Animation.prototype.start = function(mode)
{
	for(var property in this.style)
		this._fromStyle[property] = this._element.style[property];
	
	this._endSteps = Math.floor(this._duration / 24);	// a timer can run every 24 ms or so
	this._stepDuration = this._duration / this._endSteps;
	this._mode = mode;
	
	
	var animation = this;
	setTimeout(function() { animation._perform(); }, 0);
}

Animation.prototype.reverseAnimation = function()
{
	var animation = new Animation(this._element, this._duration);
	for(var property in this.style)
		animation.style[property] = this._element.style[property];
	return animation;
}

Animation.prototype._perform = function()
{
	if(this._steps == this._endSteps)
	{
		this.end();
		return;
	}
	
	var completion = this._steps / this._endSteps;
	
	for(var property in this.style)
	{
		var value;
		try {
			if(Animation.transformUnits[property] == undefined)
			{
				value = Animation.transformUnits.defaultTransformUnit(this._fromStyle[property], this.style[property], completion);
				this._element.style[property] = value;
			}
			else
			{
				value = Animation.transformUnits[property](this._fromStyle[property], this.style[property], completion);
				this._element.style[property] = value;
			}
		}
		catch(e)
		{
			alert(property + "\n" + e.message + "\n" + value);
		}
	}
	
	this._steps++;
	var animation = this;
	setTimeout(function() { animation._perform(); }, this._stepDuration);
}

Animation.prototype.end = function()
{
	for(var property in this.style)
		this._element.style[property] = this.style[property];
	
	if(this.onfinish != null)
		this.onfinish(this);
}

// Transform Units
// specialized functions to help the transition between different properties.
Animation.transformUnits = new Object();

// default transform unit
// this will do for any style property in the form of [number] [units].
Animation.transformUnits.defaultTransformUnit = function(from, to, completion)
{
	var fromValues = from.match(/([0-9.]+)([^0-9.]*)/);
	var toValues = to.match(/([0-9.]+)([^0-9.]*)/);
	
	var fromPX = parseInt(fromValues[1]);
	var toPX = parseInt(toValues[1]);
	
	return fromPX + (toPX - fromPX) * completion + toValues[2];
}

// opacity transform unit
Animation.transformUnits.opacity = function(from, to, completion)
{
	from = parseFloat(from);
	to = parseFloat(to);
	var result = from + (to - from) * completion;
	return result;
}

// color transform
// can read the following formats: #RRGGBB, #RGB, rgb(x,x,x), rgba(x,x,x,[ignored])
Animation.transformUnits.color = function(from, to, completion)
{
	var parsedFrom = Animation.transformHelpers.parseColorString(from);
	var parsedTo = Animation.transformHelpers.parseColorString(to);
	var colorComponents = Animation.transformHelpers.blendColors(parsedFrom, parsedTo, completion);
	return Animation.transformHelpers.createHexcodeFromComponents(colorComponents);
}

Animation.transformUnits.backgroundColor = Animation.transformUnits.color;

// Transform Helpers
// various functions designed to help the making of transform units.
Animation.transformHelpers = new Object();

Animation.transformHelpers.parseColorString = function(color)
{
	if(color.match(/rgba\(/))	// alpha is ignored though
		return Animation.transformHelpers.parseRGBAColorString(color);
	if(color.match(/rgb\(/))
		return Animation.transformHelpers.parseRGBColorString(color);
	if(color.match(/#[a-fA-F0-9]{6}/))
		return Animation.transformHelpers.parseLongHexcode(color);
	if(color.match(/#[a-fA-F0-9]{3}/))
		return Animation.transformHelpers.parseShortHexcode(color);
	// if we find nothing valid, return black
	return new Array(0,0,0);
}

Animation.transformHelpers.parseRGBColorString = function(color)
{
	var colors = color.match(/rgb\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/).slice(1);
	for(var i = 0; i < 3; i++)
		colors[i] = parseInt(colors[i]);
	return colors;
}

Animation.transformHelpers.parseRGBAColorString = function(color)
{
	var colors = color.match(/rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/).slice(1,3);
	for(var i = 0; i < 3; i++)
		colors[i] = parseInt(colors[i]);
	return colors;
}

Animation.transformHelpers.parseLongHexcode = function(color)
{
	var digits = color.match(/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/).slice(1);
	for(var i = 0; i < 3; i++)
		digits[i] = parseInt(digits[i], 16);
	return digits
}

Animation.transformHelpers.parseShortHexcode = function(color)
{
	var digits = new Array();
	for(var i = 0; i < 3; i++)
	{
		digits[i] = parseInt(color.charAt(i+1), 16);
		digits[i] = (digits[i] << 4) | digits[i];
	}
	return digits;
}

Animation.transformHelpers.createHexcodeFromComponents = function(array)
{
	var returnCode = '#';
	for(var i = 0; i < 3; i++)
	{
		var hex = array[i].toString(16);
		if(hex.length == 1)
			returnCode += '0' + hex;
		else
			returnCode += hex;
	}
	return returnCode;
}

Animation.transformHelpers.blendColors = function(from, to, completion)
{
	return new Array(
		Math.floor(from[0] + (to[0] - from[0]) * completion),
		Math.floor(from[1] + (to[1] - from[1]) * completion),
		Math.floor(from[2] + (to[2] - from[2]) * completion));
}
