/*
 * Provides facilities for basic property animations and animation sequencing.
 * Loosely based on Alex Uhlmann's Animation Package for Flash.
 * @document
 * @require window.js
 * @require easing.js
 */

/**
 * Default animation interval resolution (ms pause between callbacks)
 */
Animation.defaultResolution = 10;
/**
 * Default animation speed (in ms)
 */
Animation.defaultDuration = 100;
/**
 * Default easing equation.
 */
Animation.defaultEasing = Math.easeNone;

/**
 * Defines the successing sequence type
 */
Sequence.TypeSuccessive = 0;
/**
 * Defines the simultaneous sequence type
 */
Sequence.TypeSimultaneous = 1;

/**
 * Combines multiple Animation or Sequence instances.
 * When sequence is run, the children instances are run either
 * successivelly or simultaneously.
 * @param Number type The type of the sequence; either Sequence.TypeSuccessive or Sequence.TypeSimultaneous.
 * @param Object callback If this argument is a Function, it will be used as the callback. If it is an Array,
 * the first element is considered the callback object, and the second the callback method.
 * @constructor
 */
function Sequence(type, callback)
{
	Dispatcher.call(this, "onstart", "oncomplete");

	this.type = type != null ? type : Sequence.TypeSuccessive;
	this.children = new Array;
	this.currentAnimation = 0;

	if (callback instanceof Function)
		this.addListener("oncomplete", null, callback);
	else if (callback instanceof Array)
		this.addListener("oncomplete", callback[0], callback[1]);

}
Sequence.prototype = new Dispatcher;
Sequence.prototype.constructor = Sequence;

/**
 * Adds a child animation.
 * The child can be an instance of either Animation or Sequence class
 * @param Object child The instance to add
 * @param Number pause The time (in milliseconds) to wait before starting the child animation.
 */
Sequence.prototype.addChild = function (child, pause)
{
	if (!this.containsChild(child))
	{
		child.addListener("oncomplete", this, "onstepcomplete");
		this.children.push({ child: child, pause: pause || 0 });
	}
}
/**
 * Returns true if the current sequence contains the supplied child.
 * @param String child The child to verify.
 * @return Boolean True us the supplied child exists.
 */
Sequence.prototype.containsChild = function (child)
{
	var contains = false;
	for (var i = 0; i < this.children.length; i++)
	{
		if (this.children[i].child == child)
			contains = true;
	}
	return contains;
}
/**
 * Runs the animation sequence
 */
Sequence.prototype.run = function ()
{
	this.fireEvent("onstart");
	this.currentAnimation = 0;

	if (this.type == Sequence.TypeSimultaneous)
	{
		for (var i = 0; i < this.children.length; i++)
			this.runChild(i);
	}
	else
	{
		this.runChild(this.currentAnimation);
	}
}

Sequence.prototype.stop = function ()
{
	for (var i = 0; i < this.children.length; i++)
		this.stopChild(i);
}

Sequence.prototype.runChild = function (childIndex)
{
	var currentChild = this.children[childIndex];
	var childObj = currentChild.child;
	var childPause = currentChild.pause;

	var timeoutCaller = window.createCallback(childObj, "run");
	currentChild.timeout = window.setTimeout(timeoutCaller, childPause);
}

Sequence.prototype.stopChild = function (childIndex)
{
	var currentChild = this.children[childIndex];
	var childObj = currentChild.child;
	if (currentChild.timeout != null)
		window.clearTimeout(currentChild.timeout);

	childObj.stop();
}

/**
 * Fires sucessively until the sequnce is complete
 */
Sequence.prototype.onstepcomplete = function ()
{
	var sequenceComplete = false;
	if (this.type == Sequence.TypeSuccessive)
	{
		if (this.currentAnimation == this.children.length - 1)
			sequenceComplete = true;
		else
			this.runChild(++this.currentAnimation);
	}
	else
		if (++this.currentAnimation == this.children.length)
			sequenceComplete = true;

	if (sequenceComplete)
	{
		this.fireEvent("oncomplete");
	}
}

function Switch(object, property, state, callback)
{
	Dispatcher.call(this, "oncomplete");

	this.object = object;
	this.property = property;
	this.state = state;

	if (callback instanceof Function)
		this.addListener("oncomplete", null, callback);
	else if (callback instanceof Array)
		this.addListener("oncomplete", callback[0], callback[1]);
}
Switch.prototype = new Dispatcher;
Switch.prototype.constructor = Switch;

Switch.prototype.setValue = function ()
{
	this.object[this.property] = this.state;
}

Switch.prototype.run = function ()
{
	if (this.object != null)
	{
		this.setValue();
		this.fireEvent("oncomplete", { object: this.object, state: this.state });
	}
}

function VisibilityInherit(element, callback)
{
	Switch.call(this, element.style, "visibility", "inherit", callback);
}
VisibilityInherit.prototype = new Switch;
VisibilityInherit.prototype.constructor = VisibilityInherit;

function VisibilityShow(element, callback)
{
	Switch.call(this, element.style, "visibility", "visible", callback);
}
VisibilityShow.prototype = new Switch;
VisibilityShow.prototype.constructor = VisibilityShow;

function VisibilityHide(element, callback)
{
	Switch.call(this, element.style, "visibility", window.display(this.object, true), callback);
}
VisibilityHide.prototype = new Switch;
VisibilityHide.prototype.constructor = VisibilityHide;

function DisplayVisible(element, callback)
{
	Switch.call(this, element.style, "display", "block", callback);
}
DisplayVisible.prototype = new Switch;
DisplayVisible.prototype.constructor = DisplayVisible;

function DisplayNone(element, callback)
{
	Switch.call(this, element.style, "visibility", "none", callback);
}
DisplayNone.prototype = new Switch;
DisplayNone.prototype.constructor = DisplayNone;

/**
 * Combines multiple tweens of an element into an animation.
 * @param Object element The DOM element to animate.
 * @param Object easingType Defines the easing type. If the value is an instance of Function,
 * this function will be used for easing equations. If the value is an instance of an Array,
 * than the first element is considered the equations, and the remainder
 * second the callback method.
 * @param Object callback If this argument is used, it will be interpreted and added as
 * an oncomplete listener. If it's a Function instance, it will be used as the callback function.
 * If it's an Array instance than the first element is considered the callback object, and the
 * second the callback method.
 * @constructor
 */
function Animation(element, easingType, callback)
{
	Dispatcher.call(this, "onstart", "onprogress", "oncomplete");

	this.tweens = new Array;
	this.tweensComplete = 0;
	this.element = element;
	this.unit = "px";

	this.callbackInterval = Animation.defaultResolution;
	this.easingType = easingType;

	if (callback instanceof Function)
		this.addListener("oncomplete", null, callback);
	else if (callback instanceof Array)
		this.addListener("oncomplete", callback[0], callback[1]);
}
Animation.prototype = new Dispatcher;
Animation.prototype.constructor = Animation;

Animation.prototype.setUnit = function (unit)
{
	this.unit = unit;
	for (var i = 0; i < this.tweens.length; i++)
		this.tweens[i].unit = this.unit;
}

Animation.prototype.getEventObject = function (eventName)
{
	var eventObj =
	{
		animation: this,
		element: this.element
	};
	return eventObj;
}

/**
 * Adds a tween to the animation
 * @param String property The name of the property to tween.
 * @param String unit Optional unit to append to the value, typically a css unit (such as px,pt or %)
 * @param Number endValue The value to tween the property to,.
 * @param Object easingType Defines the easing type. If the value is an instance of Function,
 * this function will be used for easing equations. If the value is an instance of an Array,
 * than the first element is considered the equations, and the remainder
 * second the callback method.
 * @param Boolean roundValues If true, the values returned by the easing function will be rounded.
 */
Animation.prototype.addTween = function (property, unit, targetValue, duration, easingType, roundValues)
{
	var tweenObj = new Tween(this.element.style, property, unit, targetValue, duration, easingType, roundValues);
	tweenObj.addListener("onprogress", this, "onprogress");
	tweenObj.addListener("oncomplete", this, "oncomplete");
	this.tweens.push(tweenObj);

	return tweenObj;
}
/**
 * Returns true if the current animation contains the supplied tween.
 * @param String tween The tween to verify.
 * @return Boolean True us the supplied tween exists.
 */
Animation.prototype.containsTween = function (property)
{
	for (var i = 0; i < this.tweens.length; i++)
		if (this.tweens[i].property == property)
			return true;

	return false;
}
Animation.prototype.fireEvent = function (eventType)
{
	var eventObj = new Object;
	for (var i = 0; i < this.tweens.length; i++)
		eventObj[this.tweens[i].property] = this.tweens[i].getValue();

	Dispatcher.prototype.fireEvent.call(this, eventType, eventObj);
}

/**
 * Called continously as the tween onprogress events fire
 */
Animation.prototype.onprogress = function ()
{
	this.fireEvent("onprogress");
}
/**
 * Called when the animation is complete.
 */
Animation.prototype.oncomplete = function (eventObj)
{
	if (++this.tweensComplete == this.tweens.length)
		this.fireEvent("oncomplete");
}
/**
 * Runs the animation
 */
Animation.prototype.run = function ()
{
	this.fireEvent("onstart");
	this.tweensComplete = 0;
	for (var i = 0; i < this.tweens.length; i++)
		this.tweens[i].run();
}

Animation.prototype.stop = function ()
{
	for (var i = 0; i < this.tweens.length; i++)
		this.tweens[i].stop();
}

function ColorAnim(element, color, value, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	this.unit = "";
	this.color = color;

	this.tweenRed = this.addTween(color, this.unit, value.r, duration, easingType, true);
	this.tweenGreen = this.addTween(color, this.unit, value.g, duration, easingType, true);
	this.tweenBlue = this.addTween(color, this.unit, value.b, duration, easingType, true);

	this.tweenRed.setPropertyGetter([this, "getRed"]);
	this.tweenRed.setPropertySetter([this, "setRed"]);
	this.tweenGreen.setPropertyGetter([this, "getGreen"]);
	this.tweenGreen.setPropertySetter([this, "setGreen"]);
	this.tweenBlue.setPropertyGetter([this, "getBlue"]);
	this.tweenBlue.setPropertySetter([this, "setBlue"]);

	this.setColor(this.getColor());
}
ColorAnim.prototype = new Animation;
ColorAnim.prototype.constructor = ColorAnim;

ColorAnim.prototype.getColor = function ()
{
	var color = this.element.style[this.color];
	if (color != null)
	{
		if (color.match(/auto|transparent/))
		{
			var parent = this.element.parentNode;
			while (parent && parent.style[this.color].match(/auto|transparent/))
				parent = parent.parentNode;
			if (parent != null)
				color = parent.style[this.color];
		}
		if (color != null)
		{
			//window.status = color;
			if (color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/))
			{
				color = new Rgb(parseInt(RegExp.$1), parseInt(RegExp.$2), parseInt(RegExp.$3));
			}
			else if (color.match(/^#([\da-fA-F])([\da-fA-F])([\da-fA-F])$/))
			{
				var r = parseInt(RegExp.$1 + RegExp.$1, 16);
				var g = parseInt(RegExp.$2 + RegExp.$2, 16);
				var b = parseInt(RegExp.$3 + RegExp.$3, 16);
				color = new Rgb(r, g, b);
			}
			else if (color.match(/^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/))
			{
				var r = parseInt(RegExp.$1, 16);
				var g = parseInt(RegExp.$2, 16);
				var b = parseInt(RegExp.$3, 16);
				color = new Rgb(r, g, b);
			}
			else if (X11Colors[color] != null)
			{
				if (X11Colors[color].match(/^#([\da-fA-F]){2}([\da-fA-F]){2}([\da-fA-F]){2}$/))
				{
					var r = parseInt(RegExp.$1, 16);
					var g = parseInt(RegExp.$2, 16);
					var b = parseInt(RegExp.$3, 16);
					color = new Rgb(r, g, b);
				}
			}
		}
	}
	if (color == null)
		color = new Rgb(255, 255, 255);

	return color;
}

ColorAnim.prototype.setColor = function (color)
{
	this.element.style[this.color] = color.toString();
}
ColorAnim.prototype.getRed = function ()
{
	return this.getColor().r;
}
ColorAnim.prototype.getGreen = function ()
{
	return this.getColor().g;
}
ColorAnim.prototype.getBlue = function ()
{
	return this.getColor().b;
}
ColorAnim.prototype.setRed = function (value)
{
	var color = this.getColor();
	color.r = value;
	this.setColor(color);
}
ColorAnim.prototype.setGreen = function (value)
{
	var color = this.getColor();
	color.g = value;
	this.setColor(color);
}
ColorAnim.prototype.setBlue = function (value)
{
	var color = this.getColor();
	color.b = value;
	this.setColor(color);
}

function Clip(element, clipRect, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	this.unit = "px";

	this.tweenTop  = this.addTween("top", this.unit, clipRect.top, duration, easingType, true);
	this.tweenRight = this.addTween("right", this.unit, clipRect.right, duration, easingType, true);
	this.tweenBottom = this.addTween("bottom", this.unit, clipRect.bottom, duration, easingType, true);
	this.tweenLeft = this.addTween("left", this.unit, clipRect.left, duration, easingType, true);

	this.tweenTop.setPropertyGetter([this, "getTop"]);
	this.tweenRight.setPropertyGetter([this, "getRight"]);
	this.tweenBottom.setPropertyGetter([this, "getBottom"]);
	this.tweenLeft.setPropertyGetter([this, "getLeft"]);

	this.tweenTop.setPropertySetter([this, "setTop"]);
	this.tweenRight.setPropertySetter([this, "setRight"]);
	this.tweenBottom.setPropertySetter([this, "setBottom"]);
	this.tweenLeft.setPropertySetter([this, "setLeft"]);

	if (!this.element.style.clip)
		this.setRect(new Rect(0, this.element.offsetWidth, this.element.offsetHeight, 0));
}
Clip.prototype = new Animation;
Clip.prototype.constructor = Clip;

Clip.prototype.setRect = function (rect)
{
	this.element.style.clip = "rect({1}{5} {2}{5} {3}{5} {4}{5})".format(rect.top, rect.right, rect.bottom, rect.left, this.unit);
}

Clip.prototype.getRect = function ()
{
	var rect = new Rect(0, this.element.offsetWidth, this.element.offsetHeight, 0);
	var clipRect = this.element.style.clip;
	if (clipRect && clipRect.match(/rect\((\S+) (\S+) (\S+) (\S+)\)/))
	{
		rect.top = parseInt(RegExp.$1);
		rect.right = parseInt(RegExp.$2);
		rect.bottom = parseInt(RegExp.$3);
		rect.left = parseInt(RegExp.$4);
	}
	return rect;
}

Clip.prototype.getTop = function ()
{
	return this.getRect().top;
}

Clip.prototype.getRight = function ()
{
	return this.getRect().right;
}

Clip.prototype.getBottom = function ()
{
	return this.getRect().bottom;
}

Clip.prototype.getLeft = function ()
{
	return this.getRect().left;
}

Clip.prototype.setTop = function (value)
{
	var rect = this.getRect();
	rect.top = value;
	this.setRect(rect);
}

Clip.prototype.setRight = function (value)
{
	var rect = this.getRect();
	rect.right = value;
	this.setRect(rect);
}

Clip.prototype.setBottom = function (value)
{
	var rect = this.getRect();
	rect.bottom = value;
	this.setRect(rect);
}

Clip.prototype.setLeft = function (value)
{
	var rect = this.getRect();
	rect.left = value;
	this.setRect(rect);
}

/**
 * Represents an animation that moves an element.
 * @param Object element The element to move
 * @param Number targetX The move's target x coordinate
 * @param Number targetY The move's target y coordinate
 * @param Object easingType Defines the easing type. If the value is an instance of Function,
 * this function will be used for easing equations. If the value is an instance of an Array,
 * than the first element is considered the equations, and the remainder
 * second the callback method.
 * @param Object callback If this argument is used, it will be interpreted and added as
 * an oncomplete listener. If it's a Function instance, it will be used as the callback function.
 * If it's an Array instance than the first element is considered the callback object, and the
 * second the callback method.
 */
function Move(element, targetX, targetY, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	this.tweenLeft = this.addTween("left", "px", targetX, duration, easingType, true);
	this.tweenTop  = this.addTween("top", "px", targetY, duration, easingType, true);

	this.tweenLeft.setPropertyGetter([this, "getLeft"]);
	this.tweenTop.setPropertyGetter([this, "getTop"]);
}
Move.prototype = new Animation;
Move.prototype.constructor = Move;

/**
 * Returns the 'left' property from the current element's style object.
 * @return Number Current object's 'left' property.
 */
Move.prototype.getLeft = function ()
{
	var left = this.element.style.left;
	if (left == "")
		left = this.element.style.pixelLeft = window.getLeft(this.element);
	if (left == "auto")
		left = 0;

	return parseInt(left);
}

/**
 * Returns the 'top' property from the current element's style object.
 * @return Number Current object's 'top' property.
 */
Move.prototype.getTop = function ()
{
	var top = this.element.style.top;
	if (top == "")
		top = this.element.style.pixelTop = window.getTop(this.element);
	if (top == "auto")
		top = 0;

	return parseInt(top);
}

Move.prototype.setEndValue = function (x, y)
{
	this.tweenLeft.endValue = x;
	this.tweenTop.endValue = y;
}

function MoveX(element, targetX, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	this.tweenLeft = this.addTween("left", "px", targetX, duration, easingType, true);
	this.tweenLeft.setPropertyGetter([this, "getLeft"]);
}
MoveX.prototype = new Animation;
MoveX.prototype.constructor = MoveX;
MoveX.prototype.getLeft = Move.prototype.getLeft;

function MoveY(element, targetY, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	this.tweenTop  = this.addTween("top", "px", targetY, duration, easingType, true);
	this.tweenTop.setPropertyGetter([this, "getTop"]);
}
MoveY.prototype = new Animation;
MoveY.prototype.constructor = MoveY;
MoveY.prototype.getTop = Move.prototype.getTop;

/**
 * Represents an animation that stretches/shrinks the width and height of an element.
 * @param Object element The element to stretch or shrink
 * @param Number targetWidth The element's target width
 * @param Number targetHeight The element's target height
 * @param Object easingType Defines the easing type. If the value is an instance of Function,
 * this function will be used for easing equations. If the value is an instance of an Array,
 * than the first element is considered the equations, and the remainder
 * second the callback method.
 * @param Object callback If this argument is used, it will be interpreted and added as
 * an oncomplete listener. If it's a Function instance, it will be used as the callback function.
 * If it's an Array instance than the first element is considered the callback object, and the
 * second the callback method.
 */
function Stretch(element, targetWidth, targetHeight, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	var tweenWidth = this.addTween("width", this.unit, targetWidth, duration, easingType, true);
	var tweenHeight = this.addTween("height", this.unit, targetHeight, duration, easingType, true);

	tweenWidth.setPropertyGetter([this, "getWidth"]);
	tweenHeight.setPropertyGetter([this, "getHeight"]);
}
Stretch.prototype = new Animation;
Stretch.prototype.constructor = Stretch;

/**
 * Returns the 'width' property from the current element's style object.
 * @return Number Current object's 'width' property.
 */
Stretch.prototype.getWidth = function ()
{
	var width = this.element.style.width || this.element.style.width;
	if (width == "auto")
	{
		if (this.unit == "px")
			width = this.element.style.pixelWidth = this.element.offsetWidth;
		else
			width = this.element.style.width = 100 + this.unit;
	}

	return parseInt(width);
}

/**
 * Returns the 'height' property from the current element's style object.
 * @return Number Current object's 'height' property.
 */
Stretch.prototype.getHeight = function ()
{
	var height = this.element.style.height || this.element.style.height;
	if (height == "auto")
	{
		if (this.unit == "px")
			height = this.element.style.pixelHeight = this.element.offsetHeight;
		else
			height = this.element.style.height = 100 + this.unit;
	}

	return parseInt(height);
}

function StretchWidth(element, targetWidth, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	var tweenWidth = this.addTween("width", "px", targetWidth, duration, easingType, true);

	tweenWidth.setPropertyGetter([this, "getWidth"]);
}
StretchWidth.prototype = new Animation;
StretchWidth.prototype.constructor = StretchWidth;
StretchWidth.prototype.getWidth = Stretch.prototype.getWidth;

function StretchHeight(element, targetHeight, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	var tweenHeight = this.addTween("height", "px", targetHeight, duration, easingType, true);

	tweenHeight.setPropertyGetter([this, "getHeight"]);
}
StretchHeight.prototype = new Animation;
StretchHeight.prototype.constructor = StretchHeight;
StretchHeight.prototype.getHeight = Stretch.prototype.getHeight;

function Fade(element, targetAlpha, duration, easingType, callback)
{
	Animation.call(this, element, easingType, callback);

	var tweenAlpha = this.addTween("-moz-opacity", "", targetAlpha, duration, easingType, true);

	tweenAlpha.setPropertyGetter([this, "getAlpha"]);
	tweenAlpha.setPropertySetter([this, "setAlpha"]);

	this.ie_filter = null;

	if (window.is_ie)
	{
		for (var i = 0; i < this.element.filters.length; i++)
		{
			if (this.element.filters[i].opacity != null)
				this.ie_filter = this.element.filters[i];
		}
		if (this.ie_filter == null)
		{
			this.element.style.filter += "alpha(opacity=100)";
			this.ie_filter = this.element.filters[this.element.filters.length-1];
		}
	}
	else
	{
		if (this.element.style.opacity == "")
			this.element.style.opacity = this.element.style.opacity;
	}
}
Fade.prototype = new Animation;
Fade.prototype.constructor = Fade;

Fade.prototype.getAlpha = function ()
{
	var opacity = 100;
	if (window.is_ie)
		opacity = this.ie_filter.opacity;
	else
		opacity = parseFloat(this.element.style.opacity) * 100;

	return opacity;
}
Fade.prototype.setAlpha = function (value)
{
	if (window.is_ie)
		this.ie_filter.opacity = value;
	else
		this.element.style.opacity = value / 100;
}

/**
 * Tweens an object's property
 * @param Object object The object whose property should be tweened
 * @param String property The name of the property to tween.
 * @param String unit Optional unit to append to the value, typically a css unit (such as px,pt or %)
 * @param Number endValue The value to tween the property to,.
 * @param Object easingType Defines the easing type. If the value is an instance of Function,
 * this function will be used for easing equations. If the value is an instance of an Array,
 * than the first element is considered the equations, and the remainder
 * second the callback method.
 * @param Boolean roundValues If true, the values returned by the easing function will be rounded.
 */
function Tween(object, property, unit, endValue, duration, easingType, roundValues)
{
	Dispatcher.call(this, "onstart", "onprogress", "oncomplete");

	this.isPaused = false;

	this.endValue = endValue;
	this.duration = duration;
	this.object = object;
	this.unit = unit || "";

	this.property = property;
	this.timeStart = null;
	this.beginValue = null;
	this.roundValues = roundValues;

	this.propertyGet = [this, "_propertyGet"];
	this.propertySet = [this, "_propertySet"];

	this.easingFx = Animation.defaultEasing;
	this.easingParams = null;

	if (easingType instanceof Array)
	{
		this.easingFx = easingType[0];
		this.easingParams = easingType.slice(1);
	}
	else if (easingType != null)
	{
		this.easingFx = easingType;
		this.easingParams = null;
	}
}
Tween.prototype = new Dispatcher;
Tween.prototype.constructor = Tween;

/**
 * Assigns the object and method to handle the retrieving of property value.
 * @param Array getterFx Two element array where the first argument is the object
 * whose method should be called, and the second is the name of the method to call.
 */
Tween.prototype.setPropertyGetter = function (getterFx)
{
	this.propertyGet = getterFx;
}

/**
 * Assigns the object and method to handle the setting of property value.
 * @param Array getterFx Two element array where the first argument is the object
 * whose method should be called, and the second is the name of the method to call.
 */
Tween.prototype.setPropertySetter = function (setterFx)
{
	this.propertySet = setterFx;
}

/**
 * Returns the actual element's value of the current tween's property.
 * @return Number The current property value.
 */
Tween.prototype._propertyGet = function ()
{
	return parseInt(this.object[this.property]);
}

/**
 * Sets the actual element's value of the current tween's property.
 * @param Number value The value to set.
 */
Tween.prototype._propertySet = function (value)
{
	try
	{
		this.object[this.property] = value + this.unit;
	}
	catch(e) {
		window.status =
			"Setting property '" + this.property +
			"' to '" + value + this.unit +
			"' failed: " + e.message;
	}
}

/**
 * Returns the value of the call to propertyGet.
 * @return Number The current property value.
 */
Tween.prototype.getValue = function ()
{
	return this.propertyGet[0][this.propertyGet[1]]();
}

/**
 * Calls propertySet.
 * @param Number value The value to set.
 */
Tween.prototype.setValue = function (value)
{
	this.propertySet[0][this.propertySet[1]](value);
}

/**
 * Runs the tween
 */
Tween.prototype.run = function ()
{
	this.timeStart = new Date().getTime();
	this.beginValue = this.getValue();

	if (!isNaN(this.beginValue))
	{
		this.intervalCallback = window.createCallback(this, "step");
		this.interval = window.setInterval(this.intervalCallback, Animation.defaultResolution);
	}
}

/**
 * Called continuously until the tween is complete.
 */
Tween.prototype.step = function ()
{
	var timeElapsed = new Date().getTime() - this.timeStart;
	var easingParams = [
		timeElapsed,
		this.beginValue,
		(this.endValue - this.beginValue),
		this.duration
	];

	if (this.easingParams)
		for (var i = 0; i < this.easingParams.length; i++)
			easingParams.push(this.easingParams[i]);

	var targetValue = this.easingFx.apply(this, easingParams);

	if (this.roundValues)
		targetValue = Math.round(targetValue);

	this.setValue(targetValue);
	this.fireEvent("onprogress", { tween: this, value: currValue });

	var currValue = this.getValue();
	if (currValue == this.endValue || timeElapsed >= this.duration)
	{
		this.stop();
		this.setValue(this.endValue);

		this.fireEvent("onprogress", { tween: this, value: this.endValue });
		this.fireEvent("oncomplete", { tween: this, value: this.endValue });
		this.intervalCallback = null;
	}
}

Tween.prototype.stop = function ()
{
	window.clearInterval(this.interval);
}

function Rect(top, right, bottom, left)
{
	this.top = top;
	this.right = right;
	this.bottom = bottom;
	this.left = left;
}

Rect.clipRect = function (elem, type)
{
	var w = elem.offsetWidth;
	var h = elem.offsetHeight;
	switch (type)
	{
		case "T":
			return new Rect(0, w, 0, 0);
		case "L":
			return new Rect(0, 0, h, 0);
		case "B":
			return new Rect(h, w, h, 0);
		case "R":
			return new Rect(0, w, h, w);
		case "TL":
			return new Rect(0, 0, 0, 0);
		case "TR":
			return new Rect(0, w, 0, w);
		case "BL":
			return new Rect(h, 0, h, 0);
		case "BR":
			return new Rect(h, w, h, w);
		default:
			return new Rect(0, w, h, 0);
	}
}


function Rgb(r, g, b)
{
	this.r = r;
	this.g = g;
	this.b = b;
}
Rgb.prototype.toString = function ()
{
	if (arguments[0] == "hex")
	{
		var r = decimalToHex(this.r);
		var g = decimalToHex(this.g);
		var b = decimalToHex(this.b);

		r = r.length == 1 ? "0" + r : r;
		g = g.length == 1 ? "0" + g : g;
		b = b.length == 1 ? "0" + b : b;

		return ("#" + r + g + b);
	}
	else
		return "rgb({1}, {2}, {3})".format(this.r, this.g, this.b);
}

var HD ="0123456789ABCDEF";
function decimalToHex(value)
{
	var string = String(value);
	var hex = HD.substr(value & 15, 1);
	while(value > 15)
	{
		value >>= 4;
		hex = HD.substr(value & 15, 1) + hex;
	}
	return hex;
}
var X11Colors =
{
	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", deeppink: "#ff1493", 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", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db",
	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: "#6b8e23", orange: "#ffa500", orangered: "#ff4500",
	orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", 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", yellowgreen: "#9acd32"
};


