/* Commonly used functions */
/* krudd - emlab, UoW - 16/12/05 */

/* Implement "Function.call" for older browsers */
if ( !Function.prototype.call ) {
	Function.prototype.call = function()
	{
		var argLen = arguments.length;
		if ( argLen < 1 ) return;
		var args = '';
		for ( var i = 1; i < argLen; i++ )
			args += 'arguments[' + i + ']' + ( i < ( argLen - 1 ) ? ',' : '' ); 
		var obj = arguments[ 0 ];
		obj.TEMPFUNCTION = this;
		var result = eval( 'obj.TEMPFUNCTION(' + args + ')' );
		obj.TEMPFUNCTION = null;
		return result;
	}
}

/* Implement "Array.push" for older browsers */
if ( !Array.prototype.push ) {
	Array.prototype.push = function()
	{
		for( var i = 0; i < arguments.length; i++ )
			this[ this.length ] = arguments[ i ];
		return this.length;
	}
}

function isArray( o )
{
    return o && typeof( o ) == 'object' && o.constructor == Array;
}

/* Useful info */
var isMacIE5 = /MSIE 5[\S\s]+Mac/i.test( navigator.userAgent );
var isIE = /MSIE/i.test( navigator.userAgent );
var isOpera = /OPERA/i.test( navigator.userAgent );
var isSafari = /SAFARI/i.test( navigator.userAgent );

/* Strip spaces, etc from string to make it suitable for useing as an id, etc */
function stripNonAlphaNumericChars( str )
{
	return str.replace( /[^a-z0-9]+/gi, '' );
}

/* Escape string for use in a RegExp */
function escapeForRegExp( str )
{
	return str.replace( /(\\|\)|\(|\}|\{|\]|\[|\*|\.|\?|\+|\^|\$|\-|\|)/i, '\\$1' );
}
																				 
/* Remove multiple spaces and trim whitespace from both ends */
function compact( str )
{
	return str.replace( /\s+/, ' ' ).replace( /^\s*|\s*$/g, '' );
}

/* Easy alias for "indexOf( ... ) != -1" */
function contains( haystack, needle )
{
	return haystack.indexOf( needle ) != -1;
}

/* Adding, removing and testing for class names on elements */
function addClass( element, classToAdd )
{
	if ( element && !hasClass( element, classToAdd ) )
		element.className = compact( element.className + ' ' + classToAdd );
}

function removeClass( element, classToRemove )
{
	if ( element && element.className )
		element.className = compact( element.className.replace( new RegExp( '(^| )' + classToRemove + '( |$)' ), ' ' ) );
}

function hasClass( element, classToCheck )
{
	return ( element && element.className && ( element.className.search( new RegExp( '(^| )' + classToCheck + '( |$)' ) ) > -1 ) ) ? true : false;
}

function getElementsByClass( className, tagName, parent )
{
	var elems = new Array();
	var allElems = null;

	if ( !parent ) parent = document;
	if ( !tagName || tagName == '*' )
		allElems = parent.all || parent.getElementsByTagName( '*' );
	else
		allElems = parent.getElementsByTagName( tagName );	

	for( i = 0; i < allElems.length; i++ ) {
		if ( hasClass( allElems[ i ], className ) )
			elems.push( allElems[ i ] );
	}

  return elems;
}

/* hide/show content. */
function hideElement( elem ) { addClass( elem, 'hidden' ); }
function showElement( elem ) { removeClass( elem, 'hidden' ); }

/* -- Gets the (first) highest header (H1 highest - H6 lowest) in a container -- */
function getHighestHeader( container )
{
	var headingTags = new Array();
	for ( var headingLevel = 1; headingTags.length == 0 && headingLevel < 7; headingLevel++ )
		headingTags = container.getElementsByTagName( 'h' + headingLevel );
	
	if ( headingTags.length > 0 )
		return headingTags[ 0 ];

	return null;
}

/* "Protective wrapper" for "onload" functions. Prevents reruning (happens on IE Mac) */
if ( typeof ( onloadFunctionsThatHaveRun ) == 'undefined' )
	var onloadFunctionsThatHaveRun = new Array;

function preventOnLoadRerun( handler )
{
	var flagName = Math.random();
	return function()
	{
		if ( typeof( onloadFunctionsThatHaveRun[ flagName ] ) == 'undefined' ) {
			onloadFunctionsThatHaveRun[ flagName ] = true;
			return handler();
		}
		return;
	};
}

/* Add function to the chain of event functions fired for an element */
function addOnEventHandler( elem, eventName, handler )
{
	if ( !elem || !eventName || !handler ) return;

	if ( ( isMacIE5 || isSafari ) && eventName == 'onload' )
		handler = preventOnLoadRerun( handler );

	addToCleanupList( elem, eventName );

	var oldHandler = elem[ eventName ];
	if ( typeof( oldHandler ) != 'function' ) {
		elem[ eventName ] = function( e )
		{
			e = e||window.event;
			return handler.call( this, e );
		};
	}
	else {
		elem[ eventName ] = function( e )
		{
			e = e||window.event;
			var r1 = oldHandler.call( this, e );
			var r2 = handler.call( this, e );
			return r1 && r2;
		};
	}
}

/* Event handler and attribute cleanup. For the benefit of Win IE */
var cleanupArrayForAttributes = new Array;

function addToCleanupList( obj, attributeName )
{
	cleanupArrayForAttributes.push( { 'obj': obj, 'attributeName': attributeName } );
}

function cleanupDynamicAttributes()
{
	var aLen = cleanupArrayForAttributes.length;
	for ( var i = 0; i < aLen; i++ ) {
		var curItem = cleanupArrayForAttributes[ i ];
		curItem.obj[ curItem.attributeName ] = null
		cleanupArrayForAttributes[ i ] = null;
	}
	cleanupArrayForAttributes = null;
}

addOnEventHandler( window, 'onunload', cleanupDynamicAttributes );

/* Create a generic DOM node, with optional child/text node content */
function createDOMNode( tag, child )
{
    if ( document.createElementNS )
        var node = document.createElementNS( 'http://www.w3.org/1999/xhtml', tag );
    else
        var node = document.createElement( tag );

	if ( child ) {
		if ( typeof( child ) == 'string' ) {
			node.appendChild( document.createTextNode( child ) );
		}
		else if ( isArray( child ) ) {
			for ( var i = 0; i < child.length; i++ ) {
				if ( child[ i ].nodeName )
					node.appendChild( child[ i ] );
			}
		}
		else if ( typeof( child ) == 'object' && child.nodeName ) {
			node.appendChild( child );
		}
	}

	return node;
}

/* Create a link node (DOM methods) */
function createLink( url, child )
{
	var linkNode = createDOMNode( 'a', child );
	linkNode.href = url;
	return linkNode;
}

/* Get lower case nodeName. Tests for null. */
function getNodeName( elem )
{
	if ( elem && elem.nodeName )
		return elem.nodeName.toLowerCase( elem );
	return null;
}

/* Create a unique id from the base id */
function createUniqueId( base )
{
	var newId = stripNonAlphaNumericChars( base );
	var inc = 1;
	while ( document.getElementById( newId ) ) {
		inc = inc + 1;
		newId = base + inc;
	}
	
	return newId;
}

/* Insert node _after_ another node. */
function insertAfter( newNode, siblingNode )
{
	var p = siblingNode.parentNode;
	if ( p )
		return p.insertBefore( newNode, siblingNode.nextSibling );
	else
		return null;
}

/* Add a style set */
function addStyle( rules, id )
{
	if ( document.createStyleSheet && !isMacIE5 ) {
		var elem = document.createStyleSheet();
		elem.cssText = rules;
		if ( id )
			elem.name = id;
	}
	else {
		var elem = createDOMNode( 'link' );

		if ( id )
			elem.id = id;

		elem.rel = 'stylesheet';
		elem.type = 'text/css';
		elem.href = 'data:text/css,' + escape( rules );

		document.getElementsByTagName( 'head' )[ 0 ].appendChild( elem );
	}
}

/* Remove a style set */
function removeStyle( id )
{
	if ( document.createStyleSheet && !isMacIE5 ) {
		for ( var i = 0; i < document.styleSheets.length; i++ )
			if ( document.styleSheets[ i ].name == id ) {
				document.styleSheets[ i ].disabled = true;
				document.styleSheets[ i ].cssText = '';
			}
	}
	else {
		var theStyle = document.getElementById( id );
		if ( theStyle && theStyle.parentNode ) {
			theStyle.href = 'data:text/css,*{}';
			if ( !isSafari )
				theStyle.parentNode.removeChild( theStyle );
		}
	}
}

function getInnerText( node )
{
	if ( node.nodeType == 3 ) // text node
		return node.data;

	if ( node.hasChildNodes() ) {
		var childText = '';
		for ( var i = 0; i < node.childNodes.length; i++ )
			childText += getInnerText( node.childNodes[ i ] );
		return childText;
	}
}

function extractChildNodes( node )
{	
	var children = new Array();
	
	for ( var i = 0; i < node.childNodes.length; i++ )
		children.push( node.childNodes[ i ] );

	for ( var i = 0; i < children.length; i++ )
		node.removeChild( children[ i ] );
	
	return children;
}

/* ---------- Useful common handlers that are run onload (and some before) -------------- */

/* ---------- Make external, and non-HTML, links open in new windows and "target" windows get focus ---------- */
/* krudd - emlab, UoW - 20/01/06 */

function smartOpenRelatedWindow(e)
{
	if ( this.target ) {
		var w = window.open( this.href, this.target );
		w.focus();
		return false;
	}
	return true;
}

function setupSmartOpenRelatedWindow()
{	
	var curHostname = location.hostname;
	var curBaseLoc = location.href.replace( /\?.*|#.*/i, '' );

	// fixes for bugs in Safari 2.0.3
	// location.href == "file:" rather than "file://"
	// location.href is NOT URL encoded, fix for spaces only
	curBaseLoc = curBaseLoc.replace( /^file:\/([^\/])/i, 'file:///$1' );
	curBaseLoc = curBaseLoc.replace( / /g, '%20' );
 
	var baseURLClipper = new RegExp( '^' + escapeForRegExp( curBaseLoc ) + '(\\?[^#]*)?', 'i' );

	var allLinks = document.getElementsByTagName( 'a' ) || document.links;
	for ( var i = 0; i < allLinks.length; i++ ) {
		var curLink = allLinks[ i ];
		if ( !curLink.href ) continue;

		var curLinkCleanHref = curLink.href.replace( baseURLClipper, '' );

		if ( curLinkCleanHref.search( /^#/i ) < 0 ) {

			var curTitle = curLink.title + ( curLink.title.length ? ' ' : '' );

			if ( curLink.href.search( /^mailto:/i ) > -1 && !hasClass( curLink, 'email' ) ) {
				addClass( curLink, 'email' );
				curLink.title = curTitle + '(email link)';
			}
			else if ( curLink.hostname != curHostname && !hasClass( curLink, 'external' ) ) {
				curLink.target = stripNonAlphaNumericChars( curLink.href );
				addClass( curLink, 'external' );
				curLink.title = curTitle + '(external link that opens in separate window)';
			}
			else if ( curLink.href.search( /\.html?(\?[\s\S]+)?$/i ) < 0  && !hasClass( curLink, 'document' ) ) {
				curLink.target = stripNonAlphaNumericChars( curLink.href );
				addClass( curLink, 'document' );
				curLink.title = curTitle + '(opens in separate window)';
			}
		}

		if ( curLink.target ) {
			addOnEventHandler( curLink, 'onclick', smartOpenRelatedWindow );
		}
	}
}

if ( document.getElementsByTagName )
	addOnEventHandler( window, 'onload', setupSmartOpenRelatedWindow );

/* ---------- Supplant some submit buttons with a "proxy link" for aesthetic purposes ---------- */

function setupButtonProxyLinks()
{
	var proxyButtons = getElementsByClass( 'proxied', 'input' );
	for ( var i = 0; i < proxyButtons.length; i++ ) {
		var curButton = proxyButtons[ i ];
		var buttonId = curButton.id;

		if ( buttonId.length > 0 && curButton.type != 'submit' ) return;

		var proxyLink = createLink( '#' + buttonId, curButton.value );
		proxyLink.id = buttonId + 'ProxyLink';
		addOnEventHandler( proxyLink, 'onclick',
			function() {
				document.getElementById( buttonId ).click( );
				return false;
			}
		);
		
		hideElement( curButton );
		curButton.parentNode.insertBefore( proxyLink, curButton );
	}
}

if ( !isMacIE5 && document.getElementById )
	addOnEventHandler( window, 'onload', setupButtonProxyLinks );

/* ---------- Highlight search words and note current search in search box ---------- */

var highlightWords = true;

function cleanAndTrimQueryString( queryString )
{
	string = unescape( queryString );
	string = string.toLowerCase();
	string = string.replace( /(\+)+/gi, ' ' );			// +'s to spaces
	string = string.replace( /[^'\/a-z0-9]|\s+/g, ' ' );	// blank out invalid characters
	string = string.replace( /^\s*|\s*$/g, '' );		// trim spaces

	return string;
}

function getWordListFromQueryString( )
{
	// grab stuff after ? in the URL
	var searchString = window.location.search;

	// find the start of the query
	var queryPrefix = 'f=';
	var startPos = searchString.indexOf( queryPrefix );
	if ( ( startPos < 0 ) || ( startPos + queryPrefix.length == searchString.length ) ) {
		return null;
	}

	// find the end of the query
	var endPos = searchString.indexOf( '&', startPos );
	if ( endPos < 0 )
		endPos = searchString.length;

	// extract the contents of the query	
	var queryString = searchString.substring( startPos + queryPrefix.length, endPos );

	// clean up the query string of words
	var wordsString = cleanAndTrimQueryString( queryString );

	if ( wordsString.length < 1 )
		return null;

	// split the query string of words into an array
	var words = wordsString.split( /\s+/gi );

	if ( words.length < 1 )
		return null;

	return words;
}

function highlightTextInNode( node, text )
{
	var pos, middlebit, endbit, middleclone, spanclone;
	var len = text.length;

	var highlightSpan = createDOMNode( 'span' );
	highlightSpan.className = 'searchHighlight';

	var skip = 0;
	if ( node.nodeType == 3 ) {		// text node
		pos = node.data.toLowerCase().indexOf( text );
		if ( pos >= 0 ) {
			middlebit = node.splitText( pos );
			endbit = middlebit.splitText( len );

			middleclone = middlebit.cloneNode( true );
			newHighlight = highlightSpan.cloneNode( true );

			newHighlight.appendChild( middleclone );
			middlebit.parentNode.replaceChild( newHighlight, middlebit );

			skip = 1;
		}
	}
	else if ( node.hasChildNodes() && getNodeName( node ) != 'script' && getNodeName( node ) != 'style' ) {
		for ( var child = 0; child < node.childNodes.length; child++ )
			child = child + highlightTextInNode( node.childNodes[ child ], text );
	}

	return skip;
}

function compareLen( a, b ) { return ( b.length - a.length ); }

function highlightSearchWords( container )
{
	if ( !container )
		container = document.getElementsByTagName( 'body' )[ 0 ] || document.body;
	
	var words = getWordListFromQueryString();
	if ( words == null )
		return;

	// display actual string searched for in the search field
	var queryInput = document.getElementById( 'f' )
	if ( queryInput )
		queryInput.value = words.join( ' ' );

	words = words.sort( compareLen );
	
	for ( var i = 0; i < words.length; i++ )	{
		if ( words[ i ].length < 1 )
			continue;
		highlightTextInNode( container, words[ i ] );
	}

	// create a "remove highlight" link
	if ( highlightWords ) {
		var removeLink = createLink( '#removeHighlight', 'Remove highlighting' );
		removeLink.id = 'removeHighlight';
		addOnEventHandler( removeLink, 'onclick', removeHighlights );
		
		var searchForm = document.getElementById( 'searchForm' )
		if ( searchForm )
			searchForm.appendChild( removeLink );
	}
}

function removeHighlights( e )
{
	var highlights = getElementsByClass( 'searchHighlight', 'span' );
	for ( var i = 0; i < highlights.length; i++ )	{
		var curSpan = highlights[ i ];
		var parent = curSpan.parentNode;
		parent.insertBefore( curSpan.firstChild, curSpan.nextSibling );
		parent.removeChild( curSpan );
	}
	
	// remove "remove highlight" link
	var removeHighlight = document.getElementById( 'removeHighlight' )
	if ( removeHighlight )
		removeHighlight.parentNode.removeChild( removeHighlight );
		
	return false;
}

function triggerHighlight()
{
	if( highlightWords == true )
		highlightSearchWords( );
}

if ( !isMacIE5 && document.getElementById )
	addOnEventHandler( window, 'onload', triggerHighlight );

/* ---------- Folding lists ---------- */
/* krudd - emlab, UoW - 31/10/05 */

function toggleListItem( e )
{
	var curItem = this.parentNode;
	while ( getNodeName( curItem ) != 'li' )
		curItem = curItem.parent;

	if ( curItem != null ) {
		if ( hasClass( curItem, 'closed' ) ) {
			removeClass( curItem, 'closed' );
			addClass( curItem, 'open' );
		}
		else {
			removeClass( curItem, 'open' );
			addClass( curItem, 'closed' );
		}
	}

	return false;
}

function setupFoldingLists()
{
	// loop through all 'folding lists'
	var foldingLists = getElementsByClass( 'foldingList', 'ul' );
	for ( fli = 0; fli < foldingLists.length; fli++ ) {

		// loop through all child (but not grandchild) 'list items'
		var listItems = foldingLists[ fli ].childNodes;
		for ( i = 0; i < listItems.length; i++ ) {

			var curItem = listItems[ i ];
			if ( getNodeName( curItem ) != 'li' )
				continue;

			// if there's a heading turn it into a toggling link
			var heading = getHighestHeader( curItem );
			if ( heading ) {

				// default to closed list
				if ( !hasClass( curItem, 'open' ) )
					addClass( curItem, 'closed' );
				
				// mark heading as toggle
				addClass( heading, 'toggle' );
				
				// extract heading's child nodes (mostly text)
				var headingText = getInnerText( heading );
				var headingChildren = extractChildNodes( heading );
				
				// tag heading with a unique id
				heading.id = createUniqueId( headingText + 'ListSection' );
				
				// add in a link
				var toggleLink = createLink( '#' + heading.id, headingChildren )
				heading.appendChild( toggleLink );
				
				addOnEventHandler( heading, 'onclick', toggleListItem );
			}
		}
	}
	
	// undo the temporary hide
	removeStyle( 'tempFoldingListHide' );
}

if ( document.getElementById ) {
	// temporarily hide
	addStyle( 'ul.foldingList ul { display: none; } ul.foldingList li.open ul { display: block; }', 'tempFoldingListHide' );

	addOnEventHandler( window, 'onload', setupFoldingLists );
}