/*====================================================================================
	
	Alchemy
	.........................

	A thin javascript library that allows separation of behavior from structure, plus
	some other goodies.
	
	Licensed under the GNU LGPL.

	Marti Congost, 2005
	marticongost@gmail.com

====================================================================================*/

var alchemy = {};

/* Element selection
------------------------------------------------------------------------------------------------------*/

alchemy.createSelector = function (value) {
		
	// Create a selector from a string
	if (value.length) {

		// Universal selector
		if (value == "*") {
			var selector = function (element) { return true; }
		}
		// Class selector
		else if (value.charAt(0) == ".") {
			var cls = value.substr(1);
			var selector = function (element) { return element.className.hasWord(cls); }
		}
		// Id selector
		else if (value.charAt(0) == "#") {
			var id = value.substr(1);
			var selector = function (element) { return element.id == id; }
		}
		// Attribute selector
		else if (value.charAt(0) == "@") {
			var parts = value.substr(1).split("=");
			if (parts.length == 1) {
				var attrib = parts[0];
				var selector = function (element) { return element.getAttribute(attrib); }
			}
			else {
				var attrib = parts[0];
				var value = parts[1];
				var selector = function (element) { return element.getAttribute(attrib) == value; }
			}
		}
		// Tag selector
		else {
			value = value.toUpperCase();
			var selector = function (element) { return element.tagName == value; }
		}
	}

	// Otherwise, assume a custom selector (a function)
	else selector = value;

	return selector;
}

alchemy.selectElement = function (selector, parent, shallow) {
	
	var matches = alchemy.createSelector(selector);

	function scan(element) {
		
		if (element.nodeType == 1) {
		
			if (matches(element)) {
				return element;
			}
			else if (!shallow) {
				for (var i = 0; i < element.childNodes.length; i++) {
					var selectedChild = scan(element.childNodes[i]);
					if (selectedChild) return selectedChild;
				}
			}
		}

		return null;
	}

	return scan(parent || document.body);
}

alchemy.selectElements = function (selector, parent, shallow) {

	var elements = [];
	var matches = alchemy.createSelector(selector);

	function scan(element) {
		if (element.nodeType == 1) {
			if (matches(element)) elements.push(element);
			if (!shallow) for (var i = 0; i < element.childNodes.length; i++) scan(element.childNodes[i]);
		}
	}

	scan(parent || document.body);
	return elements;
}

/* Bindings
------------------------------------------------------------------------------------------------------*/

alchemy.bindings = [];

alchemy.bind = function (selection, behavior) {

	var binding = {};
	
	binding.applies = alchemy.createSelector(selection);
	binding.apply = function (element) { behavior(element); }
	
	alchemy.bindings.push(binding);
}

alchemy.applyBindings = function (element) {
	
	if (element.nodeType == 1) {
		for (var i = 0; i < alchemy.bindings.length; i++) {
			var binding = alchemy.bindings[i];
			if (binding.applies(element)) binding.apply(element);
		}
		for (var i = 0; i < element.childNodes.length; i++) alchemy.applyBindings(element.childNodes[i]);
		alchemy.notify(element, "ready");
	}
}


/* Signal & Event handling
------------------------------------------------------------------------------------*/
alchemy.addHandler = function (element, event, handler) {
	var hName = "_alchemy_" + event;
	var id;
	if (!element[hName]) id = element[hName] = 1;
	else id = ++element[hName];
	element["_alchemy_" + event + id] = handler;
}

alchemy.notify = function (element, event, data) {	
	var count = element["_alchemy_" + event];
	if (count) {
		for (var i = 1; i <= count; i++) {
			element["_alchemy_" + event + i](data);
		}
	}
}

alchemy.stopEvent = function (e) {
	if (!e) e = window.event;
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
}

alchemy.concealEvent = function (e) {
	if (!e) e = window.event;
	e.returnValue = false;
	if (e.preventDefault) e.preventDefault();
	return false;
}

/* DOM utilities
------------------------------------------------------------------------------------------------------*/
alchemy.clear = function (element) {
	while (element.firstChild) element.removeChild(element.firstChild);
}

alchemy.clearTextNodes = function (element) {
	var nodes = element.childNodes;
	for (var i = nodes.length - 1; i >= 0; i--) {		
		if (nodes[i].nodeType == 3) element.removeChild(nodes[i]);
	}
}

alchemy.swapNodes = function (a, b) {

	if (a == b) return;

	var aParent = a.parentNode;
	var bParent = b.parentNode;
	var aNext = a.nextSibling;
	var bNext = b.nextSibling;

	aNext ? aParent.insertBefore(b, aNext) : aParent.appendChild(b);
	bNext ? bParent.insertBefore(a, bNext) : bParent.appendChild(a);
}

alchemy.insertAfter = function (point, node) {
	var parent = point.parentNode;
	point.nextSibling ? parent.insertBefore(node, point.nextSibling) : parent.appendChild(node);
}

alchemy.nodeDescendsFrom = function (node, ancestor) {
	
	while (node = node.parentNode) {
		if (node == ancestor) return true;
	}

	return false;
}

alchemy.getAbsolutePosition = function (element) {
	
	var pos = {};
	pos.x = 0;
	pos.y = 0;

	while (element) {
		pos.x += element.offsetLeft;
		pos.y += element.offsetTop;
		element = element.offsetParent;
	}

	return pos;
}

alchemy.getAbsoluteLeft = function (element) {
	var left = 0;
	while (element) {
		left += element.offsetLeft;
		element = element.offsetParent;
	}
	return left;
}

alchemy.getAbsoluteTop = function (element) {
	var top = 0;
	while (element) {
		top += element.offsetTop;
		element = element.offsetParent;
	}
	return top;
}

alchemy.getMousePos = function (e) {
	var pos = {};
	pos.x = (e.pageX != undefined ? e.pageX : e.clientX + document.body.scrollLeft);
	pos.y = (e.pageY != undefined ? e.pageY : e.clientY + document.body.scrollTop);
	return pos;
}

alchemy.getRelMousePos = function (element, e) {
	var pos = alchemy.getMousePos(e);
	pos.x -= alchemy.getAbsoluteLeft(element);
	pos.y -= alchemy.getAbsoluteTop(element);
	return pos;	
}

alchemy.addClass = function (element, cls) {
	if (!element.className.hasWord(cls)) element.className += " " + cls;
}

alchemy.removeClass = function (element, cls) {
	if (element.className.hasWord(cls)) {
		var classes = element.className.split(" ");
		var className = "";
		for (var i = 0; i < classes.length; i++) {
			if (classes[i] != cls) {
				if (className.length > 0) className += " ";
				className += classes[i];
			}
		}
		element.className = className;
	}
}

alchemy.setEnabled = function (element, value) {
	
	if (value) {
		if (element.enabledOnClick) element.onclick = element.enabledOnClick;
		alchemy.removeClass(element, "disabled");		
	}
	else {
		if (!element.enabledOnClick) element.enabledOnClick = element.onclick;
		element.onclick = function () { return false; }
		alchemy.addClass(element, "disabled");
	}	
}

alchemy.isDisplayed = function (element) {
	return element.style.display != "none";
}

alchemy.setDisplayed = function (element, value) {
	//if (value != (element.offsetHeight > 0)) element.style.display = value ? "" : "none";
	element.style.display = value ? "" : "none";
}

// Enable, disable or hide an element
alchemy.setState = function (element, state) {
	alchemy.setEnabled(element, state != -1);
	alchemy.setDisplayed(element, state != 0);
}

// *** Use only when there's absolutely no other viable option ***
alchemy.checkBrowser = function (check) {
	return navigator.userAgent.toLowerCase().contains(check.toLowerCase());
}

alchemy.getNext = function (element) {
	while (element && (element = element.nextSibling) && element.nodeType != 1);
	return element;
}

alchemy.getPrevious = function (element) {
	while (element && (element = element.previousSibling) && element.nodeType != 1);
	return element;
}

/* Array utilities
--------------------------------------------------------------------*/
Array.prototype.remove = function (item) {
		
	for (var i = 0; i < this.length; i++) {
		if (this[i] == item) {
			for (; i < this.length - 1; i++) {
				this[i] = this[i + 1];
			}
			this.length--;
			return true;
		}
	}

	return false;
}

Array.prototype.indexOf = function (item) {
	for (var i = 0; i < this.length; i++) if (this[i] == item) return i;
	return -1;
}

Array.prototype.clone = function () {
	var clone = new Array();
	for (var i = 0; i < this.length; i++) clone.push(this[i]);
	return clone;
}

Array.prototype.slice = function (start, end) {	
	var slice = [];
	for (var i = start; i <= end; i++) slice.push(this[i]);
	return slice;
}

// IE 5 fix
if (!Array.prototype.push) {
	Array.prototype.push = function (item) {
		this[this.length - 1] = item;
	}
}

/* String utilities
------------------------------------------------------------------------------------------------------*/

String.prototype.hasWord = function (word) {
	var pos = this.indexOf(word);
	var right = pos + word.length - 1;
	
	return	(pos != -1
		&&	(pos == 0 || this.charAt(pos - 1) == ' ')
		&&	(right == this.length - 1 || this.charAt(right + 1) == ' '));
}

String.prototype.contains = function (substring) {
	return this.indexOf(substring) != -1;
}

String.prototype.capitalize = function () {
	return this.length == 0 ? this : this.charAt(0).toUpperCase() + this.substr(1);
}

String.prototype.startsWith = function (substring) {
	return this.indexOf(substring) == 0;
}

String.prototype.strip = function () {
	
	var i, j;
	for (i = 0; i < this.length && (this.charAt(i) == " " || this.charAt(i) == "\t"); i++);
	for (j = this.length - 1; j >= i && (this.charAt(j) == " " || this.charAt(j) == "\t"); j--);

	return this.substring(i, j + 1);
}

String.prototype.leftSplit = function (separator) {
	var parts = [];
	var pos = this.indexOf(separator);
	return (pos == -1) ? [null, this] : [this.substr(0, pos), this.substr(pos + 1)];
}

String.prototype.rightSplit = function (separator) {
	var parts = [];
	var pos = this.lastIndexOf(separator);
	return (pos == -1) ? [this, null] : [this.substr(0, pos), this.substr(pos + 1)];
}

String.prototype.remove = function (removedChar) {
	var str = "";
	for (var i = 0; i < this.length; i++) {
		var c = this.charAt(i);
		if (c != removedChar) str += c;
	}
	return str;
}

/* Timers
--------------------------------------------------------------------*/
alchemy.timers = [];

alchemy.setTimeout = function (action, timeout) {
	var id = alchemy.timers.length;
	alchemy.timers.push(action);
	setTimeout("alchemy.timers[" + id + "]()", timeout);
}

alchemy.setInterval = function (action, interval) {
	var id = alchemy.timers.length;
	alchemy.timers.push(action);
	setInterval("alchemy.timers[" + id + "]()", interval);
}

/* Initialisation
------------------------------------------------------------------------------------*/
window.customOnLoad = window.onload;
window.customOnUnload = window.onunload;

window.onload = function (e) {
	alchemy.notify(window, "loaded");
	alchemy.applyBindings(document.body);
	if (window.customOnLoad) window.customOnLoad(e);
	alchemy.notify(window, "ready");
}

window.onunload = function (e) {
	alchemy.notify(window, "unloaded");
	if (window.customOnUnload) window.customOnUnload(e);
}
