// PRINT DIALOG
// pops up print dialog for browsers that support if a var
// doPrintDialog is initialised on the page and set to true
function popPrintDialog() {
	if ( typeof doPrintDialog != 'undefined' ) {
		if (  doPrintDialog ) {
			if (typeof window.print != 'undefined') {
				window.print();
			}
		}
	}
}
addLoadHandler(function(){popPrintDialog(); return true;})

// ADD HOVER AND CLICK --------------------------------------------
// adds a hover + click behaviours to arbitrary elements; only
function Addhover(id,tag) {
	var container,targets,numTargets;
	if(	(typeof document.getElementById != 'undefined') &&
		(typeof document.getElementsByTagName != 'undefined') &&
		(container = document.getElementById(id)) &&
		(targets = container.getElementsByTagName(tag)) ) {

//		loop through target elements
		numTargets = targets.length;
		for (var i = 0; i < numTargets; i++) {
//			add function onhover
			targets[i].onmouseover =	function() {
											this.className =	this.className +
											((this.className.length > 0)?' ':'') +
											((this.className.indexOf('current') == -1)?'hover':'currHover');
											return true;
										}

			targets[i].onmouseout =	function() {
										var hoverReplace =	new RegExp(
																((this.className.indexOf(' ') != -1)?' ':'') +
																((this.className.indexOf('current') != -1)?'currHover':'hover')
															);
										this.className = this.className.replace(hoverReplace,'');
										return true;
									}

//			add function onclick
			targets[i].onclick =	function() {
										document.location.href = this.getElementsByTagName('a')[0].href;
										return true;
									}
		}
//		end loop
		addUnloadHandler(new Function ('targets = document.getElementById("' + id + '").getElementsByTagName("' + tag + '"); var len = targets.length; for (var i = 0; i < targets.len; i++) targets[i].onmouseover = targets[i].onmouseout = targets[i].onclick = null; return true;'));
	}
}
addLoadHandler(function() {Addhover('nav','td'); return true;});



// ADD IMAGE SWAP -------------------------------------------------
// adds a JS mouse over swap to all image inputs and image links
// in the document with '_off' or '_on' as the last bit of the
// filename before the extension
function AddSwap() {
//	declare variables
	var	inputs,
		hyperlinks,
		numElements,
		childImages,
		numImages,
		i,
		j;
//	test for DOM Methods and get all input elements
	if(	(typeof document.getElementById != 'undefined') &&
		(typeof document.getElementsByTagName != 'undefined') &&
		(inputs = document.getElementsByTagName('input')) &&
		(hyperlinks = document.getElementsByTagName('a'))
		) {
//		loop through the inputs
		numElements = inputs.length;
		for (i = 0; i < numElements; i++) {
//			find the ones that are of type 'image'
			if (inputs[i].type.toLowerCase() == 'image') {
//				pass image inputs to the function that adds the swap
				this.makeSwap(inputs[i]);
			}
		}
//		loop through the anchors
		numElements = hyperlinks.length;
		for (i = 0; i < numElements; i++) {
//			find the ones that have image children
			childImages = hyperlinks[i].getElementsByTagName('img');
			numImages = childImages.length;
			if (numImages > 0) {
//				pass the images to the function that adds the swap
				for (j = 0; j < numImages; j++) {
					this.makeSwap(childImages[j]);
				}
			}
		}
		addUnloadHandler(this.cleanUp);
		return true;
	}
	return false;
}
AddSwap.prototype.makeSwap = function (el) {
	if (((typeof el.src == 'string') && (el.src.indexOf('_off.') != -1)) || ((typeof el.src == 'string') && (el.src.indexOf('-off.') != -1)) ) {
//		if the image name contains 'off' set the src of off state img to the current
//		src and swap the 'off' with 'on' to get the src for the on state
		el.offImg = new Image();
		el.offImg.src = el.src;
		el.onImg = new Image();
		if (el.src.indexOf('_off.') != -1) {
			el.onImg.src = el.src.replace(/_off\./,'_on.');
		} else if (el.src.indexOf('-off.') != -1) {
			el.onImg.src = el.src.replace(/-off\./,'-on.');
		}
	} else if ( ((typeof el.src == 'string') && (el.src.indexOf('_on.') != -1)) || ((typeof el.src == 'string') && (el.src.indexOf('-on.') != -1)) ) {
//		otherwise, if the image name contains 'on' set the src of on state img to the
//		current src and swap the 'on' with 'ooff' to get the src for the on state
		el.offImg = new Image();
		el.offImg.src = el.src;
		el.onImg = new Image();
		if (el.src.indexOf('_on.') != -1) {
			el.onImg.src = el.src.replace(/_off\./,'_off.');
		} else if (el.src.indexOf('-on.') != -1) {
			el.onImg.src = el.src.replace(/-off\./,'-off.');
		}
//	if neither of the state slugs is present, return false
	} else return false;
//	add mouseover and mouseout functions
	el.onmouseover = function() { this.src = this.onImg.src; return true; }
	el.onmouseout = function() { this.src = this.offImg.src; return true; }
	return true
}
AddSwap.prototype.cleanUp = function () {
	var	inputs = document.getElementsByTagName('input'),
		hyperlinks = document.getElementsByTagName('a'),
		numElements = inputs.length,
		childImages,
		numImages,
		i,
		j;
//	loop through the inputs
	for (i = 0; i < numElements; i++) {
//		find the ones that are of type 'image'
		if (inputs[i].type.toLowerCase() == 'image') {
//			remove handlers & expandos
			inputs[i].offImg = null;
			inputs[i].onImg = null;
			inputs[i].onmouseover = null;
			inputs[i].onmouseout = null;
		}
	}
//	loop through the anchors
	numElements = hyperlinks.length;
	for (i = 0; i < numElements; i++) {
//		find the ones that have image children
		childImages = hyperlinks[i].getElementsByTagName('img');
		numImages = childImages.length;
		if (numImages > 0) {
//			pass the images to the function that adds the swap
			for (j = 0; j < numImages; j++) {
				childImages[j].offImg = null;
				childImages[j].onImg = null;
				childImages[j].onmouseover = null;
				childImages[j].onmouseout = null;
			}
		}
	}
	return true;
}
addLoadHandler(function() { return new AddSwap();});

/*	ADD POPUPS -----------------------------------------------------
	loops through links checking the TARGET attribute for values
	of the form _D(D)(D)(D)xD(D)(D)(D) where 'D' is a digit and
	(D) is an optional digit; when it finds such a link, it adds
	a click handler to create a pop-up window
*/
function addPopUps() { if (document.getElementsByTagName) {
	var	links = document.getElementsByTagName('A'),
		num = links.length,
		i,
		cleanUp = [],
		target;
	for (i = 0; i < num; i++) {
		if (typeof links[i].target != 'undefined' && (dimensions = links[i].target.match(/_(\d+)x(\d+)\b/))) {
			links[i].onclick = new Function('var win = window.open("' + links[i].href + '","BIApopup","width=' + dimensions[2] + ',height=' + dimensions[1] + ',location,menubar,scrollbars,toolbar,status"); win.focus(); return false;');
			cleanUp[cleanUp.length] = links[i];
		}
	}
	if (cleanUp.length > 0) {
		addUnloadHandler(function() { var len = cleanUp.length; for ( var j = 0; j < len; j++) { cleanUp[j].onclick = null; cleanUp[j] = null; } return true; });
	}
} return false; }
addLoadHandler(function() { return addPopUps();});


// UTILITIES ======================================================


function attachEventListener(target, eventType, functionRef, capture) {
	if (typeof target.addEventListener != "undefined") {
		target.addEventListener(eventType, functionRef, capture);
	} else if (typeof target.attachEvent != "undefined") {
		target.attachEvent("on" + eventType, functionRef);
	} else {
		eventType = "on" + eventType;
		if (typeof target[eventType] == "function") {
			var oldListener = target[eventType];
			target[eventType] = function() {
				oldListener();
				return functionRef();
			}
		} else {
			target[eventType] = functionRef;
		}
	}
	return true;
}

function detachEventListener(target, eventType, functionRef, capture) {
	if (typeof target.removeEventListener != "undefined") {
		target.removeEventListener(eventType, functionRef, capture);
	} else if (typeof target.detachEvent != "undefined") {
		target.detachEvent("on" + eventType, functionRef);
	} else {
		target["on" + eventType] = null;
	}
	return true;
}

function getEventTarget(event) {
	var targetElement = null;
	if (typeof event.target != "undefined") {
		targetElement = event.target;
	} else {
		targetElement = event.srcElement;
	}
	while (targetElement.nodeType == 3 && targetElement.parentNode != null) {
		targetElement = targetElement.parentNode;
	}
	return targetElement;
}

function stopDefaultAction(event) {
	event.returnValue = false;
	if (typeof event.preventDefault != "undefined") {
		event.preventDefault();
	}
	return true;
}

/*	ADD LOAD HANDLER -----------------------------------------------
	allows adding more than one function to the onload event
	based on Simon Willison's discussion of closures at:
	http://simon.incutio.com/archive/2004/05/26/addLoadEvent
*/
function addLoadHandler(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
	window.onload = func;
  } else {
	window.onload = function() {
	  oldonload();
	  func();
	}
  }
}
/*	ADD ONUNLOAD HANDLER -------------------------------------------
	allows adding more than one function to the window's onlunload
	event based on Simon Willison's closure demo
*/
function addUnloadHandler(func) {
	var oldonunload = window.onunload;
	if (typeof window.onunload != 'function') {
		window.onunload = function() {
			func();
			window.onload = null;
			window.onunload = null;
		}
	} else {
		window.onunload = function() {
			func();
			oldonunload();
		}
	}
}

/*	ADD HANDLER ----------------------------------------------------
	http://simon.incutio.com/archive/2004/05/26/addLoadEvent
*/
function addHandler(obj,evt,func) {
	var oldhandler = obj[evt];
	if (typeof obj[evt] != 'function') {
		obj[evt] = func;
	} else {
		obj[evt] = function() {
			oldhandler();
			func();
		}
	}
}

/*
	IDENTIFY BROWSER

	Mainly identifies the browser based on 'known to be (un)supported methods' as opposed to user agent sniffing.

	Extended slightly to accommodate differentiating between Safari 1.2 and Safari 2.

	Based on the solution described in the book
	The Javascript Anthologyv by Cameron Adams and James Edwards

	RETURNS browser (string). Possible values:
					kde
					opera5
					opera7
					ie5
					ie5mac
					ie5.5
					ie6
					ie7
					mozilla
					safari1
					safari1.2
					safari2

			or false (boolean)
*/
function identifyBrowser() {
	var agent = navigator.userAgent.toLowerCase();

	if (typeof navigator.vendor != "undefined" && navigator.vendor == "KDE" && typeof window.sidebar != "undefined") {
		return "kde";
	} else if (typeof window.opera != "undefined") {
		var version = parseFloat(agent.replace(/.*opera[\/ ]([^ $]+).*/, "$1"));
		if (version >= 7) {
			return "opera7";
		} else if (version >= 5) {
			return "opera5";
		}
		return false;
	} else if (typeof document.all != "undefined") {
		if (typeof document.getElementById != "undefined") {
			var browser = agent.replace(/.*ms(ie[\/ ][^ $]+).*/, "$1").replace(/ /, "");
			if (typeof document.uniqueID != "undefined") {
				if (browser.indexOf("5.5") != -1) {
					return browser.replace(/(.*5\.5).*/, "$1");
				} else {
					return browser.replace(/(.*)\..*/, "$1");
				}
			} else {
				return "ie5mac";
			}
		}
		return false;
	} else if (typeof document.getElementById != "undefined") {
		if (navigator.vendor.indexOf("Apple Computer, Inc.") != -1) {
			if (typeof window.XMLHttpRequest != "undefined") {
				// we know it's 1.2 or higher.
				// Now detect on user agent string
				var agentWebkit = agent.lastIndexOf('safari');
				// + 7 to account for 'safari/'
				var webKitMajorVersion = agent.substr(agentWebkit + 7 ,3);
				// as advised by apple: http://developer.apple.com/internet/safari/faq.html#anchor2
				// use applewebkit string to detect webkitversion.
				// Safari started shipping with Tiger, it's earliest webkit version is 412
				if ( webKitMajorVersion >= 412 ) {
					return "safari2";
				} else {
					return "safari1.2";
				}
			}
			return "safari1";
		} else if (agent.indexOf("gecko") != -1) {
			return "mozilla";
		}
	}
	 return false;
}

/*	GET DAYS IN MONTH ----------------------------------------------
	gets the number of days in a pariticular month of a particular
	year; returns false if month supplied is invalid

	PARAMETERS (optional -- operates on the instantiated date object if not supplied)
	month	= integer/numeric string; numeric month from 0 to 11
	year	= integer/numeric string; year
*/
Date.prototype.getDaysInMonth = function (month, year) {
//	make sure we've got a date & year to work with -- use the instantiated date object if none supplied
	month = (typeof month != 'undefined')?month:this.getMonth();
	if (typeof month == 'string') {
		month = month.replace(/^0+/,'')
		month = (month.length == 0)?0:parseInt(month);
	}
	year = (typeof year != 'undefined')?year:this.getFullYear();
	if (typeof year == 'string') {
		year = year.replace(/^0+/,'')
		year = (year.length == 0)?0:parseInt(year);
	}
	switch(month) {
		case 0 :
			return 31;
		case 1 :
//			check for leap year: every 4 years, except even centuries OTHER than those divisible by 400
			if (year % 4 != 0) return 28;
			else if (year % 400 == 0) return 29;
			else if (year % 100 == 0) return 28;
			else return 29;
		case 2 :
			return 31;
		case 3 :
			return 30;
		case 4 :
			return 31;
		case 5 :
			return 30;
		case 6 :
			return 31;
		case 7 :
			return 31;
		case 8 :
			return 30;
		case 9 :
			return 31;
		case 10 :
			return 30;
		case 11 :
			return 31;
	}
	return false;
}

/*	GET MONTH NAME -------------------------------------------------
	gets the number of days in a pariticular month of a particular
	year; returns false if month supplied is invalid

	PARAMETERS (optional -- operates on the instantiated date object if not supplied)
	month	= integer/numeric string; numeric month from 0 to 11
*/
Date.prototype.getMonthName = function (month) {
	month = (typeof month != 'undefined')?month:this.getMonth();
	if (typeof month == 'string') {
		month = month.replace(/^0+/,'')
		month = (month.length == 0)?0:parseInt(month);
	}
	switch(month) {
		case 0 :
			return 'January';
		case 1 :
			return 'February';
		case 2 :
			return 'March';
		case 3 :
			return 'April';
		case 4 :
			return 'May';
		case 5 :
			return 'June';
		case 6 :
			return 'July';
		case 7 :
			return 'August';
		case 8 :
			return 'September';
		case 9 :
			return 'October';
		case 10 :
			return 'November'
		case 11 :
			return 'December';
	}
	return false;
}

/*	GET MONTH ABBREVIATION -----------------------------------------
	gets the month name and truncates it to 3 characters to form the
	abbreviation; returns false if month supplied is invalid

	PARAMETERS (optional -- operates on the instantiated date object if not supplied)
	month	= integer/numeric string; numeric month from 0 to 11
*/
Date.prototype.getMonthAbbr = function (month) {
	var monthName = this.getMonthName((typeof month == 'undefined')?month:this.getMonth());
	return (monthName)?monthName.substr(0,3):false;
}

/*	GET DAY NAME ---------------------------------------------------
	gets the day name using a numeric day; returns false if day
	supplied is invalid

	PARAMETERS (optional -- operates on the instantiated date object if not supplied)
	day	= integer/numeric string; numeric day from 0 to 6
*/
Date.prototype.getDayName = function (day) {
	day = (typeof day != 'undefined')?day:this.getDay();
	if (typeof day == 'string') {
		day = day.replace(/^0+/,'')
		day = (day.length == 0)?0:parseInt(day);
	}
	switch(day) {
		case 0 :
			return 'Sunday';
		case 1 :
			return 'Monday';
		case 2 :
			return 'Tuesday';
		case 3 :
			return 'Wednesday';
		case 4 :
			return 'Thursday';
		case 5 :
			return 'Friday';
		case 6 :
			return 'Saturday';
	}
	return false;
}

/*	GET LEFT -------------------------------------------------------
	utility script to get the left edge of an arbitrary element;
	very loosely based on PPK's script at
	http://www.quirksmode.org/js/findpos.html, but with heavy mods
	to account for absolutely positioned elements and borders &
	margins on the BODY

	PARAMETERS
	- element = node object; the element whose left edge is to be
	  found
*/
function getLeft(element) {
	var left = 0, absoluteAncestor = false, computedStyle = (document.defaultView && document.defaultView.getComputedStyle), currentStyle = (element.currentStyle);
//	so long as the element has an positioning context...
	while (element.offsetParent) {
//		add the left offset of the element
		left += element.offsetLeft
//		set the element to the element which provided the positioning context
		element = element.offsetParent;
//		if the element hasLayout (IE-only property)
		if (currentStyle && element.currentStyle.hasLayout && element.nodeName.toLowerCase() != 'html') {
//			add the width of the left border
			left += element.clientLeft;
//			and if it's absolutely positioned, flag that we've got an absolutely positioned ancestor
			if (element.currentStyle['position'] == 'absolute') absoluteAncestor = true;
//		otherwise, if it's a browser that supports computedStyle & the element is absolutely positioned
		} else if (computedStyle && document.defaultView.getComputedStyle(element, "").getPropertyValue('position') == 'absolute') {
//			add the left border of the element
			left += parseInt(document.defaultView.getComputedStyle(element, "").getPropertyValue('border-left-width'));
//			and flag that we've got an absolutely positioned ancestor
			absoluteAncestor = true;
		}
	}
//	if the browser supports currentStyle (i.e. is IE) and we found an absolutely positioned ancestor, add any left margin on the BODY
	if (!absoluteAncestor && currentStyle) return left += document.getElementsByTagName('BODY')[0].offsetLeft;
//	otherwise, if we found an absolutely positioned ancestor and the browser supports computed style add any left border from the BODY
	else if (!absoluteAncestor && computedStyle) return left += parseInt(document.defaultView.getComputedStyle(document.getElementsByTagName('BODY')[0], "").getPropertyValue('border-left-width'));
}

/*	GET TOP --------------------------------------------------------
	utility script to get the top edge of an arbitrary element;
	very loosely based on PPK's script at
	http://www.quirksmode.org/js/findpos.html, but with heavy mods
	to account for absolutely positioned elements and borders &
	margins on the BODY

	PARAMETERS
	- element = node object; the element whose top edge is to be
	  found
*/
function getTop(element) {
	var top = 0, absoluteAncestor = false, computedStyle = (document.defaultView && document.defaultView.getComputedStyle), currentStyle = (element.currentStyle);
//	so long as the element has an positioning context...
	while (element.offsetParent) {
//		add the top offset of the element
		top += element.offsetTop
//		set the element to the element which provided the positioning context
		element = element.offsetParent;
//		if the element hasLayout (IE-only property)
		if (currentStyle && element.currentStyle.hasLayout && (element.nodeName.toLowerCase() != 'html')) {
//			add the width of the top border
			top += element.clientTop;
//			and if it's absolutely positioned, flag that we've got an absolutely positioned ancestor
			if (element.currentStyle['position'] == 'absolute') absoluteAncestor = true;
//		otherwise, if it's a browser that supports computedStyle & the element is absolutely positioned
		} else if (computedStyle && document.defaultView.getComputedStyle(element, "").getPropertyValue('position') == 'absolute') {
//			add the top border of the element
			top += parseInt(document.defaultView.getComputedStyle(element, "").getPropertyValue('border-top-width'));
//			and flag that we've got an absolutely positioned ancestor
			absoluteAncestor = true;
		}
	}
//	if the browser supports currentStyle (i.e. is IE) and we found an absolutely positioned ancestor, add any top margin on the BODY
	if (!absoluteAncestor && currentStyle) return top += document.getElementsByTagName('BODY')[0].offsetTop;
//	otherwise, if we found an absolutely positioned ancestor and the browser supports computed style add any top border from the BODY
	else if (!absoluteAncestor && computedStyle) top += parseInt(document.defaultView.getComputedStyle(document.getElementsByTagName('BODY')[0], "").getPropertyValue('border-top-width'));
	return top;
}


/*	ADD STYLESHEET -------------------------------------------------
	adds a stylesheet prior to document load
	must be placed after any stylesheets it is to override

	PARAMETERS
	- path = string; path to CSS file, including filename
*/
function addStyleSheet(path) {
	var headElement,linkElement;
//		test that we have the necessary support by getting a
//		reference to the HEAD element, creating a new LINK element
//		and setting the REL attributes for a CSS stylesheet
	if (	(headElement  = document.getElementsByTagName('HEAD')[0]) &&
			(linkElement = document.createElement('LINK'))) {
//		set REL attribute for a CSS stylesheet
		linkElement.setAttribute('rel','stylesheet');
//		set TYPE attribute for a CSS stylesheet
		linkElement.setAttribute('type','text/css');
//		set the HREF to the supplied cssPath + the stylesheet filename 'ticker.css'
		linkElement.setAttribute('href',path);
//		append the link to the head element, thereby adding the stylesheet to the document
		headElement.appendChild(linkElement);
//		note that the method above, while kosher in XHTML with an application/xml+xhtml MIME type,
//		does NOT work in IE5/Mac so the script gracefully degrades in that browser; if compatibility
//		with IE5/Mac is needed comment out all lines from getting the reference to the head down to
//		this commment and uncomment the line immediately following this one; note, though, that
//		document.write is verboten in XHTML sent as application/xml+xhtml
//		document.write('<link rel="stylesheet" type="text/css" href="' + path + '">');
	}
	return linkElement;
}


/*	CLEAR TEXT NODES -----------------------------------------------
	utility to clear the text node children from a parent node; does
	NOT clear text node grandchildren

	PARAMETERS
	- node = node object; node from which text nodes are to be
	  cleared
*/
clearTextNodes = function (node) {
	var numKids = node.childNodes.length;
	for (var i = 0; i < numKids; i++) {
		if (node.childNodes[i].nodeName.toLowerCase() == '#text') {
			node.removeChild(node.childNodes[i]);
			i -= 1;
			numKids -= 1;
		}
	}
}


/*	GET TEXT -------------------------------------------------------
	utility to get collect the text from within an element

	PARAMETERS
	- e = HTML element object; node from which the text is to be
	  collected
*/
getText = function (e) {
	var	numKids = e.childNodes.length,
		i,
		t = '';
	for (i = 0; i < numKids; i++) {
		if (e.childNodes[i].nodeName.toLowerCase() == '#text') t += e.childNodes[i].nodeValue;
		else t += getText(e);
	}
	return t;
}

/*	PARSE PADDED ---------------------------------------------------
	removes 0s from 0-padded numeric strings and returns an integer
	or float

	PARAMETERS
	- num = string; 0-padded numeric string
	- float = boolean; true to parse num as a float; false to parse
	  as an integer
*/
function parseZeroPadded (num,isFloat) {
	if (typeof num == 'number') return num;
	else {
//	strip all leading 0s
		num = num.replace(/^0+/,'');
//	if there were only zeros, we now have a 0-length string and need to set it back to 0
		if (num.length == 0) num = '0';
//	replace one if we've got a decimal point at the beginning
		else if (num.indexOf('.') == 0) num = '0' + num;
		return (isFloat)?parseFloat(num):parseInt(num);
	}
	return false;
}

// 3RD PARTY ======================================================


/*	REMEDIAL APPLY -------------------------------------------------
	adds an apply() method to functions in browsers that don't have
	it
	from: http://www.crockford.com/javascript/remedial.html
	this version doesn't require the .method() widget, however
*/
if (typeof Function.apply != 'function') {
	Function.prototype.apply =  function (o, a) {
		var r, x = '____apply';
		if (!isObject(o)) {
			o = {};
		}
		o[x] = this;
		switch ((a && a.length) || 0) {
		case 0:
			r = o[x]();
			break;
		case 1:
			r = o[x](a[0]);
			break;
		case 2:
			r = o[x](a[0], a[1]);
			break;
		case 3:
			r = o[x](a[0], a[1], a[2]);
			break;
		case 4:
			r = o[x](a[0], a[1], a[2], a[3]);
			break;
		case 5:
			r = o[x](a[0], a[1], a[2], a[3], a[4]);
			break;
		case 6:
			r = o[x](a[0], a[1], a[2], a[3], a[4], a[5]);
			break;
		default:
			alert('Too many arguments to apply.');
		}
		delete o[x];
		return r;
	};
}

/*	REMEDIAL SPLICE ------------------------------------------------
	adds a splice() method to arrays in browsers that don't have it
	from: http://www.crockford.com/javascript/remedial.html
*/
if (typeof Array.prototype.splice != 'function') {
	Array.prototype.splice = function (s, d) {
		var max = Math.max,
			min = Math.min,
			a = [], // The return value array
			e,  // element
			i = max(arguments.length - 2, 0),   // insert count
			k = 0,
			l = this.length,
			n,  // new length
			v,  // delta
			x;  // shift count

		s = s || 0;
		if (s < 0) {
			s += l;
		}
		s = max(min(s, l), 0);  // start point
		d = max(min(isNumber(d) ? d : l, l - s), 0);    // delete count
		v = i - d;
		n = l + v;
		while (k < d) {
			e = this[s + k];
			if (!isUndefined(e)) {
				a[k] = e;
			}
			k += 1;
		}
		x = l - s - d;
		if (v < 0) {
			k = s + i;
			while (x) {
				this[k] = this[k - v];
				k += 1;
				x -= 1;
			}
			this.length = n;
		} else if (v > 0) {
			k = 1;
			while (x) {
				this[n - k] = this[l - k];
				k += 1;
				x -= 1;
			}
		}
		for (k = 0; k < i; ++k) {
			this[s + k] = arguments[k + 2];
		}
		return a;
	};
}


/*	DOM BULDER -----------------------------------------------------
from: http://www.vivabit.com/bollocks/2006/04/06/introducing-dom-builder

Copyright (c) 2006 Dan Webb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
DomBuilder = {
  IE_TRANSLATIONS : {
	'class' : 'className',
	'for' : 'htmlFor'
  },
  ieAttrSet : function(a, i, el) {
	var trans;
	if (trans = this.IE_TRANSLATIONS[i]) el[trans] = a[i];
	else if (i == 'style') el.style.cssText = a[i];
	else if (i.match(/^on/)) el[i] = new Function(a[i]);
	else el.setAttribute(i, a[i]);
  },
	apply : function(o) {
	  o = o || {};
		var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" +
					   "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" +
					   "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
					   "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" +
					   "label|dfn|kbd|samp|var").split("|");
	var el, i=0;
		while (el = els[i++]) o[el.toUpperCase()] = DomBuilder.tagFunc(el);
		return o;
	},
	tagFunc : function(tag) {
	  return function() {
		var a = arguments, at, ch; a.slice = [].slice; if (a.length>0) {
		if (a[0].nodeName || typeof a[0] == "string") ch = a;
		else { at = a[0]; ch = a.slice(1); } }
		return DomBuilder.elem(tag, at, ch);
	  }
  },
	elem : function(e, a, c) {
		a = a || {}; c = c || [];
		var isIE = navigator.userAgent.match(/MSIE/)
		var el = document.createElement((isIE && a.name)?"<" + e + " name=" + a.name + ">":e);
		for (var i in a) {
		  if (typeof a[i] != 'function') {
			if (isIE) this.ieAttrSet(a, i, el);
			else el.setAttribute(i, a[i]);
		  }
	  }
		for (var i=0; i<c.length; i++) {
			if (typeof c[i] == 'string') c[i] = document.createTextNode(c[i]);
			el.appendChild(c[i]);
		}
		return el;
	}
}
/* end Copyright (c) 2006 Dan Webb */
DomBuilder.apply(window);


/**
 * "getElementsByClassName"
 * http://www.snook.ca/archives/javascript/your_favourite_1/
 *
 * Finds all elements with a specific class, within a parent ('node'),
 * and returns them in an array.
 *
 * NOTE: Improved structure, regexp and readability. {pf}
 *
 * Last modified: 2006-11-22 {pf}
 */

function getElementsByClassName(classname, node) {

	var a = [];
	var re = new RegExp('(^| )' + classname + '( |$)');
	var els = node.getElementsByTagName("*");

	for (var i = 0, j = els.length; i < j; i++) {
		if (re.test(els[i].className)) {
			a.push(els[i]);
		}
	}

	return a;

}

/**
 * Validate Field
 *
 * Intelligently checks a form field to make sure either a selection has
 * been made or a value entered (as appropriate to the field type).
 *
 * Last modified: 2006-11-22 {pf}
 */

function validateField(field) {

	switch(field.type) {

		case 'select-one':
			if (field.options[field.selectedIndex].value == '') {
				return false;
			}
			break;

		default: // text/password
			if (getAttr(field, 'class').indexOf('validateEmail') >= 0 ) {
				var emailPattern = /^([a-zA-Z0-9_\-\.\']+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
				var regExp = new RegExp(emailPattern);
				var validEmailAddr = regExp.test(field.value);
				if (!validEmailAddr) {
					return false;
				}
			}
			else if (field.value == '') {
				return false;
			}

	}

	return true;

}

/**
 * Look Up Label
 *
 * A relatively simple function that finds the label for the current
 * form element.
 *
 * Last modified: 2006-11-22 {pf}
 */

function lookUpLabel(field, form) {

	var labelStr = '';
	var htmlLabels = form.getElementsByTagName('label');

	for (var l = 0, htmlLabel; htmlLabel = htmlLabels[l]; l++) {
		if (getAttr(htmlLabel, 'for') == field.id) {
			labelStr = htmlLabel.firstChild.nodeValue;
			break;
		}
	}

	return labelStr;

}

/**
 * Check Requireds
 *
 * Alerts the user to any 'required' inputs, in the target form, which
 * have not been completed.
 *
 * Last modified: 2006-11-21 {pf}
 */
function checkRequireds(form) {

	var errorFlag = false;

	//	Find the hidden warning box-out in our form
	var requiredWarning = getElementsByClassName('requiredWarning', form);
		requiredWarning = requiredWarning[0]; // only ever one per form

	//	Find all required elements that haven't been completed
	var requireds = getElementsByClassName('required', form);

	for (var r = 0, required; required = requireds[r]; r++) {

		if (required.type != undefined) {

			//	Field incomplete
			if (!validateField(required)) {

				//	Set error flag
				if (!errorFlag) {
					errorFlag = true;
				}

				//	Display warning message
				if (requiredWarning.style.display != 'block') {
					requiredWarning.style.display = 'block';
				}

				//	Apply warning wrapper if not already in place
				if (getAttr(required.parentNode, 'class') != 'requiredWrapper') {
					//	Highlight empty required field
					var clonedRequired = required.cloneNode(true);
					var requiredWrapper = document.createElement('div');
					var container = required.parentNode;
					container.replaceChild(requiredWrapper, required);
					setAttr(requiredWrapper, 'class', 'requiredWrapper');
					requiredWrapper.appendChild(clonedRequired);
				}

			}

			//	Field completed
			else {

				//	Remove warning wrapper if already applied
				if (getAttr(required.parentNode, 'class') == 'requiredWrapper') {
					var requiredWrapper = required.parentNode;
					var container = requiredWrapper.parentNode;
					var clonedRequired = required.cloneNode(true);
					container.replaceChild(required, requiredWrapper);
				}

			}

		}

	}

	if (errorFlag) {

		//	Scroll to warning
		window.scroll(0, form.offsetTop);

		//	Prevent form submitting
		return false;

	}
	else {

		//	Submit form
		return true;

	}

}

/**
 * Find Requireds
 *
 * Automatically scans the page for forms, then adds an 'onsubmit'
 * function to any forms which contain input elements classed as
 * 'required'.
 *
 * Last modified: 2006-11-21 {pf}
 */

function findRequireds() {

	//	Find all forms on the page and identify those that need validating upon submission
	var forms = document.getElementsByTagName('form');
	if (forms.length >= 1) {
		for (var f = 0, form; form = forms[f]; f++) {
			var inputs = form.getElementsByTagName('input');
			for (var i = 0, input; input = inputs[i]; i++) {
				if (getAttr(input, 'class')
					&& getAttr(input, 'class').indexOf('required') >= 0) {
					form.onsubmit = function() {
						return checkRequireds(this);
					};
					break;
				}
			}
		}
	}

}

addLoadHandler(
	function() {
		findRequireds();
		return true;
	}
);

//	U T I L I T I E S

/*	GET ATTR -------------------------------------------------------
	gets an attribute value; works around the IE class/className,
	for/htmlfor & camel-case debacles

	PARAMETERS
	- o = object; HTML element on which the attribute resides
	- a = string; attribute name

	REQUIRED SCRIPTS
	- translateAttrName; utility to translate from standard to
	  MS-specific attribute names

	LIMITATIONS
	- may not have exhaustive list of case-sensitive attributes
*/
function getAttr(o,a) {
	var attr;
//	if the.getAttributeNode method exists, use it
	if (o.getAttributeNode) return (attr = o.getAttributeNode(a))?attr.nodeValue:'';
//	otherwise, try getAttribute with translation
	else if (o.getAttribute) return o.getAttribute(translateAttrName(a));
//	if all else fails, try just accessing it as a property
	else return o[a];
}

/*	SET ATTR -------------------------------------------------------
	sets an attribute value; works around the IE class/className,
	for/htmlfor & camel-case debacles

	PARAMETERS
	- o = object; HTML element on which the attribute resides
	- a = string; attribute name
	- v = string; attribute value

	REQUIRED SCRIPTS
	- translateAttrName; utility to translate from standard to
	  MS-specific attribute names

	LIMITATIONS
	- may not have exhaustive list of case-sensitive attributes
*/
function setAttr(o,a,v) {
	var attr;
//	if the getAttributeNode method exists, use it
	if (o.getAttributeNode) {
//		if the attribute already exists, we can just set the nodeValue and have done
		if (attr = o.getAttributeNode(a)) return attr.nodeValue = v;
//		otherwise...
		else {
//			create a new attribute
			attr = document.createAttribute(a);
//			set it's nodeValue
			attr.nodeValue = v;
//			and finally add it to the element
			return o.setAttributeNode(attr);
		}
//	if there's no getAttribute node, try using setAttribute with IE translation
	} else if (o.setAttribute) return o.setAttribute(translateAttrName(a),v);
//	and if all else fails, just set it as a property
	else return o[a] = v;
}

/*	TRANSLATE ATTR NAME --------------------------------------------
	translatest from standard to IE-specific attribute names

	PARAMETERS
	- n = string; attribute name

	LIMITATIONS
	- may not have exhaustive list of case-sensitive attributes
*/
function translateAttrName(n) {
	switch(n) {
		case 'class'		:	return 'className';
		case 'for'			:	return 'htmlFor';
		case 'accesskey'	:	return 'accessKey';
		case 'colspan'		:	return 'colSpan';
		case 'rowspan'		:	return 'rowSpan';
		case 'maxlength'	:	return 'maxLength';
		case 'usemap'		:	return 'useMap';
		default				:	return n;
	}
}


/**
 *	jQuery 'Wait' prototype function
 *  http://docs.jquery.com/Cookbook/wait
 *	Last modified: 2008-10-07 {pf}
 */
$.fn.wait = function(time, type) {
	time = time || 1000;
	type = type || "fx";
	return this.queue(type, function() {
		var self = this;
		setTimeout(function() {
			$(self).dequeue();
		}, time);
	});
};



/**
 *	Show Language Options [jQuery]
 *	Changes the link behaviour of the header's 'Languages' hyperlink so
 *	that it reveals the languages sub-menu on hover.
 *	Last modified: 2008-10-07 {pf}
 */
function showLangOpts() {
	
	$('#languagesDrop').hover(
		//	Fade in language options on 'hover over'
		function() {
			
			$('#languageOptions').fadeIn(175);
			
		},
		//	Null 'hover out' function
		function() {}
	);
	
	$('#languageOptions').hover(
		//	Null 'hover over' function
		function() {},
		//	Fade out language options on 'hover out'
		function() {
			
			$('#languageOptions').fadeOut(500);
			
		}
	);
	
}

//	Bind on DOM load
$(document).ready(function(){
  showLangOpts();
});
