// ---------------------------------------------------------------------------------------------
//	Javascript methods used to address basic DOM compatability issues in event handling and element selection
//	Version	Date			Reason
//	0.1		Feb-2009		Initial version using Core.js from Simply Javascript by Kevin Yank and Cameron Adams
//	0.2		28-Feb-2009	Base.byId - a uniform getElementById to support W3C and older browsers, e.g. IE4, providing documents.all[element name]
//	0.3		29-Feb-2009	Base.byTag - a uniform getElementByTagName which also supports older browsers e.g. IE4 providing element.all.tags('tag name')
//	0.4		1-Mar-2009	Modify Base.addEventListener to support IE4 and IE5 Mac using DOM 0 event handlers
//	0.5		11-Nov-2009		Add global constant values for window.Node if not defined by the browser
//							Added Base.getEventTarget
//	0.6		20-Apr-2010		Use DOMCOntentLoaded in Base.Start 
//	0.7		11-Apr-2010		Add createStyleSheet and addStyleRule functions
//	0.8		26-Sep-2011		Add importNode, deleteStyleRule and countStyleRules functions
//	0.85	06-Oct-2011		Add getArgs method
// ---------------------------------------------------------------------------------------------

// Define some global constant values for node types
if (typeof document.Node == 'undefined') {
	var Node = {
		ELEMENT_NODE: 1,
		ATTRIBUTE_NODE: 2,
		TEXT_NODE: 3,
		CDATA_SECTION_NODE: 4,
		ENTITY_NODE: 6,
		PROCESSING_INSTRUCTION_NODE: 7,
		COMMENT_NODE: 8,
		DOCUMENT_NODE: 9,
		DOCUMENT_TYPE_NODE: 10,
		DOCUMENT_FRAGMENT_NODE: 11,
		NOTATION_NODE: 12
	};
}

var Base = {};

if (document.addEventListener) { // W3C DOM 2 Events model
// Development Note:  Safari 2.03 and earlier implementation of preventDefault is  non-functioning, which means that Safari < 2.0.4 will need to be excluded 
	Base.addEventListener = function (target, type, listener) {
		target.addEventListener(type, listener, false);
	}; // end Base.addEventListener method for W3C
	Base.removeEventListener = function (target, type, listener) {
		target.removeEventListener(type, listener, false);
	}; // end Base.removeEventListener method for W3C
	Base.preventDefault = function (event) {
		event.preventDefault();
	}; // end Base.preventDefault method for W3C
	Base.stopPropagation = function (event) {
		event.stopPropagation();
	}; // end Base.stopPropagation method for W3C

} else if (document.attachEvent) { // Internet Explorer Events model (IE 5+ Win)

	Base.addEventListener = function (target, type, listener) {
		// prevent adding the same listener twice, since DOM 2 Events ignores duplicates like this
		if (Base._findListener(target, type, listener) != -1) {
			return;
		}
		// listener2 calls listener as a method of target in one of two ways, depending on what this version of IE supports,
		// and passes it the global event object as an argument
		var listener2 = function () {
			var event = window.event;
			if (Function.prototype.call) { // IE 5.5 + supports call
				listener.call(target, event);
			} else { // IE 5.0 doesn't support call
				target._currentListener = listener;
				target._currentListener(event);
				target._currentListener = null;
			}
		};
		// following code allows complete removal of all listeners on unload - necessary due to notorious IE memory leak
		// add listener2 using IE's attachEvent method
		target.attachEvent('on' + type, listener2);
		// create an object describing this listener so we can clean it up later
		var listenerRecord = {
			target: target,
			type: type,
			listener: listener,
			listener2: listener2
		};
		// get a reference to the window object containing target
		var targetDocument = target.document || target;
		var targetWindow = targetDocument.parentWindow;
		// create a unique ID for this listener
		var listenerId = 'l' + Base._listenerCounter++;
		// store a record of this listener in the window object
		if (!targetWindow._allListeners) {
			targetWindow._allListeners = {};
		}
		targetWindow._allListeners[listenerId] = listenerRecord;
	    // store this listener's ID in target
	    if (!target._listeners) {
			target._listeners = [];
		}
	    target._listeners[target._listeners.length] = listenerId;
		// set up Base._removeAllListeners to clean up all listeners on unload
	    if (!targetWindow._unloadListenerAdded) {
			targetWindow._unloadListenerAdded = true;
			targetWindow.attachEvent('onunload', Base._removeAllListeners);
		}
	}; // end Base.addEventListener method for IE 5+ Win

	Base.removeEventListener = function (target, type, listener) {
		// find out if the listener was actually added to target
		var listenerIndex = Base._findListener(target, type, listener);
		if (listenerIndex == -1) {
			return;
		}
		// get a reference to the window object containing target
		var targetDocument = target.document || target;
		var targetWindow = targetDocument.parentWindow;
		// obtain the record of the listener from the window object
		var listenerId = target._listeners[listenerIndex];
		var listenerRecord = targetWindow._allListeners[listenerId];
		// remove the listener, and remove its ID from target
		target.detachEvent('on' + type, listenerRecord.listener2);
		target._listeners.splice(listenerIndex, 1);
		// remove the record of the listener from the window object
		delete targetWindow._allListeners[listenerId];
	}; // end Base.removeEventListener method for IE 5+ Win

	Base.preventDefault = function (event) {
		event.returnValue = false;
	}; // end Base.preventDefault method for IE 5+ Win

	Base.stopPropagation = function (event) {
		event.cancelBubble = true;
	}; // end Base.stopPropagation method for IE 5+ Win

	Base._findListener = function (target, type, listener) {
		// get the array of listener IDs added to target
		var listeners = target._listeners;
		if (!listeners) {
			return -1;
		}
		// get a reference to the window object containing target
		var targetDocument = target.document || target;
		var targetWindow = targetDocument.parentWindow;
		// searching backward (to speed up onunload processing), find the listener
		for (var i = listeners.length - 1; i >= 0; i--) {
			// get the listener's ID from target
			var listenerId = listeners[i];
			// get the record of the listener from the window object
			var listenerRecord = targetWindow._allListeners[listenerId];
			// compare type and listener with the retrieved record
			if (listenerRecord.type == type && listenerRecord.listener == listener) {
				return i;
			}
		}
		return -1;
	}; // end Base._findListener private method for IE 5+ Win

	Base._removeAllListeners = function () {
		var targetWindow = this;
		for (id in targetWindow._allListeners) {
			var listenerRecord = targetWindow._allListeners[id];
			listenerRecord.target.detachEvent('on' + listenerRecord.type, listenerRecord.listener2);
			delete targetWindow._allListeners[id];
		}
	}; // end Base._removeAllListeners private method for IE 5+ Win

	Base._listenerCounter = 0;

} else { // old browsers - particularly IE4 and IE5Mac - which do not support W3C or IE event listeners, but do support DOM 0 event model

	Base.addEventListener = function (target, type, listener) {
		type = 'on' + type;
		if (typeof target[type] == 'function') {
			var oldListener = target[type];
			target[type] = function () {
				oldListener();
				return listener();
			};
		} else {
			target[type] = listener;
		}
	};  // end Base.addEventListener method for IE 4 Win
	Base.preventDefault = function (event) { //  DOM 0 supports cancelling the  default action only by returning false from the event handler
	};
	// Development Notes:
	// 1. No remove or detach event method, so any removal algorithm inevitably will be complicated
	// 2. stopPropagation ?
}

Base.addClass = function (target, theClass) {
	if (!Base.hasClass(target, theClass)) {
		if (target.className === '') {
			target.className = theClass;
		} else {
			target.className += ' ' + theClass;
		}
	}
}; // end Base.addClass method
Base.getElementsByClass = function (theClass, node, tag) {
	var elements, count, pattern, i, j;
	var classElements = new Array();
	if (node == null) {
		node = document;
	}
	if (tag == null) {
		tag = '*';
	}
	elements = node.getElementsByTagName(tag);
	count = elements.length;
//	pattern = new RegExp('(^|\\\\s)' + theClass + '(\\\\s|$)');
	pattern = new RegExp('(^| )' + theClass + '( |$)');
	for (i = 0, j = 0; i < count; i++) {
		if (pattern.test(elements[i].className) ) {
			classElements[j] = elements[i];
			j++;
		}
	}
	return classElements;
}; // end Base.getElementsByClass method
Base.hasClass = function (target, theClass) {
	var pattern = new RegExp('(^| )' + theClass + '( |$)');
	if (pattern.test(target.className)) {
		return true;
	}
	return false;
}; // end Base.hasClass method
Base.removeClass = function (target, theClass) {
	var pattern = new RegExp('(^| )' + theClass + '( |$)');
	target.className = target.className.replace(pattern, '$1');
	target.className = target.className.replace(/ $/, '');
}; // end Base.removeClass method

Base.getComputedStyle = function (element, styleProperty) {
	var computedStyle = null;
	if (typeof element.currentStyle != 'undefined') {
		computedStyle = element.currentStyle;
	} else {
		computedStyle = document.defaultView.getComputedStyle(element, null);
	}
	return computedStyle[styleProperty];
}; // end Base.getComputedStyle method

Base.byId = function (element) {
	var elementId;
	if (typeof document.getElementById != 'undefined') {
		elementId = document.getElementById(element);
	} else if (typeof document.all != 'undefined') {
		elementId = document.all[element];
	}
	return elementId;
}; // end Base.byId method
Base.byTag = function (elementId, tag) {
// Development Note: this function still needs to be tested
	var element = Base.byId(elementId);
	if (typeof element.getElementByTagName != 'undefined') {
		return element.getElementByTagName(tag);
	} else if (typeof element.all.tags != 'undefined') {
		return element.all.tags('a');
	}
	return elementId;
}; // end Base.byTag method
Base.createStyleSheet = function(media) {
	var s = null;
	if (typeof document.styleSheets !== 'undefined') {
		s = document.createElement('style');
		s.setAttribute('type', 'text/css');
		if (typeof media === 'undefined') {
			s.setAttribute('media', 'all');
		} else {
			s.setAttribute('media', media);
		}
		s = document.getElementsByTagName('head')[0].appendChild(s);
		if (document.styleSheets.length > 0) {
			s = document.styleSheets[document.styleSheets.length - 1];
		}
	}
	return s;
}; // end Base.createStyleSheet method
Base.addStyleRule = function(sheet, selector, properties, index) {
	if (typeof sheet.addRule !== 'undefined') { // IE
		sheet.addRule(selector, properties, index);
	} else if (typeof sheet.insertRule !== 'undefined') { //W3C
		if (typeof index === 'undefined') {
			index = sheet.cssRules.length;
		}
		sheet.insertRule(selector + ' {' + properties + ' }', index);
	}
}; // end Base.addStyleRule method
Base.deleteStyleRule = function(sheet, index) {
	if (typeof sheet.removeRule !== 'undefined') { // IE
		sheet.removeRule(index);
	} else if (typeof sheet.deleteRule !== 'undefined') { //W3C
		sheet.deleteRule(index);
	}
}; // end Base.deleteStyleRule method
Base.countStyleRules = function(sheet) {
	if (typeof sheet.rules !== 'undefined') { // IE
		return sheet.rules.length;
	} else if (typeof sheet.cssRules !== 'undefined') { // W3C
		return sheet.cssRules.length;
	} 
}; // end Base.countStyleRules method
Base.ordinalSuffix = function(n) {
	var o = 'th';
	if (n % 10 == 1 && n % 100 != 11) {
		o = 'st';
	} else if (n % 10 == 2 && n % 100 != 12) {
		o = 'nd';
	} else if (n % 10 == 3 && n % 100 != 13) {
		o = 'rd';
	}
	return o;
}; // end Base.ordinalSuffix method
Base.padWithZeroes = function(number, length) {
	var s = '' + number;
    while (s.length < length) {
        s = '0' + s;
    }
    return s;
}; // end Base.padWithZeroes method
Base.stringTrim = function (str) {
// enrich javascript's String object with a trim method which removes white space from the start and end of a string
	return str.replace(/^\s+|\s+$/g, '');
}; // end String.prototype.trim method
Base.stringReverse = function(str) { 
// enrich javascript's String object with a method to reverse the string; first it converts the string to an array, then uses the array reverse method and finally returns the reversed string
	return str.split('').reverse().join('');
}; // end Base.stringTrim
Base.importNode = function(currentNode, allChildren) {
	switch (currentNode.nodeType) {
		case Node.ELEMENT_NODE:
			var newNode = document.createElement(currentNode.nodeName);
			if (currentNode.attributes && currentNode.attributes.length > 0) {
				var len = currentNode.attributes.length;
				for (var i = 0; i < len; i++) {
					newNode.setAttribute(currentNode.attributes[i].nodeName, currentNode.getAttribute(currentNode.attributes[i].nodeName));
				}
			}
			if (allChildren && currentNode.childNodes && currentNode.childNodes.length > 0) {
				var len = currentNode.childNodes.length;
				for (var i = 0; i < len; i++) {
					newNode.appendChild(Base.importNode(currentNode.childNodes[i], allChildren));
				}
			}
			return newNode;
			break;
		case Node.TEXT_NODE:
		case Node.CDATA_SECTION_NODE:
		case Node.COMMENT_NODE:
			return document.createTextNode(currentNode.nodeValue);
			break;
	}		
}; // end Base.importNode
Base.getArgs = function() {
	var args, query, len, i, pos, argName;  
	if (location.search.length > 0) {
		args = new Object;
		query = location.search.substring(1);
		query = query.split(", ");
		len = query.length;
		for (var i = 0; i < len; i++) {
			pos = query[i].indexOf('=');	// search for 'name = value'
			if (pos >= 0) {					// process if found
				argName = query[i].substring(0, pos);
				args[argName] = decodeURIComponent(query[i].substring(pos+1));
			}
		}
	}
	return args;
}; // end Base.getArgs

Base.start = function (runnable) {
	var initOnce = function() {
		if (arguments.callee.done) return;
		arguments.callee.done = true;
		runnable.init();
	};
	Base.addEventListener(document, 'DOMContentLoaded', initOnce);
	Base.addEventListener(window, 'load', initOnce);
}; // end Base.start method
