// --------
// Part I
// --------

// object constructor
function createObjectById(id)
{
	this.el = document.getElementById ? document.getElementById(id) : null;
	if (!this.el) throw new Error('Element with "' + id + '" id not found.');
	this.css = this.el.style;
	this.i = createObjectById.registry.length;
	createObjectById.registry[this.i] = this;
	this.w = this.el.offsetWidth ? this.el.offsetWidth : 0;
	this.h = this.el.offsetHeight ? this.el.offsetHeight : 0;
	this.x = this.el.offsetLeft ? this.el.offsetLeft : 0;
	this.y = this.el.offsetTop ? this.el.offsetTop : 0;
	this.o = 100;
}

// global reference to the dhtml object
createObjectById.registry = [];

// update object values
createObjectById.prototype.update = function()
{
	this.w = this.el.offsetWidth ? this.el.offsetWidth : 0;
	this.h = this.el.offsetHeight ? this.el.offsetHeight : 0;
	this.x = this.el.offsetLeft ? this.el.offsetLeft : 0;
	this.y = this.el.offsetTop ? this.el.offsetTop : 0;
}

// visibility
createObjectById.prototype.show = function() { this.css.visibility = 'visible'; }
createObjectById.prototype.hide = function() { this.css.visibility = 'hidden'; }

// display
createObjectById.prototype.display = function(d) { this.css.display = d; }

// colors
createObjectById.prototype.fg = function(f) { this.css.color = f; }
createObjectById.prototype.bg = function(b) { this.css.backgroundColor = b; }

// set layer opacity (in percent, between 0 and 100)
createObjectById.prototype.setOpacity = function(o)
{
//	if (typeof this.css.MozOpacity != 'undefined') { this.css.MozOpacity = (o / 100); this.o = o; }
//	else if (this.el.filters) { this.css.filter = 'alpha(opacity=' + o + ')'; this.o = o; }
	this.css.MozOpacity = (o / 100);
	this.css.opacity = (o / 100);
	this.css.filter = 'alpha(opacity=' + o + ')';
	this.o = o;
}

// moves a layer to (x, y) pixels
createObjectById.prototype.moveTo = function(x, y)
{
	if (x != null && typeof x == 'number')
	{
		x = Math.round(x);
		this.x = x;
		this.css.left = x + 'px';
	}
	if (y != null && typeof y == 'number')
	{
		y = Math.round(y);
		this.y = y;
		this.css.top = y + 'px';
	}
}

// resize layer to (width, height) pixels
createObjectById.prototype.setSize = function(w, h)
{
	if (w != null && typeof w == 'number')
	{
		if (w < 0) w = 0;
		w = Math.round(w);
		this.css.width = w + 'px';
	}
	if (h != null && typeof h == 'number')
	{
		if (h < 0) h = 0;
		h = Math.round(h);
		this.css.height = h + 'px';
	}
	this.update();
}

// clips layer to specific dimensions; (top, right, bottom, left) in pixels
createObjectById.prototype.clipTo = function(t, r, b, l)
{
	if (t < 0) t = 0; if (r < 0) r = 0; if (b < 0) b = 0; if (l < 0) l = 0;
	this.css.clip = 'rect(' + t + 'px, ' + r + 'px, ' + b + 'px, ' + l + 'px)';
}

// write content to layer
createObjectById.prototype.write = function(text)
{
	if (typeof this.el.innerHTML != 'undefined')
	{
		this.el.innerHTML = text;
		this.update();
	}
}

// --------
// Part II
// --------

// timer variables
createObjectById.prototype.slideTimer = 0;
createObjectById.prototype.resizeTimer = 0;
createObjectById.prototype.fadeTimer = 0;

// slideTo(); slides a layer from it's current position to another point over a specified time period.
// x, y     = destination coordinates in pixels
// duration = how long in milliseconds to do the animation for
// acc      = coefficient of accelleration: 1 = accelerate; 0 = linear; -1 = deccelerate
//            (if acc > 1 then it will go backwards before heading towards destination)
// fn       = execute onSlideEnd() function when animation has finished? true or false.
createObjectById.prototype.slideTo = function(x, y, duration, acc, fn)
{
	clearInterval(this.slideTimer);
	if (!acc) acc = 0;
	if (!fn) fn = false;
	var startTime = new Date().getTime();
	var endTime = startTime + duration;
	this.slideTimer = setInterval('createObjectById.registry[' + this.i + '].slide(' + this.x + ', ' + this.y + ', ' + x + ', ' + y + ', ' + acc + ', ' + startTime + ', ' + endTime + ', ' + fn + ')', 10);
}

createObjectById.prototype.onSlide = new Function();
createObjectById.prototype.onSlideEnd = new Function();

createObjectById.prototype.slide = function(startX, startY, endX, endY, acc, startTime, endTime, fn)
{
	var currentTime = new Date().getTime();
	if (currentTime < endTime)
	{
		var percent = (currentTime - startTime) / (endTime - startTime);
		var c1 = new createObjectById.coord(1, 1);
		var c2 = new createObjectById.coord(0, 0);
		if (acc != 0) var c3 = new createObjectById.coord(0.5 + (acc / 2), 0.5 - (acc / 2)); else c3 = null;
		var pos = createObjectById.getBezier(percent, c1, c2, c3);
		var x = ((endX - startX) * pos.y) + startX;
		var y = ((endY - startY) * pos.y) + startY;
		this.moveTo(x, y);
		this.onSlide(x, y);
	}
	else
	{
		clearInterval(this.slideTimer);
		this.moveTo(endX, endY);
		this.onSlide(endX, endY);
		if (fn) this.onSlideEnd(endX, endY);
	}
}

// bezierSlide(); like slideTo but instead of a straight line it follows a bezier curve
// x, y     = destination coordinates in pixels
// c1x, c1y,
// c2x, c2y = bezier control points
// duration = how long in milliseconds to do the animation for
// acc      = coefficient of accelleration: 1 = accelerate; 0 = linear; -1 = deccelerate
//            (if acc > 1 then it will go backwards before heading towards destination)
// fn       = execute onSlideEnd() function when animation has finished? true or false.
createObjectById.prototype.bezierSlide = function(x, y, c1x, c1y, c2x, c2y, duration, acc, fn)
{
	clearInterval(this.slideTimer);
	if (!acc) acc = 0;
	if (!fn) fn = false;
	var startTime = new Date().getTime();
	var endTime = startTime + duration;
	this.slideTimer = setInterval('createObjectById.registry[' + this.i + '].slideBezier(' + this.x + ', ' + this.y + ', ' + x + ', ' + y + ', ' + c1x + ', ' + c1y + ', ' + c2x + ', ' + c2y + ', ' + acc + ', ' + startTime + ', ' + endTime + ', ' + fn + ')', 10);
}

createObjectById.prototype.slideBezier = function(startX, startY, endX, endY, c1x, c1y, c2x, c2y, acc, startTime, endTime, fn)
{
	var currentTime = new Date().getTime();
	if (currentTime < endTime)
	{
		var percent = (currentTime - startTime) / (endTime - startTime);
		var startPos = new createObjectById.coord(0, 0);
		var endPos = new createObjectById.coord(1, 1);	
		if (acc != 0) var c1 = new createObjectById.coord(0.5 - (acc / 2), 0.5 + (acc / 2)); else c1 = null;
		var pos = createObjectById.getBezier(percent, startPos, endPos, c1);
		var stage = pos.y;
		var c1 = new createObjectById.coord(startX, startY);
		var c2 = new createObjectById.coord(endX, endY);
		var c3 = new createObjectById.coord(c1x, c1y);
		var c4 = new createObjectById.coord(c2x, c2y);
		var pos = createObjectById.getBezier(stage, c1, c2, c3, c4);
		this.moveTo(pos.x, pos.y);
		this.onSlide(pos.x, pos.y);
	}
	else
	{
		clearInterval(this.slideTimer);
		this.moveTo(endX, endY);
		this.onSlide(endX, endY);
		if (fn) this.onSlideEnd(endX, endY);
	}
}

// resizeTo(); resizes a layer from it's current size to another size over a specified time period.
// w, h     = target (width, height) in pixels
// duration = how long in milliseconds to do the animation for
// acc      = coefficient of accelleration: 1 = accelerate; 0 = linear; -1 = deccelerate
//            (if acc > 1 then it will go backwards before heading towards destination)
// fn       = execute onResizeEnd() function when animation has finished? true or false.
createObjectById.prototype.resizeTo = function(w, h, duration, acc, fn)
{
	clearInterval(this.resizeTimer);
	if (!acc) acc = 0;
	if (!fn) fn = false;
	var startTime = new Date().getTime();
	var endTime = startTime + duration;
	this.resizeTimer = setInterval('createObjectById.registry[' + this.i + '].resize(' + this.w + ', ' + this.h + ', ' + w + ', ' + h + ', ' + acc + ', ' + startTime + ', ' + endTime + ', ' + fn + ')', 10);
}

createObjectById.prototype.onResize = new Function();
createObjectById.prototype.onResizeEnd = new Function();

createObjectById.prototype.resize = function(startW, startH, endW, endH, acc, startTime, endTime, fn)
{
	var currentTime = new Date().getTime();
	if (currentTime < endTime)
	{
		var percent = (currentTime - startTime) / (endTime - startTime);
		var c1 = new createObjectById.coord(1, 1);
		var c2 = new createObjectById.coord(0, 0);
		if (acc != 0) var c3 = new createObjectById.coord(0.5 + (acc / 2), 0.5 - (acc / 2)); else c3 = null;
		var pos = createObjectById.getBezier(percent, c1, c2, c3);
		var w = ((endW - startW) * pos.y) + startW;
		var h = ((endH - startH) * pos.y) + startH;
		this.setSize(w, h);
		this.onResize(w, h);
	}
	else
	{
		clearInterval(this.resizeTimer);
		this.setSize(endW, endH);
		this.onResize(endW, endH);
		if (fn) this.onResizeEnd(endW, endH);
	}
}

// fadeTo(); fades a layer from it's current opacity to another opacity over a specified time period.
// o        = target opacity to fade to (between 0 and 100)
// duration = how long in milliseconds to do the animation for
// acc      = coefficient of accelleration: 1 = accelerate; 0 = linear; -1 = deccelerate
//            (if acc > 1 then it will go backwards before heading towards destination)
// fn       = execute onResizeEnd() function when animation has finished? true or false.
createObjectById.prototype.fadeTo = function(o, duration, acc, fn)
{
	clearInterval(this.fadeTimer);
	if (!acc) acc = 0;
	if (!fn) fn = false;
	var startTime = new Date().getTime();
	var endTime = startTime + duration;
	this.fadeTimer = setInterval('createObjectById.registry[' + this.i + '].fade(' + this.o + ', ' + o + ', ' + acc + ', ' + startTime + ', ' + endTime + ', ' + fn + ')', 10);
}

createObjectById.prototype.onFade = new Function();
createObjectById.prototype.onFadeEnd = new Function();

createObjectById.prototype.fade = function(startO, endO, acc, startTime, endTime, fn)
{
	var currentTime = new Date().getTime();
	if (currentTime < endTime)
	{
		var percent = (currentTime - startTime) / (endTime - startTime);
		var c1 = new createObjectById.coord(1, 1);
		var c2 = new createObjectById.coord(0, 0);
		if (acc != 0) var c3 = new createObjectById.coord(0.5 + (acc / 2), 0.5 - (acc / 2)); else c3 = null;
		var pos = createObjectById.getBezier(percent, c1, c2, c3);
		var o = ((endO - startO) * pos.y) + startO;
		this.setOpacity(o);
		this.onFade(o);
	}
	else
	{
		clearInterval(this.fadeTimer);
		this.setOpacity(endO);
		this.onFade(endO);
		if (fn) this.onFadeEnd(endO);
	}
}


// bezier math (calculates a given position along a bezier curve specified by 2, 3 or 4 control points)
createObjectById.b1 = function(t) { return (t * t * t); }
createObjectById.b2 = function(t) { return (3 * t * t * (1 - t)); }
createObjectById.b3 = function(t) { return (3 * t * (1 - t) * (1 - t)); }
createObjectById.b4 = function(t) { return ((1 - t) * (1 - t) * (1 - t)); }

// coordinate constructor
createObjectById.coord = function(x, y)
{
	if (!x) var x = 0;
	if (!y) var y = 0;
	return { x: x, y: y }
}

// finds the coordinates of a point at a certain stage through a bezier curve
createObjectById.getBezier = function(percent, c1, c2, c3, c4)
{
	// if there aren't any extra control points plot a straight line, if there is only 1 make 2nd point same as 1st
	if (!c4 && !c3) var c4 = new createObjectById.coord(c1.x + 3 * (c2.x - c1.x) / 4, c1.y + 3 * (c2.y - c1.y) / 4);
	if (!c4) var c4 = c3;
	if (!c3) var c3 = new createObjectById.coord(c1.x + (c2.x - c1.x) / 4, c1.y + (c2.y - c1.y) / 4);
	var pos = new createObjectById.coord();
	pos.x = c1.x * createObjectById.b1(percent) + c3.x * createObjectById.b2(percent) + c4.x * createObjectById.b3(percent) + c2.x * createObjectById.b4(percent);
	pos.y = c1.y * createObjectById.b1(percent) + c3.y * createObjectById.b2(percent) + c4.y * createObjectById.b3(percent) + c2.y * createObjectById.b4(percent);
	return pos;
}

// --------
// Part III
// --------

// measure page width and height
function getViewport()
{
	if (document.documentElement && document.documentElement.scrollLeft) this.scrollX = document.documentElement.scrollLeft;
	else if (document.body && document.body.scrollLeft) this.scrollX = document.body.scrollLeft;
	else if (window.scrollX) this.scrollX = window.scrollX;
	else if (window.pageXOffset) this.scrollX = window.pageXOffset;
	else this.scrollX = 0;
	if (document.documentElement && document.documentElement.scrollTop) this.scrollY = document.documentElement.scrollTop;
	else if (document.body && document.body.scrollTop) this.scrollY = document.body.scrollTop;
	else if (window.scrollY) this.scrollY = window.scrollY;
	else if (window.pageYOffset) this.scrollY = window.pageYOffset;
	else this.scrollY = 0;
	if (document.documentElement && document.documentElement.clientWidth) this.width = document.documentElement.clientWidth;
	else if (document.body && document.body.clientWidth) this.width = document.body.clientWidth;
	else if (window.innerWidth) this.width = window.innerWidth;
	else this.width = 0;
	if (document.documentElement && document.documentElement.clientHeight) this.height = document.documentElement.clientHeight;
	else if (document.body && document.body.clientHeight) this.height = document.body.clientHeight;
	else if (window.innerHeight) this.height = window.innerHeight;
	else this.height = 0;
}

// event listeners
// object       = the object to attach the event handler to
// eventType    = the type of event to listen for
// functionName = the handler function to execute when the event is fired
// capture      = use event capture?
function addEvent(object, eventType, functionName, capture)
{
	if (object.addEventListener)
	{
		object.addEventListener(eventType, functionName, capture);
		return true;
	}
	else if (object.attachEvent)
	{
		var ae = object.attachEvent('on' + eventType, functionName);
		return ae;
	}
	else { object['on' + eventType] = functionName; }
}

function removeEvent(object, eventType, functionName, capture)
{
	if (object.removeEventListener)
	{
		object.removeEventListener(eventType, functionName, capture);
		return true;
	}
	else if (object.detachEvent)
	{
		var re = object.detachEvent('on' + eventType, functionName);
		return re;
	}
	else { object['on' + eventType] = null; }
}

function fixEvent(e, currentTarget)
{
	if (!e) e = event;
	if (!e.target) e.target = e.srcElement;
	if (!e.currentTarget) e.currentTarget = currentTarget;
	if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
	if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
	if (typeof e.clientX == 'undefined') e.clientX = e.pageX;
	if (typeof e.clientY == 'undefined') e.clientY = e.pageY;
	if (!e.stopPropagation) e.stopPropagation = function() { this.cancelBubble = true; }
	if (!e.preventDefault) e.preventDefault = function() { this.returnValue = false; }
	return e;
}

// browser check
function browserCheck()
{
	this.ua = navigator.userAgent.toLowerCase();
	this.dom = document.getElementById ? 1 : 0;
	this.op7 = (this.dom && this.ua.indexOf('opera 7') > -1 || this.ua.indexOf('opera/7') > -1) ? 1 : 0;
	this.ie5 = (this.dom && this.ua.indexOf('msie 5') > -1) ? 1 : 0;
	this.ie6 = (this.dom && this.ua.indexOf('msie 6') > -1) ? 1 : 0;
	this.moz = (this.dom && this.ua.indexOf('mozilla') > -1 && this.ua.indexOf('gecko') > -1) ? 1 : 0;
	return this;
}

// returns a random number between x and y (integers)
function rand(x, y) { return (Math.round(Math.random() * (y - x)) + x); }

// browser check variable
var bw = browserCheck();


// --------
// Part IIII
// --------

// tile
createObjectById.prototype.tile = function(num, startX, startY, width, spacingX, spacingY)
{
	var x = startX + (this.w + spacingX) * (num % width);
	var y = startY + (this.h + spacingY) * Math.floor(num / width);
	this.moveTo(x, y);
}

/*
align layer relative to the page (use your keyboard's numeric keypad as reference)
7-8-9 = 7 is top left, 8 is top center and 9 is top right
4-5-6 = 4 is left center, 5 is center of page and 6 is right center
1-2-3 = 1 is bottom left, 2 is bottom center and 3 is bottom right
*/
createObjectById.prototype.alignTo = function(num)
{
	var iface = new getViewport();
	if (num == 1) this.moveTo(0, iface.height - this.h);
	if (num == 2) this.moveTo(iface.width / 2 - this.w / 2, iface.height - this.h);
	if (num == 3) this.moveTo(iface.width - this.w, iface.height - this.h);
	if (num == 4) this.moveTo(0, iface.height / 2 - this.h / 2);
	if (num == 5) this.moveTo(iface.width / 2 - this.w / 2, iface.height / 2 - this.h / 2);
	if (num == 6) this.moveTo(iface.width - this.w, iface.height / 2 - this.h / 2);
	if (num == 7) this.moveTo(0, 0);
	if (num == 8) this.moveTo(iface.width / 2 - this.w / 2, 0);
	if (num == 9) this.moveTo(iface.width - this.w, 0);
}

