function harmonicGenerator() {
	this.debug = true;
	var obj;
	this.callbacks = new Array();
	this.callbacks['cleanup'] = new Array();
}
harmonicGenerator.prototype.start = function() {
	this.trace('start()');
	obj = this; // store reference to this in global var so interval callback can access this object instance
	var intervalTime = 1000/this.frequency;
	this.totalSteps = Math.ceil(this.duration/1000 * this.frequency);
	this.interval = setInterval(this.progress, intervalTime);
	this.startDate = new Date();
	this.startTime = this.startDate.getTime();
}
harmonicGenerator.prototype.getFactor = function() {
	// calculate progress based on time (rather than, as previously done, by using step num).
	var now = new Date();
	var factor = (now.getTime() - this.startTime) / this.duration;
	if (factor > 1) factor = 1;
	var degree = factor*180-90;
	// convert to radions
	var factor = (Math.sin(Math.PI/180*degree)+1)/2;
	return factor;
}
harmonicGenerator.prototype.progress = function() {
	// handle reaching EOL
	obj.factor = obj.getFactor();
	if (obj.factor < 1) {
		obj.nextStep();
	} else {
		// do cleanup callbacks
		clearInterval(obj.interval);
		obj.trace('Clean up.');
		var func, o;
		// do internal callback if exists
		if (obj.cleanup)
			obj.cleanup();
		if (obj.callbacks['cleanup'].length > 0) {
			for (var i in obj.callbacks['cleanup']) {
				func = obj.callbacks['cleanup'][i][0];
				o = obj.callbacks['cleanup'][i][1];
				if (o)
					func.apply(o);
				else
					func();
			}
		}
	}
}
harmonicGenerator.prototype.nextStep = function() {
	// handle reaching EOL
	this.trace("Abstract function.\nPlease concretionize!");
	clearInterval(obj.interval);
}
// callback handlers
harmonicGenerator.prototype.on_cleanup = function(f, obj) {
	if (f == false) {
		this.callbacks['cleanup'] = new Array();
	} else {
		this.callbacks['cleanup'].push(new Array(f, obj));
		this.trace('adding on_cleanup function');
	}
}

harmonicGenerator.prototype.trace = function(msg) {
	if (this.debug)
		alert(msg);
}

// extend harmonicGenerator class
harmonicEnheightener.prototype = new harmonicGenerator();

function harmonicEnheightener(id) {
	this.elementId = id;
	this.debug = false;
	this.duration = 500; // time in MS
	this.frequency = 30; // frequency in Hz (ie frame rate)
}
harmonicEnheightener.prototype.resize = function(height) {
	this.div = document.getElementById(this.elementId);
	this.startHeight = this.div.offsetHeight;
	this.endHeight = height;
	this.distance = this.endHeight - this.startHeight;
	this.trace('this.distance ' + this.distance);
	this.start();
}
harmonicEnheightener.prototype.cleanup = function() {
		this.div.style.height = this.endHeight + 'px';
}
harmonicEnheightener.prototype.nextStep = function() {
	var nextHeight = Math.round(this.factor * this.distance + this.startHeight);
	this.div.style.height = nextHeight + 'px';
	this.trace('nextHeight ' + nextHeight);
}

function getSizeOfDiv(id) {
	var div = document.getElementById(id);
	return div.offsetHeight;
}

mover.prototype = new harmonicGenerator();

function mover(id) {	
	this.elementId = id;
	this.debug = false;
	this.duration = 400; // time in MS
	this.frequency = 90; // frequency in Hz (ie frame rate)
}

mover.prototype.moveTo = function(x, y) {
	this.div = document.getElementById(this.elementId);
	this.startPosX = this.div.offsetLeft;
	this.startPosY = this.div.offsetTop;
	this.endPosX = x;
	this.endPosY = y;
	this.xDistance = x - this.startPosX;
	this.yDistance = y - this.startPosY;
	this.trace("this.xDistance: " + this.xDistance + "\nthis.yDistance: " + this.yDistance);
	this.start();
}

mover.prototype.nextStep = function() {
	var nextPosX = Math.round(this.factor * this.xDistance + this.startPosX);
	var nextPosY = Math.round(this.factor * this.yDistance + this.startPosY);
	
	this.trace('nextPosX: ' + nextPosX + '\nnextPosY: ' + nextPosY);
	this.div.style.left = nextPosX + 'px';
	this.div.style.top = nextPosY + 'px';
}

mover.prototype.cleanup = function() {
	this.div.style.left = this.endPosX + 'px';
	this.div.style.top = this.endPosY + 'px';
}


function sheetPositionManager() {
	this.sheetObjs = new Array();
}
sheetPositionManager.prototype.add = function(obj) {
	this.sheetObjs.push(obj);
}

sheetPositionManager.prototype.update = function() {
	for(i in this.sheetObjs) {
		this.sheetObjs[i].correctPosition();
	}
}
var sheetPositionManager = new sheetPositionManager();
if (isMSIE()) {
	window.onscroll = function() {sheetPositionManager.update()};
}

sheetDisplayer.prototype = new mover();
function sheetDisplayer(id) {
	this.elementId = id;
	this.duration = 500; // time in MS
	this.isShown = false;
	if (!isMSIE()) {
		document.getElementById(this.elementId).style.position = 'fixed';
	}
	sheetPositionManager.add(this);
}
sheetDisplayer.prototype.show = function() {
	if (this.isShown != true) {
		document.getElementById(this.elementId).style.display = 'block';
		// start off screen
		var height = document.getElementById(this.elementId).offsetHeight;
		if (isMSIE()) {
			var startYPos = (0+this.getScrollTop())-height;
			var endYPos = 0+this.getScrollTop();
		} else {
			var startYPos = 0-height;
			var endYPos = 0;
		}
		document.getElementById(this.elementId).style.top = startYPos + 'px';
		// centre horizontally
		var startXPos = document.body.offsetWidth/2 - document.getElementById(this.elementId).offsetWidth/2;
		document.getElementById(this.elementId).style.left = startXPos + 'px';
		this.on_cleanup(false);
		this.moveTo(startXPos, endYPos);
		this.isShown = true;
	}
}
sheetDisplayer.prototype.hide = function() {
	if (this.isShown == true) {
		var xPos = document.getElementById(this.elementId).offsetLeft;
		var height = document.getElementById(this.elementId).offsetHeight;
		if (isMSIE()) {
			var endYPos = (0+this.getScrollTop())-height;
		} else {
			var endYPos = 0-height;
		}
		this.moveTo(xPos, endYPos);
		this.on_cleanup(false);
		this.on_cleanup(function () {
			// hide element after off screen
			document.getElementById(obj.elementId).style.display = 'none';
		});
		this.isShown = false;
	}
}
sheetDisplayer.prototype.correctPosition = function() {
	if (isMSIE()) {
		if (this.isShown == true) {
			document.getElementById(this.elementId).style.top = this.getScrollTop() + 'px';
		}
	}
}
sheetDisplayer.prototype.getScrollTop = function() {
	// work out where to find sctollTop info
	if (document.documentElement && document.documentElement.scrollTop) {
		var top = document.documentElement.scrollTop;
	} else {
		var top = document.body.scrollTop;
	}
	return top;
}
