var renderEngine;
var interpolator;

document.observe('dom:loaded', function()
{
	renderEngine = new RenderEngine(60);
	interpolator = new Interpolator();
});

var Interpolator = Class.create(
{
	clamp: function(value, min, max)
	{
		return Math.max(min, Math.min(max, value))	
	},
	
	linear: function(startValue, endValue, time)
	{
		return startValue + time * (endValue - startValue);
	},
	
	cosine: function(startValue, endValue, time)
	{
		return this.linear(startValue, endValue, -Math.cos(Math.PI * time) / 2 + 0.5);
	},
	
	smoothStep: function(startValue, endValue, time)
	{
		return this.linear(startValue, endValue, time * time * (3 - 2 * time));
	}
});

var RenderEngine = Class.create(
{
	initialize: function(frameRate)
	{
		this.frameDelay = (1 / frameRate) * 1000; 
		this.renderables = new Array();
		this.isRunning = false;
	},
	
	addRenderable: function(renderable)
	{
		this.renderables.push(renderable);
		
		if(!this.isRunning)
		{
			this.lastTick = new Date().getTime();
			this.isRunning = true
			setTimeout(this.update.bind(this), this.frameDelay);
		};
	},
	
	update: function()
	{
		newTime = new Date().getTime();
		elapsed = newTime - this.lastTick;
		this.lastTick = newTime;

		for(i = 0; i < this.renderables.length; )
		{
			if(this.renderables[i].update(elapsed))
			{
				this.renderables = this.renderables.without(this.renderables[i]);
			} 
			else
			{
				++i;
			}
		}
		
		if(this.renderables.length > 0)
		{
			setTimeout(this.update.bind(this), this.frameDelay);
		}
		else
		{
			this.isRunning = false;
		}
	}
});


var OpacityInterpolator = Class.create(
{
	initialize: function(targetObject, targetValue, duration)
	{
		this.targetObject = targetObject;
		this.initialValue = targetObject.getOpacity();
		this.targetValue = targetValue;
		this.duration = duration * 1000;
		this.totalElapsed = 0;
	},

	update: function(elapsed)
	{
		var isDead = false;
		this.totalElapsed += elapsed;
		
		if(this.totalElapsed > this.duration)
		{
			this.totalElapsed = this.duration;
			isDead = true;
		}
		
		opacity = interpolator.linear(this.initialValue, this.targetValue, this.totalElapsed / this.duration);

		this.targetObject.setOpacity(opacity);
		
		return isDead;
	}
});

var SmoothRemover = Class.create(
{
	initialize: function(targetObject, duration, deleteNode)
	{
		this.targetObject = targetObject
		this.deleteNode = deleteNode;
		this.initialHeight = targetObject.getHeight();
		this.duration = duration * 1000;
		this.empty = false;
		this.totalElapsed = 0;
	},
	
	update: function(elapsed)
	{
		this.totalElapsed += elapsed;
		
		var isDead = false;
		
		if(this.totalElapsed > this.duration)
		{
			this.totalElapsed = this.duration;
			isDead = true;
			
			if(this.deleteNode)
			{
				this.targetObject.remove();
			}
		}
		
		opacity = interpolator.linear(1, 0, interpolator.clamp((this.totalElapsed / this.duration) * 2, 0, 1));
		height = interpolator.smoothStep(this.initialHeight, 0, interpolator.clamp((this.totalElapsed / this.duration) * 2 - 1, 0, 1));

		this.targetObject.setOpacity(opacity);
		this.targetObject.style.height = height + 'px';
		
		return isDead;
	}
});

var SmoothExpander = Class.create(
{
	initialize: function(targetObject, duration)
	{
		this.originalObject = targetObject;
		this.targetObject = targetObject.wrap('div');
		
		this.duration = duration * 1000;
		this.empty = false;
		this.totalElapsed = 0;
		
		this.targetObject.setOpacity(0);
		this.targetObject.style.height = '0px';

		targetObject.setOpacity(1);
		targetObject.style.height = 'auto';
		targetObject.show();

		this.targetHeight = targetObject.getHeight();
	},
	
	update: function(elapsed)
	{
		this.totalElapsed += elapsed;

		var isDead = false;
		
		if(this.totalElapsed > this.duration)
		{
			this.targetObject.replace(this.originalObject);
			this.totalElapsed = this.duration;
			isDead = true;
		}
		
		opacity = Math.min(1, ((this.totalElapsed * 2 - this.duration) / this.duration));
		height = Math.min(1, (this.totalElapsed * 2 / this.duration)) * this.targetHeight;
		
		this.targetObject.setOpacity(opacity);
		this.targetObject.style.height = height + 'px';
		
		return isDead;
	}
});

var SmoothReplacer = Class.create(
{
	initialize: function(oldObject, newObject, duration)
	{
		this.totalElapsed = 0;
		this.hasSwapped = false;
		
		this.oldObject = oldObject;
		this.newObject = newObject;
		this.duration = duration * 1000;
		this.oldObjectWrapper = Element.wrap(oldObject, 'div');
		this.newObjectWrapper = Element.wrap(newObject, 'div');
		
		this.oldObjectWrapper.style.height = oldObject.getHeight() + 'px';
		
		this.newObjectWrapper.setOpacity(0);
		this.newObjectWrapper.style.height = '0px';
		
		newObject.setOpacity(1);
		newObject.style.height = 'auto';
		newObject.show();
		
		this.oldHeight = oldObject.getHeight();
		this.newHeight = newObject.getHeight();
	},
	
	update: function(elapsed)
	{
		var isDead = false;
		
		this.totalElapsed += elapsed;
		
		if(!this.hasSwapped && this.totalElapsed > this.duration / 2)
		{
			this.hasSwapped = true;
			
			temp = this.newObjectWrapper.style.height;
			
			this.newObjectWrapper.style.height = this.oldObjectWrapper.style.height;
			this.oldObjectWrapper.style.height = temp;
		}
		
		if(this.totalElapsed > this.duration)
		{
			isDead = true;
			this.totalElapsed = this.duration;
			
			this.oldObject.hide();
			this.oldObjectWrapper.replace(this.oldObject);
			this.newObjectWrapper.replace(this.newObject);
		}
		
		oldOpacity = interpolator.linear(1, 0, interpolator.clamp((this.totalElapsed / this.duration) * 2, 0, 1));
		newOpacity = interpolator.linear(0, 1, interpolator.clamp((this.totalElapsed / this.duration) * 2 - 1, 0, 1));

		this.oldObjectWrapper.setOpacity(oldOpacity);
		this.newObjectWrapper.setOpacity(newOpacity);
		
		if(this.newHeight > this.oldHeight)
		{
			height = interpolator.smoothStep(this.oldHeight, this.newHeight, interpolator.clamp((this.totalElapsed / this.duration) * 2, 0, 1));
		}
		else
		{
			height = interpolator.smoothStep(this.oldHeight, this.newHeight, interpolator.clamp((this.totalElapsed / this.duration) * 2 - 1, 0, 1));
		}
		
		if(this.hasSwapped)
		{
			this.newObjectWrapper.style.height = height + 'px';
		}
		else
		{
			this.oldObjectWrapper.style.height = height + 'px';
		}

		return isDead;
	}
	
});

