/****************************************************************
 ****************************************************************
 **
 ** Helper / Utiltiy Routines
 **
 ** Copyright (c) 2004 Nameless-UK
 ** Copyright (c) 2005-2007 Site Intelligence Ltd.
 **
 ****************************************************************
 ****************************************************************
 */

/****************************************************************
 * Globals
 ****************************************************************
 */
var aCachedREs    = new Array();
var htmlTagMatch  = /<[^>]+>/g;

/****************************************************************
 * Regular Expression Caching
 ****************************************************************
 */
function classCacheRE(sClass) {
	var key = 're_cache_' + sClass;
	if(!aCachedREs[ key ]) {
		aCachedREs[ key ] = new RegExp('^\\s*(.*)\\b('+ sClass +')\\b(.*)\\s*$');
	}
	return( aCachedREs[ key ] );
}

/****************************************************************
 * Match an object for a CSS class
 ****************************************************************
 */
function classMatch(oObject, sClass) {
	if(oObject && oObject.className) {
		return(classCacheRE(sClass).exec(oObject.className));
	}
	return(false);
}

/****************************************************************
 * CSS Class Addition / Removal
 ****************************************************************
 */
function classAdd(oObject, sClass) {
	if(oObject && !classMatch(oObject, sClass)) {
		oObject.className += ' '+ sClass;
	}
}

function classDel(oObject, sClass) {
	var aMatches = classMatch(oObject, sClass);
	if(aMatches) {
		//alert('Foo['+ aMatches.length +'/'+ sClass +'/'+ oObject.className +']: '+  aMatches[0] +'/'+ aMatches[1] +'/'+ aMatches[2] +'/'+ aMatches[ aMatches.length - 1 ]);
		oObject.className = aMatches[1] + aMatches[ aMatches.length - 1 ];
	}
}

function classExchange(oObject, sOldClass, sNewClass) {
	classDel(oObject, sOldClass);
	classAdd(oObject, sNewClass);
}


/****************************************************************
 * CHAINING
 ****************************************************************
 */
function processChain(aChain) {
	// Run each init routine in the chain
	if(aChain) {
		for(var iFunc = 0; iFunc < aChain.length; iFunc++) {
			aChain[ iFunc ]();
		}
	}
}


/****************************************************************
 * Chained onPreLoad and onLoad Handling
 ****************************************************************
 */
function addToOnPreLoadChain(oObject, pFunction) {
	// Add a function to the chain of things to run
	if(!oObject.aPreOnLoadFuncs) {
		oObject.aPreOnLoadFuncs = new Array();
	}
	oObject.aPreOnLoadFuncs.push( pFunction );
}

function addToOnLoadChain(oObject, pFunction) {
	// Add a function to the chain of things to run
	if(!oObject.aOnLoadFuncs) {
		oObject.aOnLoadFuncs = new Array();
	}
	oObject.aOnLoadFuncs.push( pFunction );
}

function processOnLoadChain() {
	// Process the pre-onLoad chain and then the onLoad chain
	processChain(this.aPreOnLoadFuncs);
	processChain(this.aOnLoadFuncs);
}

// Set the above function to be the window's onLoad handler
window.onload = processOnLoadChain;


/****************************************************************
 * Chained onUnLoad Handling
 ****************************************************************
 */
function addToOnUnloadChain(oObject, pFunction) {
	// Add a function to the chain of things to run
	if(!oObject.aOnUnloadFuncs) {
		oObject.aOnUnloadFuncs = new Array();
	}
	oObject.aOnUnloadFuncs.push( pFunction );
}

// Run each init routine in the chain
function processOnUnloadChain() {
	processChain(this.aOnUnloadFuncs);
}

// Set the above function to be the window's onLoad handler
window.onunload = processOnUnloadChain;

/***
 * Chained resize Handling
 */
 function addToOnResizeChain(oObject, pFunction) {
 // Add a function to the chain of things to run
	if(!oObject.aOnResizeFuncs) {
		oObject.aOnResizeFuncs = new Array();
	}
	oObject.aOnResizeFuncs.push( pFunction );
}

// Run each init routine in the chain
function processOnResizeChain() {
	processChain(this.aOnResizeFuncs);
}

window.onresize = processOnResizeChain;


/****************************************************************
 * Object Prototypes
 ****************************************************************
 */
function getElementsByClassName(sClassName, oObject, bNoRecursion) {
	var aoReturnValue = new Array();
	if(!oObject) oObject = document.body;

	for(var oChild = oObject.firstChild; oChild; oChild = oChild.nextSibling) {
		if(classMatch(oChild, sClassName)) aoReturnValue.push( oChild );

		// Recurse if possible / allowed
		if(oChild.hasChildNodes() && !bNoRecursion && oChild.getAttribute && !oChild.getAttribute('ignore')) {
				aoReturnValue = aoReturnValue.concat( getElementsByClassName(sClassName, oChild) );
		}
	}

	return(aoReturnValue);
}


function getElementsByAttributeName(sAttributeName, oObject, bNoRecursion) {
	var aoReturnValue = new Array();

	// Default the object to the document
	if(!oObject) {
		oObject = document.body;
	}

	for(var oChild = oObject.firstChild; oChild; oChild = oChild.nextSibling) {
		if(oChild.getAttribute && oChild.getAttribute(sAttributeName)) {
			aoReturnValue.push( oChild );
		}

		// Recurse if possible / allowed
		if(oChild.hasChildNodes() && !bNoRecursion && oChild.getAttribute && !oChild.getAttribute('ignore')) {
			aoReturnValue = aoReturnValue.concat( getElementsByAttributeName(sAttributeName, oChild) );
		}
	}

	return(aoReturnValue);
}

function getElementsByTagName(sTagName, oObject, bNoRecursion) {

	// Default the object to the document
	if(!oObject) {
		oObject = document.body;
	}

	// Recursion: call original function
	if(!bNoRecursion) {
		return(oObject.getElementsByTagName(sTagName));

	// No recursion: itterate manually.
	} else {
		sTagName = sTagName.toUpperCase();
		var aoReturnValue = new Array();
		for(var oChild = oObject.firstChild; oChild; oChild = oChild.nextSibling) {
			if(oChild.tagName && oChild.tagName.toUpperCase() == sTagName) {
				aoReturnValue.push( oChild );
			}
		}
		return(aoReturnValue);
	}
}

function getCSSOrStyleProperty(sStyleName, oObject) {
	var sPropertyName = sStyleName.replace(/([A-Z])/, '-$1').toLowerCase();

	if(window.getComputedStyle)	return(''+ window.getComputedStyle(oObject, null).getPropertyValue(sPropertyName));
	if(oObject.currentStyle)	return(''+ eval('oObject.currentStyle.'+ sStyleName));
	if(oObject.style)			return(''+ eval('oObject.style.'       + sStyleName));
								return('');
}

function copyAttributes(oSource, oDest)
{
  // TODO we need to check if this mechanism is supported
  // on various different browsers...
  var attributes = oSource.attributes;
  for (var i = 0; i < attributes.length; i++)
  {
    oDest.setAttribute(attributes[i].nodeName, attributes[i].nodeValue);
  }
}

function copyNamedAttributes(oSource, oDest, namedAttributes)
{
  for (var i = 0; i < namedAttributes.length; i++)
  {
    oDest.setAttribute(namedAttributes[i], oSource.getAttribute(namedAttributes[i]));
  }
}


// Compile a value from multiple form elements and text literals
// Each argument is either $id where id is the id of a form element
// or a text literal
function compileValue( )
{
    var compiled = "";
    
	for (var i=0; i<arguments.length; i++)
	{
	   if (arguments[i].charAt(0) == '$')
	   {
	      compiled += document.getElementById(arguments[i].substring(1)).value;
	   }
	   else
	   {
	      compiled += arguments[i];
	   }
	}
	
	return compiled;
}


function stripTags( str )
{
	return str.replace(htmlTagMatch, "");
}

// Note that the set of entities is distinctly non-exhaustive
function deentityify( str ) 
{
	str = str.replace('&amp;' ,'&');
	str = str.replace('&quot;' ,"'");
	str = str.replace('&gt;' ,'>');
	str = str.replace('&lt;' ,'<');
	str = str.replace('&pound;' ,'\xA3');
	return str;
}

function convertNewlinesToBrTags(text)
{
	text = text.replace(/\r?\n/g, "<br>");
	return text;
}

/*
 * Checks the maximum length of the provided textarea and deletes any
 * characters that are over the maxlength field of the textarea.
 * 
 * This should be used as the handler for onkeyup for the textarea to ensure
 * pasting too many characters into the field is prevented.
 */
function checkMaxLength(textarea)
{
	var text = textarea.value;

	if (text.length > Number(textarea.maxlength))
	{
		textarea.value = text.substr(0, Number(textarea.maxlength));
	}
}

/*
 * Enables or disables the specified Nameless-style menu button, which is an anchor.
 */
function enableMenuButton(buttonName, enable)
{
	var button = document.getElementById(buttonName);

	if (button)
	{
		if (enable)
		{
			if (classMatch(button, 'disabled'))
			{
				classDel(button, 'disabled');
				button.onclick = function() { return true; };
			}
		}
		else
		{
			if (!classMatch(button, 'disabled'))
			{
				classAdd(button, 'disabled');
				button.onclick = function() { return false; };
			}
		}
	}
}

/*
 * Standalone buttons (i.e. Site Reporter ones which aren't menu buttons) are anchors wrapped in a DIV.
 * The buttonName should be the ID of the wrapping DIV.
 */
function enableStandaloneButton(buttonName, enable)
{
	var div;

	div = document.getElementById(buttonName);

	if (div)
	{
		if (enable)
		{
			classDel(div, 'disabled');
			div.firstChild.disabled = false;
		}
		else
		{
			classAdd(div, 'disabled');
			div.firstChild.disabled = true;
		}
	}
}

/*
 * Returns the absolute position of the specified visual element.
 */
function getAbsoluteBounds(element, stopElement)
{
	var x;
	var y;
	var width;
	var height;

	x = element.offsetLeft;
	y = element.offsetTop;
	width = element.offsetWidth;
	height = element.offsetHeight;

	while (true)
	{
		try
		{
			element = element.offsetParent;

			if (!element || element == null || element == stopElement)
			{
				break;
			}

			x += element.offsetLeft;
			y += element.offsetTop;
		}
		catch (e)
		{
			break;
		}
	}

	return new Rectangle(x, y, width, height);
}

