// return a reference to a DOM element by ID
function e(id) {
	return document.getElementById(id);
}

// return a reference to a form element by name (within the first form)
function f(name) {
	// modifying to allow for multiple forms (function fails to work as expected in magic)
	var forms = document.forms;
	for (var i = 0; i < forms.length; i++) if (typeof(forms[i].elements[name]) != 'undefined') return forms[i].elements[name];
	return {checked:0, disabled:1, selectedIndex:0, options:{0:{value:'', text:''}}};
}

// return or set the display property of a DOM element (accepts element or element ID)
function d(element, display) {
	if (typeof(element) == 'string') element = e(element);
	if (element == null) return false;
	if (arguments.length == 1) return (element.style.display == 'none' ? false : true);
	else element.style.display = display ? '' : 'none';
	render();
}

// toggle display of a DOM element (accepts element or element ID)
function toggle(element) {
	if (typeof(element) == 'string') element = e(element);
	if (element == null) return false;
	element.style.display = element.style.display == 'none' ? '' : 'none';
	render();
}

// toggle display of a a group of DOM elements by name (doesn't accept type)
function toggleGroup(name) {
	if (typeof(name) == 'string') elements = document.getElementsByName(name);
	if (!elements.length) return false;
	for (var i = 0; i < elements.length; i++) {
		elements[i].style.display = elements[i].style.display == 'none' ? '' : 'none';
	}
	render();
}

// safely set or get an array of classes from an element
function c(element, classes) {
	if (typeof(element) == 'string') element = e(element);
	if (element == null) return false;
	if (arguments.length == 2) element.className = classes.join(' ');
	if (element.className.length) return element.className.split(' ');
	else return new Array();
}

// return whether or not an element has a named class set
function hasClass(element, className) {
	var classes = c(element);
	for (var i = 0; i < classes.length; i++) if (classes[i] == className) break;
	return i < classes.length;
}

// add a class to an element
function addClass(element, className) {
	var classes = c(element);
	for (var i = 0; i < classes.length; i++) if (classes[i] == className) break;
	classes[i] = className;
	c(element, classes);
}

// remove a class from an element
function removeClass(element, className) {
	var classes = c(element);
	for (var i = 0; i < classes.length; i++) if (classes[i] == className) break;
	delete classes[i];
	c(element, classes);
}

// toggle a class on an element
function toggleClass(element, className) {
	var classes = c(element);
	for (var i = 0; i < classes.length; i++) if (classes[i] == className) break;
	if (i < classes.length) delete classes[i];
	else classes[i] = className;
	c(element, classes);
}

// swap two classes on an element
function swapClasses(element, classNameOne, classNameTwo) {
	var classes = c(element);
	for (var i = 0; i < classes.length; i++) {
		if (classes[i] == classNameOne) classes[i] = classNameTwo;
		else if (classes[i] == classNameTwo) classes[i] = classNameOne;
	}
	c(element, classes);
}

// toggle minimise and maximise on a collapsible element
function toggleCollapsible(element) {
	// swap the minimised and maximised classes
	swapClasses(element.parentNode, 'minimised', 'maximised');
	// detect persistence and session the value
	if (hasClass(element.parentNode, 'persistent')) {
		var form = document.forms[document.forms.length - 1];
		form.elements[0].name = element.parentNode.id + 'Open';
		form.elements[0].value = hasClass(element.parentNode, 'maximised') ? 1 : 0;
		form.submit();
	}
	return false;
	render();
}

// trim a string of whitespace
var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
function trim(s) {
	for (var i = 0; i < s.length; i++) if (whitespace.indexOf(s.charAt(i)) == -1) break;
	if (i > 0) s = s.substring(i);
	for (i = s.length - 1; i > -1; i--) if (whitespace.indexOf(s.charAt(i)) == -1) break;
	return s.substring(0, i + 1);
}

// re-render the document (added to allow rendering functions and magic.js to function as expected during document changes)
function render() {
	// call popupReposition function (from magic.js) if it exists and if popupWrapper is visible
	if (popupReposition && d('popupWrapper')) popupReposition();
	// call render functions (from style.js) if they exist
	if (renderTables) renderTables(document);
	if (renderULs) renderULs(document);
	if (renderFieldsets) renderFieldsets(document);
	if (renderSelectAllCheckboxes) renderSelectAllCheckboxes(document);
}

// add "last" class to the last TR and to the last TD in each row of all tables
function renderTables(element) {
	// find all the tables
	var tables = element.getElementsByTagName('table');
	for (var i = 0; tables[i]; i++) {
		// iterate each row in the table adding class "last" to the last cell
		for (var j = 0; j < tables[i].rows.length; j++) addClass(tables[i].rows[j].cells[tables[i].rows[j].cells.length - 1], 'last');
		// add class 'last' to the last row in the table
		addClass(tables[i].rows[j], 'last');
	}
}
// add "last" class to the last item in all lists (ULs, OLs, DLs)
function renderULs(element) {
	// collect and iterate through all the lists
	var lists = [];
	lists = lists.concat(element.getElementsByTagName('ul'), element.getElementsByTagName('ol'), element.getElementsByTagName('dl'));
	for (var i = 0; i < lists.length; i++) for (var j = 0; lists[i][j]; j++) {
		// find the last child in the list that is a element node
		for (var lastItem = lists[i][j].lastChild; lastItem && lastItem.nodeType != 1; lastItem = lastItem.previousSibling) continue;
		// add class 'last' to the last LI in the UL
		addClass(lastItem, 'last');
	}
}
// add "last" class to the last block element in a fieldset
function renderFieldsets(element) {
	// iterate through all the FIELDSETs
	var fieldsets = element.getElementsByTagName('fieldset');
	for (var i = 0; fieldsets[i]; i++) {
		// find the last element in the FIELDSET (that is a element node)
		for (var lastElement = fieldsets[i].lastChild; lastElement && lastElement.nodeType != 1; lastElement = lastElement.previousSibling) continue;
		// ignore FORMs
		if (lastElement.nodeName == 'FORM') for (var lastElement = lastElement.lastChild; lastElement && lastElement.nodeType != 1; lastElement = lastElement.previousSibling) continue;
		// add class 'last' to the last element in the FIELDSET
		addClass(lastElement, 'last');
	}
}

// add "onclick" to any LI elements with only one A element, within a UL class links
function renderClickableLIs(ul) {
	// disabled cause it's not working correctly
	return;
	// get the LIs within the UL
	var items = ul.getElementsByTagName('li');
	for (var i = 0; items[i]; i++) {
		// walk the children of the LI (skipping non-element nodes) searching for an anchor
		for (var itemChild = items[i].firstChild; itemChild && itemChild.nodeType != 1; itemChild = itemChild.nextSibling) if (itemChild.nodeName == 'A') break;
		// if last iterated child is not null it was an anchor so add onclick to the LI to follow the child anchor
		if (itemChild.nodeName == 'A') items[i].onclick = activateChildAnchor;
	}
}
/* activate the child anchor of (this) LI */
function activateChildAnchor() {
	if (this.getElementsByTagName('a')[0].getAttribute('onclick')) eval(this.getElementsByTagName('a')[0].getAttribute('onclick').substring(7));
	else window.location.assign(this.getElementsByTagName('a')[0].getAttribute('href'));
}

// when a parent changes set all of its children to the same state
function handleSelectAllParentCheckboxChange() {
	// set all child checkboxes to the same check state as this parent
	for (var i = 0; this.childCheckboxes[i]; i++) this.childCheckboxes[i].checked = this.checked;
	// update the cache of checked children and clear this parent's indeterminate status
	this.checkedCount = (this.checked ? this.childCheckboxes.length : 0);
	removeClass(this, 'checkboxIndeterminate');
}

// when a child changes update the checked state of the parent
function handleSelectAllChildCheckboxChange() {
	// update the parent's cache
	this.parentCheckbox.checkedCount += (this.checked ? 1 : -1);
	updateSelectAllParentCheckbox(this.parentCheckbox);
}

// set checked state of a parent, including an indeterminate state if some but not all children are checked
function updateSelectAllParentCheckbox(parent) {
	// check the parent if any children are checked
	parent.checked = (parent.checkedCount > 0);
	// if some but not all children are checked then give the (checked) parent a style to indicate not all children are checked (e.g. 50% opacity)
	if (parent.checkedCount > 0 && parent.checkedCount < parent.childCheckboxes.length) addClass(parent, 'checkboxIndeterminate');
	else removeClass(parent, 'checkboxIndeterminate');
}

// Set up behaviour for "select-all" checkboxes in tables
function renderSelectAllCheckboxes(element) {
	// find all "selectallable" tables
	var tables = element.getElementsByTagName('table');
	for (var t = 0; tables[t]; t++) if (hasClass(tables[t], 'selectallable')) {
		// track the parent checkbox for each selectallable group of checkboxes
		var parent = null;
		for (var i = 0; i < tables[t].rows.length; i++) {
			// take the first input in the row as the checkbox
			var checkbox = tables[t].rows[i].getElementsByTagName('input')[0];
			// start a new group of checkboxes
			if (hasClass(tables[t].rows[i], 'selectaller')) {
				// initialise the previous group's parent checkbox
				if (parent) updateSelectAllParentCheckbox(parent);
				// initialise caches on the new parent
				parent = checkbox;
				parent.checkedCount = 0;
				parent.childCheckboxes = [];
			}
			// add the checkbox to the current group
			else {
				checkbox.parentCheckbox = parent;
				// update parent caches
				if (checkbox.checked) parent.checkedCount += 1;
				parent.childCheckboxes.push(checkbox);
			}
			// update the group when this checkbox changes
			checkbox.onchange = (checkbox.parentCheckbox ? handleSelectAllChildCheckboxChange : handleSelectAllParentCheckboxChange);
		}
		// initialise last parent checkbox
		if (parent) updateSelectAllParentCheckbox(parent);
	}
}

// set up z-indexes on collections so shadows display correctly
function renderCollections() {
	// if getElementsByClassName isn't supported the shadowing probably won't be either
	if (!document.getElementsByClassName) return;
	var collections = document.getElementsByClassName('collection');
	// go over each collection and sort their children vertically using the z-index property
	for (var i = 0; i < collections.length; i++) {
		// ignore empty collections
		if (collections[i].children.length == 0) continue;
		// start z-indexes at this number
		var zIndex = 30000;
		collections[i].children[0].style.zIndex = zIndex;
		for (var j = 1; j < collections[i].children.length; j++) {
			var prev = collections[i].children[j-1];
			var curr = collections[i].children[j];
			if (curr.offsetTop > prev.offsetTop) {
				zIndex -= 10; // number of columns
			}
			else zIndex += 1;
			curr.style.zIndex = zIndex;
		}
	}		
}

