function absoluteLocation(element)
{
	var left = 0;
	var top = 0;

	while (element != null)
	{
		left += element.offsetLeft;
		top += element.offsetTop;

		element = element.offsetParent;
	}

	return { left: left, top: top };
}

function Delegate(instance, method)
{
	return function() {
		return method.apply(instance, arguments);
	};
}

var colors =
{
	aliceblue: "#F0F8FF",
	antiquewhite: "#FAEBD7",
	aqua: "#00FFFF",
	aquamarine: "#7FFFD4",
	azure: "#F0FFFF",
	beige: "#F5F5DC",
	bisque: "#FFE4C4",
	black: "#000000",
	blanchedalmond: "#FFEBCD",
	blue: "#0000FF",
	blueviolet: "#8A2BE2",
	brown: "#A52A2A",
	burlywood: "#DEB887",
	cadetblue: "#5F9EA0",
	chartreuse: "#7FFF00",
	chocolate: "#D2691E",
	coral: "#FF7F50",
	cornflowerblue: "#6495ED",
	cornsilk: "#FFF8DC",
	crimson: "#DC143C",
	cyan: "#00FFFF",
	darkblue: "#00008B",
	darkcyan: "#008B8B",
	darkgoldenrod: "#B8860B",
	darkgray: "#A9A9A9",
	darkgreen: "#006400",
	darkkhaki: "#BDB76B",
	darkmagenta: "#8B008B",
	darkolivegreen: "#556B2F",
	darkorange: "#FF8C00",
	darkorchid: "#9932CC",
	darkred: "#8B0000",
	darksalmon: "#E9967A",
	darkseagreen: "#8FBC8F",
	darkslateblue: "#483D8B",
	darkslategray: "#2F4F4F",
	darkturquoise: "#00CED1",
	darkviolet: "#9400D3",
	deepskyblue: "#00BFFF",
	dimgray: "#696969",
	dodgerblue: "#1E90FF",
	firebrick: "#B22222",
	floralwhite: "#FFFAF0",
	forestgreen: "#228B22",
	fuchsia: "#FF00FF",
	gainsboro: "#DCDCDC",
	ghostwhite: "#F8F8FF",
	gold: "#FFD700",
	goldenrod: "#DAA520",
	gray: "#808080",
	green: "#008000",
	greenyellow: "#ADFF2F",
	honeydew: "#F0FFF0",
	hotpink: "#FF69B4",
	indianred: "#CD5C5C",
	indigo: "#4B0082",
	ivory: "#FFFFF0",
	khaki: "#F0E68C",
	lavender: "#E6E6FA",
	lavenderblush: "#FFF0F5",
	lawngreen: "#7CFC00",
	lemonchiffon: "#FFFACD",
	lightblue: "#ADD8E6",
	lightcoral: "#F08080",
	lightcyan: "#E0FFFF",
	lightgoldenrodyellow: "#FAFAD2",
	lightgreen: "#90EE90",
	lightgrey: "#D3D3D3",
	lightpink: "#FFB6C1",
	lightsalmon: "#FFA07A",
	lightseagreen: "#20B2AA",
	lightskyblue: "#87CEFA",
	lightslategray: "#778899",
	lightsteelblue: "#B0C4DE",
	lightyellow: "#FFFFE0",
	lime: "#00FF00",
	limegreen: "#32CD32",
	linen: "#FAF0E6",
	magenta: "#FF00FF",
	maroon: "#800000",
	mediumauqamarine: "#66CDAA",
	mediumblue: "#0000CD",
	mediumorchid: "#BA55D3",
	mediumpurple: "#9370D8",
	mediumseagreen: "#3CB371",
	mediumslateblue: "#7B68EE",
	mediumspringgreen: "#00FA9A",
	mediumturquoise: "#48D1CC",
	mediumvioletred: "#C71585",
	midnightblue: "#191970",
	mintcream: "#F5FFFA",
	mistyrose: "#FFE4E1",
	moccasin: "#FFE4B5",
	navajowhite: "#FFDEAD",
	navy: "#000080",
	oldlace: "#FDF5E6",
	olive: "#808000",
	olivedrab: "#688E23",
	orange: "#FFA500",
	orangered: "#FF4500",
	orchid: "#DA70D6",
	palegoldenrod: "#EEE8AA",
	palegreen: "#98FB98",
	paleturquoise: "#AFEEEE",
	palevioletred: "#D87093",
	papayawhip: "#FFEFD5",
	peachpuff: "#FFDAB9",
	peru: "#CD853F",
	pink: "#FFC0CB",
	plum: "#DDA0DD",
	powderblue: "#B0E0E6",
	purple: "#800080",
	red: "#FF0000",
	rosybrown: "#BC8F8F",
	royalblue: "#4169E1",
	saddlebrown: "#8B4513",
	salmon: "#FA8072",
	sandybrown: "#F4A460",
	seagreen: "#2E8B57",
	seashell: "#FFF5EE",
	sienna: "#A0522D",
	silver: "#C0C0C0",
	skyblue: "#87CEEB",
	slateblue: "#6A5ACD",
	slategray: "#708090",
	snow: "#FFFAFA",
	springgreen: "#00FF7F",
	steelblue: "#4682B4",
	tan: "#D2B48C",
	teal: "#008080",
	thistle: "#D8BFD8",
	tomato: "#FF6347",
	turquoise: "#40E0D0",
	violet: "#EE82EE",
	wheat: "#F5DEB3",
	white: "#FFFFFF",
	whitesmoke: "#F5F5F5",
	yellow: "#FFFF00",
	green: "#9ACD32"
};

function TimeLine()
{
	this.animations = new Array();
	this.busy = false;

	this.append = function(element, duration, animationObject)
	{
		var start = 0;

		for (var i = 0; i < this.animations.length; i++)
		{
			var animation = this.animations[i];

			if (start < animation.start + animation.duration)
				start = animation.start + animation.duration;
		}

		this.animations.push({ element: element, start: start, duration: duration, animationObject: animationObject });
	};

	this.schedule = function(element, start, duration, animationObject)
	{
		this.animations.push({ element: element, start: start, duration: duration, animationObject: animationObject });
	};

	this.play = function(callback)
	{
		if (this.busy)
			return;

		this.busy = true;

		var start = new Date().getTime();

		for (var i = 0; i < this.animations.length; i++)
			this.animations[i].complete = false;

		function _()
		{
			var now = new Date().getTime() - start;

			var more = false;

			for (var i = 0; i < this.animations.length; i++)
			{
				var animation = this.animations[i];

				if (now < animation.start)
					continue;

				if (now >= animation.start + animation.duration)
				{
					if (!animation.complete)
					{
						animation.animationObject._step(animation.element, 1);

						animation.complete = true;

						more = true;
					}

					continue;
				}

				more = true;
				
				animation.animationObject._step(animation.element, (now - animation.start) / animation.duration);
			}

			if (more)
			{
				setTimeout(new Delegate(this, _), 1);
			}
			else
			{
				this.busy = false;

				if (callback != null)
					callback();
			}
		}

		setTimeout(new Delegate(this, _), 1);
	};
}

function BackColorAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
		{
			var color = (element.currentStyle != null ? element.currentStyle.backgroundColor : document.defaultView.getComputedStyle(element, "").getPropertyValue("background-color"));

			if (colors[color] != null)
				color = colors[color];

			if (color.match(/^rgb/))
			{
				var components = color.replace(/rgb\((.+)\)/, "$1").replace(/\s/g, "").split(",");

				this.from =
				{
					red: parseInt(components[0]),
					green: parseInt(components[1]),
					blue: parseInt(components[2])
				};
			}
			else
			{
				this.from =
				{
					red: parseInt(color.substring(1, 3), 16),
					green: parseInt(color.substring(3, 5), 16),
					blue: parseInt(color.substring(5, 7), 16)
				};
			}
		}

		if (this.to == null)
		{
			var color = (element.currentStyle != null ? element.currentStyle.backgroundColor : document.defaultView.getComputedStyle(element, "").getPropertyValue("background-color"));

			if (colors[color] != null)
				color = colors[color];

			if (color.match(/^rgb/))
			{
				var components = color.replace(/rgb\((.+)\)/, "$1").replace(/\s/g, "").split(",");

				this.to =
				{
					red: parseInt(components[0]),
					green: parseInt(components[1]),
					blue: parseInt(components[2])
				};
			}
			else
			{
				this.to =
				{
					red: parseInt(color.substring(1, 3), 16),
					green: parseInt(color.substring(3, 5), 16),
					blue: parseInt(color.substring(5, 7), 16)
				};
			}
		}

		element.style.backgroundColor = "RGB(" + parseInt(this.from.red + (this.to.red - this.from.red) * percent) + ", " + parseInt(this.from.green + (this.to.green - this.from.green) * percent) + ", " + parseInt(this.from.blue + (this.to.blue - this.from.blue) * percent) + ")";
	};
}

function HeightAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = element.offsetHeight;

		if (this.to == null)
			this.to = element.offsetHeight;

		element.style.height = parseInt(this.from + (this.to - this.from) * percent) + "px";
	};
}

function LeftAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = absoluteLocation(element).left;

		if (this.to == null)
			this.to = absoluteLocation(element).left;

		element.style.left = parseInt(this.from + (this.to - this.from) * percent) + "px";
	};
}

function LocationAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = absoluteLocation(element);

		if (this.to == null)
			this.to = absoluteLocation(element);

		element.style.left = parseInt(this.from.left + (this.to.left - this.from.left) * percent) + "px";
		element.style.top = parseInt(this.from.top + (this.to.top - this.from.top) * percent) + "px";
	};
}

function OpacityAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		// FIXME

		element.style.filter = "alpha(opacity = " + parseInt(this.from + (this.to - this.from) * percent) + ")";
		element.style.opacity = (this.from + (this.to - this.from) * percent) / 100;
	};
}

function OrbitAnimation(from, to, radius, revolutions)
{
	this.from = from;
	this.to = to;
	this.radius = radius;
	this.revolutions = revolutions;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = absoluteLocation(element);

		if (this.to == null)
			this.to = absoluteLocation(element);

		element.style.left = parseInt((this.from.left + (this.to.left - this.from.left) * percent) + Math.sin(percent * Math.PI * 2 * this.revolutions) * this.radius) + "px";
		element.style.top = parseInt((this.from.top + (this.to.top - this.from.top) * percent) - Math.cos(percent * Math.PI * 2 * this.revolutions) * this.radius) + "px";
	};
}

function RotateAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			;  // FIXME

		if (this.to == null)
			;  // FIXME

		element.style.transform = "rotate(" + parseInt(this.from + (this.to - this.from) * percent) + "deg)";
		element.style.MozTransform = "rotate(" + parseInt(this.from + (this.to - this.from) * percent) + "deg)";
		element.style.msTransform = "rotate(" + parseInt(this.from + (this.to - this.from) * percent) + "deg)";
		element.style.oTransform = "rotate(" + parseInt(this.from + (this.to - this.from) * percent) + "deg)";
		element.style.webkitTransform = "rotate(" + parseInt(this.from + (this.to - this.from) * percent) + "deg)";
	};
}

function SizeAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = { width: element.offsetWidth, height: element.offsetHeight };

		if (this.to == null)
			this.to = { width: element.offsetWidth, height: element.offsetHeight };

		element.style.width = parseInt(this.from.width + (this.to.width - this.from.width) * percent) + "px";
		element.style.height = parseInt(this.from.height + (this.to.height - this.from.height) * percent) + "px";
	};
}

function TopAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = absoluteLocation(element).top;

		if (this.to == null)
			this.to = absoluteLocation(element).top;

		element.style.top = parseInt(this.from + (this.to - this.from) * percent) + "px";
	};
}

function WidthAnimation(from, to)
{
	this.from = from;
	this.to = to;

	this._step = function(element, percent)
	{
		if (this.from == null)
			this.from = element.offsetWidth;

		if (this.to == null)
			this.to = element.offsetWidth;

		element.style.width = parseInt(this.from + (this.to - this.from) * percent) + "px";
	};
}
