/**
 * Utilities v1.5
 * A useful collection of DOM scripting utility functions
 *
 * Dependencies JSON.js
 *
 * @author Andrew Ramsden <http://irama.org/web/dhtml/utilities/>
 * @license Common Public License Version 1.0 <http://www.opensource.org/licenses/cpl1.0.txt>
 */
function Utilities () {
	
	
	this.init = function ()
	{
		if (typeof(JSON)!='undefined') this.JSON = new JSON();
		if (typeof(JSSerializer)!='undefined') this.Serializer = new JSSerializer();
		//this.setDebugElement ('debug-area');
	}
	
	this.getComputedStyle = function (oElm, strCssRule)
	{
		var strValue = '';
		if(document.defaultView && document.defaultView.getComputedStyle){
			strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
		}
		else if(oElm.currentStyle){
			try{
				strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
					return p1.toUpperCase();
				});
				strValue = oElm.currentStyle[strCssRule]; // doesn't seem to work well for IE7
			} catch (e) {
				// blargh!
			}
		}
		//alert(strCssRule+': '+strValue);
		return strValue;
	}
	
	
	this.prependChild = function(theParentNode, theChildNode) {
		if(theParentNode.firstChild) {
			theParentNode.insertBefore(theChildNode, theParentNode.firstChild);
		} else {
			theParentNode.appendChild(theChildNode);
		}
		return true;
	}
	
	/**
	 * Recursively searches the DOM from element down, and returns
	 * the first text node it finds.
	 */
	this.getFirstInnerTextNode = function (element)
	{
		if (element.nodeType == 3) {
			return element.nodeValue;
		} else {
			if (element && typeof(element) == 'object' && element.hasChildNodes()) {
				for (childId in element.childNodes) {
					temp = this.getFirstInnerTextNode(element.childNodes[childId]);
					if (temp!=false) return temp; 
				}
				return false;
			} else {
				return false;
			}
		}
	}
	
	this.hasChildNodeOfNodeName = function (element, nodeName)
	{
		return (this.getChildNodeOfNodeName(element, nodeName) != false)?true:false;
	}
	this.getChildNodeOfNodeName = function (element, nodeName)
	{
		if (!element) return false;
		if (!element.hasChildNodes()) return false;
		
		for (childId in element.childNodes) {
			if (element.childNodes[childId].nodeName == nodeName) {
				return element.childNodes[childId];
			}
		}
		
		return false;
	}
	
	
	/**
	 * test whether variable is an array
	 */
	this.is_array = function (variable)
	{
		return (
			typeof(variable) == 'object' &&
			variable.length &&
			typeof(variable.length) != 'undefined'
		) ? true : false ;
	}
	

	
	/**
	 * Escape any variables that are being serialised.
	 * Escape ' and \
	 */
	this.escapeJSString = function (variable)
	{
		return variable;
		// these replacements are carefully ordered,
		// reorder with care!
		variable.replace("\\","\\\\"); // replace \ with \\
		variable.replace("'","\'"); // replace ' with \'
		return variable;
	}
	
	this.serialize = function (objectToSerialize)
	{
		this.Serializer.Serialize(objectToSerialize);
		return this.Serializer.GetJSString('sObj');
	}
	
	this.unserialize = function (stringToUnpack)
	{
		try {
			eval(stringToUnpack);
		} catch (e) {
			if (typeof(console)!='undefined') {
				console.warn(e.message);
				console.log('Serialised data (stringToUnpack): '+stringToUnpack);
			}
			return null;
		}
		if (typeof(sObj) != 'undefined') return sObj;
		else return null;
	}
	
	
	this.addClass = function (el, className)
	{
		if (!el) return false;
		
		this.removeClass(el, className);
		el.className += " " + className;
	}
	
	this.removeClass = function (el, className)
	{
		if (!(el && el.className)) return false;
		
		var cls = el.className.split(" ");
		var ar = new Array();
		for (var i = cls.length; i > 0;) {
			if (cls[--i] != className) {
				ar[ar.length] = cls[i];
			}
		}
		el.className = ar.join(" ");
	}
	
	this.hasClass = function (el, className)
	{
		if (!(el && el.className)) return false
		
		var cls = el.className.split(" ");
		var ar = new Array();
		for (var i = cls.length; i > 0;) {
			if (cls[--i] == className) {
				return true;
			}
		}
		return false;
	}
	
	// http://www.dustindiaz.com/getelementsbyclass/
	this.getElementsByClass  = function (searchClass,tag,node) {
		var classElements = new Array();
		
		if ( node == null ) node = document;
		if (typeof(node.getElementsByTagName)=='undefined') return classElements;
		
		if ( tag == null )
			tag = '*';
		var els = node.getElementsByTagName(tag);
		var elsLen = els.length;
		var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
		for (i = 0, j = 0; i < elsLen; i++) {
			if ( pattern.test(els[i].className) ) {
				classElements[j] = els[i];
				j++;
			}
		}
		return classElements;
	}
	
	
	/**
	 * Removes leading whitespaces
	 */
	this.ltrim = function ( inStr ) {
		if (typeof(inStr) != 'string') return false;
		var re = /\s*((\S+\s*)*)/;
		return inStr.replace(re, "$1");
	}
	
	/**
	 * Removes ending whitespaces
	 */
	this.rtrim = function ( inStr ) {
		if (typeof(inStr) != 'string') return false;
		var re = /((\s*\S+)*)\s*/;
		return inStr.replace(re, "$1");
	}
	
	/**
	 * Removes leading and ending whitespaces
	 */
	this.trim = function ( inStr ) {
		if (typeof(inStr) != 'string') return false;
		return this.ltrim(this.rtrim(inStr));
	}
	
	
	
	
	
	/**
	 * The current actual height of the element in pixels (without padding,
	 * margin or borders).
	 * @return integer Pixel value (dimension)
	 */
	this.getStyleHeight = function (element)
	{
		styleHeight = parseInt(element.clientHeight);
		if (typeof(element.style.paddingTop)!='undefined') styleHeight -= this.pixelValue(element.style.paddingTop);
		if (typeof(element.style.paddingBottom)!='undefined') styleHeight -= this.pixelValue(element.style.paddingBottom);
		return styleHeight;
	}
	
	/**
	 * The current actual width of the element in pixels (without padding,
	 * margin or borders).
	 * @return integer Pixel value (dimension)
	 */
	this.getStyleWidth = function (element)
	{
		styleWidth = parseInt(element.clientWidth);
		if (typeof(element.style.paddingLeft)!='undefined') styleWidth -= this.pixelValue(element.style.paddingLeft);
		if (typeof(element.style.paddingRight)!='undefined') styleWidth -= this.pixelValue(element.style.paddingRight);
		return styleWidth;
	}
	
	/**
	 * The current height of the element in pixels (including padding,
	 * margin and borders).
	 * @return integer Pixel value (dimension)
	 */
	this.getFullHeight = function (element)
	{
		fullHeight = parseInt(element.clientHeight);
		if (typeof(element.style.marginTop)!='undefined') fullHeight += this.pixelValue(element.style.marginTop);
		if (typeof(element.style.marginBottom)!='undefined') fullHeight += this.pixelValue(element.style.marginBottom);
		if (typeof(element.style.borderTopWidth)!='undefined') fullHeight += this.pixelValue(element.style.borderTopWidth);
		if (typeof(element.style.borderBottomWidth)!='undefined') fullHeight += this.pixelValue(element.style.borderBottomWidth);
		return fullHeight;
	}
	
	/**
	 * The current width of the element in pixels (including padding,
	 * margin and borders).
	 * @return integer Pixel value (dimension)
	 */
	this.getFullWidth = function (element)
	{
		debug(element.style.borderWidth);
		fullWidth = parseInt(element.clientWidth);
		if (typeof(element.style.marginLeft)!='undefined') fullWidth += this.pixelValue(element.style.marginLeft);
		if (typeof(element.style.marginRight)!='undefined') fullWidth += this.pixelValue(element.style.marginRight);
		if (typeof(element.style.borderLeftWidth)!='undefined') fullWidth += this.pixelValue(element.style.borderLeftWidth);
		if (typeof(element.style.borderRightWidth)!='undefined') fullWidth += this.pixelValue(element.style.borderRightWidth);
		return fullWidth;
	}
	
	/**
	 * Tries to parse a pixel value from an style measurement+unit
	 * @return integer Pixel value (dimension)
	 */
	this.pixelValue = function (value)
	{
		if (typeof(console)!='undefined') console.log(value);
		if (value == '') return 0;
		if (value.substr(value.length-2,2) == 'px') {
			val = parseInt(value.substr(0, value.length-2));
			if (typeof(console)!='undefined') console.log(val);
			return val;
		}
		if (value.substr(value.length-1,1) == '%') {
			return 0; // can't handle %
		}
		if (typeof(console)!='undefined') console.log('test');
		return 0; // can't handle anything else ;)
	}
	
	
	
	// Base64 code from Tyler Akins -- http://rumkin.com
	this.keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	
	this.encode64 = function (input) {
		
		//input = ''+input;
		
	   var output = "";
	   var chr1, chr2, chr3;
	   var enc1, enc2, enc3, enc4;
	   var i = 0;
	
	   do {
		  chr1 = input.charCodeAt(i++);
		  chr2 = input.charCodeAt(i++);
		  chr3 = input.charCodeAt(i++);
	
		  enc1 = chr1 >> 2;
		  enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		  enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		  enc4 = chr3 & 63;
	
		  if (isNaN(chr2)) {
			 enc3 = enc4 = 64;
		  } else if (isNaN(chr3)) {
			 enc4 = 64;
		  }
	
		  output = output + this.keyStr.charAt(enc1) + this.keyStr.charAt(enc2) + 
			 this.keyStr.charAt(enc3) + this.keyStr.charAt(enc4);
	   } while (i < input.length);
	   
	   return output;
	}
	
	this.decode64 = function (input) {
	   var output = "";
	   var chr1, chr2, chr3;
	   var enc1, enc2, enc3, enc4;
	   var i = 0;
	
	   // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
	   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
	
	   do {
		  enc1 = this.keyStr.indexOf(input.charAt(i++));
		  enc2 = this.keyStr.indexOf(input.charAt(i++));
		  enc3 = this.keyStr.indexOf(input.charAt(i++));
		  enc4 = this.keyStr.indexOf(input.charAt(i++));
	
		  chr1 = (enc1 << 2) | (enc2 >> 4);
		  chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
		  chr3 = ((enc3 & 3) << 6) | enc4;
	
		  output = output + String.fromCharCode(chr1);
	
		  if (enc3 != 64) {
			 output = output + String.fromCharCode(chr2);
		  }
		  if (enc4 != 64) {
			 output = output + String.fromCharCode(chr3);
		  }
	   } while (i < input.length);
	
	   return output;
	}
	
	
	this.getPageYOffset = function ()
	{
		if (document.documentElement && document.documentElement.scrollTop) {
			return document.documentElement.scrollTop;
		} else if (document.body) {
			return document.body.scrollTop;
		} else if (window.pageYOffset) {
			return window.pageYOffset;
		} else {
			return 0;
		}
	}
	
	this.getPageXOffset = function ()
	{
		if (document.documentElement && document.documentElement.scrollLeft) {
			return document.documentElement.scrollLeft;
		} else if (document.body) {
			return document.body.scrollLeft;
		} else if (window.pageXOffset) {
			return window.pageXOffset;
		} else {
			return 0;
		}
	}
	
	
	/**
	 * Sets the anchor portion of the URL (after the #)
	 * @param String hash The value to set the anchor portion of the URL
	 * @param Boolean jumpToAnchor Whether to allow the browser window
	 *        to scroll to place the anchor specified (targeted by the 
	 *        hash).
	 */
	this.setURLHash = function (hash, /*optional - default = false */ jumpToAnchor)
	{
		// Initialise variables
		hash = (typeof(hash) != 'undefined')?hash:'';
		jumpToAnchor = (typeof(jumpToAnchor) != 'undefined')?(jumpToAnchor)?true:false:false;
		
		if (jumpToAnchor) {
			// Just reset hash, allow browser to move
			window.location.hash = hash;
		} else {
			// If nothing has changed, just quit now
			if (hash == this.getURLHash()) return true;
			
			// Get current scroll position
			currentX = this.getPageXOffset();
			currentY = this.getPageYOffset();
			
			// Reset hash
			window.location.hash = hash;
			
			// Correct the scroll position
			window.scrollTo(currentX, currentY);
		}
		return true;
	}
	
	this.getURLHash = function ()
	{
		return window.location.hash.replace('#','');
	}
	
	
	
	
	/**
	 * Cross browser wrapper for document.getElementById();
	 */
	this.findObj = function (n, d) { // v4.01 - Macromedia Dreamweaver
		var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
		d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
		if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
		for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=this.findObj(n,d.layers[i].document);
		if(!x && d.getElementById) x=d.getElementById(n); return x;
	}
	
	
	
	this.setDebugElement = function (debugElement)
	{
		this.debugElement = (typeof(debugElement)=='object')?debugElement:this.findObj(debugElement);
		return (this.debugElement)?true:false;
	}
	
	/**
	 * Console is great, unfortunately, IE doesn't support it.
	 * IE does support this technique.
	 */
	this.debug = function (dumpObj, /* optional */ label)
	{
		if (typeof(this.debugElement)!='object') return;
		
		// DOM APPROACH FAILED, USE INNERHTML... YUCK!!
		
		htmlStr = '<div class="debug">';
		if (typeof(label)!='undefined') {
			htmlStr += '<strong>'+label+'</strong> ';
		}
		
		htmlStr += this.outStr(dumpObj);
		htmlStr += '</div>';
		
		this.debugElement.innerHTML = this.debugElement.innerHTML+htmlStr;
		
		/*if (this.debugElement.nodeName != 'OL') {
			tempEl = this.debugElement;
			ol = document.createElement('ol');
			tempEl.appendChild(ol);
			this.debugElement = ol;
		}
		
		li = document.createElement('li');
		
		textStr = '';
		if (typeof(label)!='undefined') {
			labelEl =  document.createElement('strong');
			labelEl.appendChild(document.createTextNode(label));
			textStr += ' ';
			textStr += this.outStr(dumpObj);
			text = document.createTextNode(textStr);
			li.appendChild(labelEl);
			li.appendChild(text);
		} else {
			textStr += this.outStr(dumpObj);
			text = document.createTextNode(textStr);
			li.appendChild(text);
		}
		
		
		this.debugElement.appendChild(li);*/
		return true;
	}
	
	
	/**
	 * output for debugging
	 */
	this.out = function (dumpObj, /* optional */ label)
	{
		if (typeof(console)=='undefined') return false;
		
		type = typeof(dumpObj);
		outMessage = this.outStr(dumpObj);
		if (typeof(label) != 'undefined') {
			console.log('\u2799 '+label+' ('+type+')');
			console.log(' \u27A5 '+outMessage);
		} else {
			console.log('\u2799 ('+type+') '+outMessage);
		}
	}
	
	this.outStr = function ()
	{
		type = typeof(dumpObj);
		switch (type) {
			default:
				return 'Utilities::out() - unknown type: '+type;
			break;
			case 'string':
				return dumpObj;
			break;
			case 'number':
				return dumpObj;
			break;
			case 'object':
				return this.serialize(dumpObj);
			break;
			case 'function':
				return dumpObj;
			break;
		}
	}
	
	
	this.init();
}

