function linear (value, bounce)
{
	return value;
}

function easeIn (value, bounce)
{
	if (bounce)
	{
		exponential(value);
	}
	return Math.sin( value * (Math.PI/2) );
}

function easeOut (value, bounce)
{
	if (bounce)
	{
		logarithmic(value);
	}
	return (  Math.asin(value) / (Math.PI / 2 )  );
}

function easeInAndOut (value, bounce)
{
	return (  Math.asin(value) / (Math.PI / 2) * (1 - value)  ) +  (  Math.sin( value * (Math.PI/2) ) * value  );
}

function exponential (value, bounce)
{
	if (bounce)
	{
		easeIn(value);
	}
	return Math.pow( value, 2 );
}

function logarithmic (value, bounce)
{
	if (bounce)
	{
		easeOut(value);
	}
	return Math.sqrt(value);
}

function createInterpolationValues ( animatedAttributes, steps, curveFunction, backwards, bounce )
{
	var interpolatedAttributes = new Array;
	for ( var i in animatedAttributes )
	{
		var interpolatedValues;
		if ( animatedAttributes[i].attribute.search(/color/i) >= 0 )
		{
			interpolatedValues = { red:new Array, green:new Array, blue:new Array, alpha:new Array };

			var startColor = colorStringToRGBAColorObject(animatedAttributes[i].startValue);
			var endColor = colorStringToRGBAColorObject(animatedAttributes[i].endValue);
			var differenceRed = endColor.red - startColor.red;
			var differenceGreen = endColor.green - startColor.green;
			var differenceBlue = endColor.blue - startColor.blue;
			var differenceAlpha = endColor.alpha - startColor.alpha;
			
			for ( j = 0 ; j <= steps ; j++ )
			{
				var progress = j/steps;
				interpolatedValues.red[j] = parseInt(startColor.red + ( differenceRed * curveFunction(progress, bounce) ));
				interpolatedValues.green[j] = parseInt(startColor.green + ( differenceGreen * curveFunction(progress, bounce) ));
				interpolatedValues.blue[j] = parseInt(startColor.blue + ( differenceBlue * curveFunction(progress, bounce) ));
				interpolatedValues.alpha[j] = startColor.alpha + ( differenceAlpha * curveFunction(progress, bounce) );
			}
		}
		else
		{
			interpolatedValues = new Array;
			for ( j = 0 ; j <= steps ; j++ )
			{
				var difference = animatedAttributes[i].endValue - animatedAttributes[i].startValue;
				var progress = j/steps;
				interpolatedValues[j] = animatedAttributes[i].startValue + ( difference * curveFunction(progress, bounce) );
			}
		}
		interpolatedAttributes[i] = { attribute:animatedAttributes[i].attribute, unit:animatedAttributes[i].unit, values:interpolatedValues };
	}
	
	return interpolatedAttributes;
}

function stopAnimation (element)
{
	if ( typeof(element) == "string" )
	{
		element = id(element);
	}
	
	clearInterval(element.animation);
	element.removeAttribute("animation");
}

function animateAttributes ( element, animatedAttributes, duration, interpolation, repeat, onFinish, direction )
{
	if (!element)
	{
		return;
	}
	
	if ( element.animation )
	{
		stopAnimation(element);
	}
	
	var framesPerSecond = 24;

	if ( duration <= 0 )
	{
		duration = 1.0;
	}
	
	var steps = parseInt(framesPerSecond * duration);
	
	var computedStyle;
	if ( element.currentStyle )
	{
		computedStyle = element.currentStyle;
	}
	else if ( window.getComputedStyle )
	{
		computedStyle = getComputedStyle(element, null);
	}
	for ( var i in animatedAttributes )
	{
		if ( animatedAttributes[i].startValue == null )
		{
			var currentValue = computedStyle[ animatedAttributes[i].attribute ];
			if ( currentValue.search(/^\s*\d+/) >= 0 )
			{//Value is a number. Convert it.
				currentValue = parseFloat(currentValue);
			}
			animatedAttributes[i].startValue = currentValue;
		}
	}
	
	if ( interpolation == "easeIn" )
	{
		interpolationFunction = easeIn;
	}	
	else if ( interpolation == "easeOut" )
	{
		interpolationFunction = easeOut;
	}
	else if ( interpolation == "easeInAndOut" )
	{
		interpolationFunction = easeInAndOut;
	}
	else if ( interpolation == "exponential" )
	{
		interpolationFunction = exponential;
	}
	else if ( interpolation == "logarithmic" )
	{
		interpolationFunction = logarithmic;
	}
	else
	{
		interpolationFunction = linear;
	}
	
	var repeat = repeat || { iterations: 0, delay: null, loop: null };
	
	var direction = direction || "forward";
	
	var interpolatedAttributes = createInterpolationValues( animatedAttributes, steps, interpolationFunction );
		
	if (repeat.loop == "bounce")
	{
		var bounceInterpolatedAttributes = createInterpolationValues( animatedAttributes, steps, interpolationFunction, true );
	}
	
	var currentFrame = 0;
	
	function stepAnimation ()
	{
		if (currentFrame > steps)
		{
			if ( repeat.iterations != 0 )
			{
				repeat.iterations--;		

				if ( direction == "forward" )
				{
					direction = "backward";
				}
				else
				{
					direction = "forward";
				}

				if ( repeat.delay && direction == "forward" )
				{
					stopAnimation(element);
					function nextAnimation () { animateAttributes ( element, animatedAttributes, duration, interpolation, repeat, onFinish, direction ); };
					setTimeout( nextAnimation, repeat.delay );
					return;
				}
				else
				{
					currentFrame = 0;
				}
			}
			else
			{
				stopAnimation(element);
				if (onFinish)
				{
					onFinish();
				}
				return;
			}
		}
		
		var webkitTransformationFlag = false;
		var mozTransformationFlag = false;
		for ( var i in interpolatedAttributes )
		{			
			var frameIndex;
			var valueArray;
			if ( direction == "backward" )
			{
				frameIndex = steps - currentFrame;
				if ( repeat.loop == "bounce" )
				{
					valueArray = bounceInterpolatedAttributes;
				}
				else
				{
					valueArray = interpolatedAttributes;
				}
			}
			else
			{
				frameIndex = currentFrame;
				valueArray = interpolatedAttributes;
			}
			
			if ( animatedAttributes[i].func )
			{
				if ( valueArray[i].attribute == "webkitTransform" )
				{
					if ( webkitTransformationFlag )
					{
						element.style[ valueArray[i].attribute ] += " " + animatedAttributes[i].func + "(" + valueArray[i].values[frameIndex] + valueArray[i].unit +")";
					}
					else
					{
						element.style[ valueArray[i].attribute ] = animatedAttributes[i].func + "(" + valueArray[i].values[frameIndex] + valueArray[i].unit +")";
					}
					webkitTransformationFlag = true;
				}
				else if ( valueArray[i].attribute == "MozTransform" )
				{
					if ( mozTransformationFlag )
					{
						element.style[ valueArray[i].attribute ] += " " + animatedAttributes[i].func + "(" + valueArray[i].values[frameIndex] + valueArray[i].unit +")";
					}
					else
					{
						element.style[ valueArray[i].attribute ] = animatedAttributes[i].func + "(" + valueArray[i].values[frameIndex] + valueArray[i].unit +")";
					}
					mozTransformationFlag = true;
				}
				else if ( valueArray[i].attribute.search(/color/i) >= 0 )
				{
					if ( animatedAttributes[i].func == "rgba" )
					{
						element.style[ valueArray[i].attribute ] = animatedAttributes[i].func + "( " + valueArray[i].values.red[frameIndex] + ", " + valueArray[i].values.green[frameIndex] + ", " + valueArray[i].values.blue[frameIndex] + ", " + valueArray[i].values.alpha[frameIndex] + " )";
					}
					else
					{
						element.style[ valueArray[i].attribute ] = animatedAttributes[i].func + "( " + valueArray[i].values.red[frameIndex] + ", " + valueArray[i].values.green[frameIndex] + ", " + valueArray[i].values.blue[frameIndex] + " )";
					}
				}
				else
				{
					element.style[ valueArray[i].attribute ] = animatedAttributes[i].func + "(" + valueArray[i].values[frameIndex] + valueArray[i].unit +")";
				}
			}
			else
			{
				if ( valueArray[i].attribute.search(/color/i) >= 0 )
				{
					element.style[ valueArray[i].attribute ] = "#" + valueArray[i].values.red[frameIndex] + valueArray[i].values.green[frameIndex] + valueArray[i].values.blue[frameIndex];
				}
				else
				{
					element.style[ valueArray[i].attribute ] = valueArray[i].values[frameIndex] + valueArray[i].unit;
				}
			}
		}
		
		currentFrame++;
	}
	
	element.animation  = setInterval(stepAnimation, (1000/framesPerSecond) );
	
}
