/* Copyright (C) 2009 Splunk Inc. All Rights Reserved. Version 3.0 */


////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

// define key codes
// see http://www.quirksmode.org/js/keys.html
var KEY_BACKSPACE	= 8;
var KEY_TAB			= 9;
var KEY_ENTER		= 13;
var KEY_ESCAPE		= 27;
var KEY_SPACE		= 32;
var KEY_DELETE		= 46;
var KEY_UP			= 38;
var KEY_DOWN		= 40;
var KEY_LEFT		= 37;
var KEY_RIGHT		= 39;
var KEY_END			= 35;
var KEY_HOME		= 36;
var KEY_PGUP		= 33;
var KEY_PGDN		= 34;
var KEY_0			= 48;
var KEY_1			= 49;
var KEY_2			= 50;
var KEY_3			= 51;
var KEY_4			= 52;
var KEY_5			= 53;
var KEY_6			= 54;
var KEY_7			= 55;
var KEY_8			= 56;
var KEY_9			= 57;
var KEY_J			= 74;
var KEY_K			= 75;
var KEY_S			= 83;
var KEY_T			= 84;

// these cannot be detected on OSX so use the event properties instead
var KEY_SHIFT		= 16;	// event.shiftKey
var KEY_CTRL		= 17;	// event.ctrlKey || event.metaKey
var KEY_ALT			= 18;	// event.altKey

// special case for modified alphanumerics
var KEY_CTRL_M		= 109;
var KEY_CTRL_M_IE	= 13;
var KEY_CTRL_R		= 114;
var KEY_CTRL_R_IE	= KEY_CTRL_R;

// separator character for browser window title
var WINDOW_TITLE_SEPARATOR = ' - ';

// search-language operators and arguments. TODO:: xsl currently still contains several hardcoded references to 'where'.
var SL_OPERATOR_WHERE = "where"

// define max number of milliseconds between clicks to constitute double click
var DBL_CLICK_THRESHOLD = 250;

////////////////////////////////////////////////////////////////////////////////
// Global Utilities
////////////////////////////////////////////////////////////////////////////////


util = {
	re : {
		startTimeTerm     :  /^starttime(::|=)\S+/,
		endTimeTerm       :  /^endtime(::|=)\S+/,
		indexTerm         :  /\b(index(?:\"?)\S+)(?:\"?)/,
		indexName         :  /\bindex(?:=|::)((?:\"[^\"]+\")|(?:\S+))/,  // warning: will also match quote chars. Clients must strip quotes themselves.
		legacyRelTimeTerm :  /(\S+sago(::|=)\S+)/g,  //because of limitations with js regex, this is a 2 stage test, the first not using the regex, the regex should find this + ! starting with start or end....
		relStartTimeTerm  :  /(\bstart\S+sago(::|=)\S+)/g,
		relEndTimeTerm    :  /(\bend\S+sago(::|=)\S+)/g,
		durationTerm      :  /(\bsearchtimespan\S+s::\S+)/,
		timeformatTerm    :  /(\btimeformat(::|=)\S+)/,
		peerTerm          :  /\b(server(::|=)\S+)\b/,
		deleteTerm        :  /\b(delete(::|=)\S+)\b/,
		multiSpace        :  /\s{2,}/g,
		leadingSpace      :  /^\s*/,
		trailingSpace     :  /\s*$/,
		kvSelect          :  / \| kv \| select .+/,
		countTerm         :  /\bcount=(\S+)\b/	
	},
    // removed the following, as per SPL-11876 
    //maxResultsTerm    :  /\bmaxresults(::|=)(\S+)\b/
	_defaultIndex : "default",
	_timezoneOffset : "480",
	_productName : "unknown",
	_instanceName : "unknown",


	getOrigin : function(mozEvent) {
		//return (mozEvent ? mozEvent.target : window.event.srcElement);
		//var event = (document.all) ?  window.event.srcElement : mozEvent.target;

		if (document.all) {
		    // in IE
		    if (window.event) return window.event.srcElement;
		} else {
		    // in FF
		    if (mozEvent) return mozEvent.target;
		}
		// should not happen
	},
  
	// returns true if the object already was of this class.  False if it wasnt.  
	// from prototype.js 1.6.  Faster than previous implementation
	addClass: function(element, className) {
		if (!element) return false;
		if (util.isOfClass(element, className)) return true;

		element.className += (element.className ? ' ' : '') + className;
		return false;
	},

	// returns true if the object already wasn't of this class.  False if it was.  
	removeClass: function(element, className) {
		if (!element) return;
		if (util.isOfClass(element, className))
		{
			element.className = element.className.replace(
				new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
			return false;
		}
		else return true;
	},

	// tests whether an element is of class className 
	// DOES NOT match in the broader case when the className att contains the string as a part of some other class' name
	// from prototype.js 1.6.  Slightly faster than previous implementation
	isOfClass: function(element, className)
	{
		if (!element) return false;
		var elementClassName = element.className || '';
		return (elementClassName.length > 0 && (elementClassName == className ||
		  new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
	},
  
  
  
	/**
	 * gets uppercase tagname for the given element.  
	 * DONT USE element.tagName, as the case can depend on DOCTYPE, content-type etc... 
	 *
	 * @param {HTMLElement} 
	 */
	 getTagName : function(element) { 
		if (!element || !element.tagName) return "undefined";
		return element.tagName.toUpperCase();
	},
	
	/**
	 * Toggles an object class; either on or off
	 *
	 * @param {boolean} true if class is now set; false if class has been
	 *					removed.
	 */
	toggleClass : function(obj, className) {
		if (!obj) return false;
		if (this.isOfClass(obj, className)) {
			this.removeClass(obj, className);
			return false;
		} else {
			this.addClass(obj, className);
			return true;
		}
	},
	
	hide : function(element) {
		if(typeof(element) == 'string') element = $(element);
		if(!element) return false;
		element.style.display = 'none';
	},

	show : function(element) {
		if(typeof(element) == 'string') element = $(element);
		if(!element) return false;
		element.style.display = 'block';
	},
	
	//TODO rewrite, improve, or drown.
	getValueForKey : function(name, queryString) {
		queryString = "&" + queryString
		var start = queryString.indexOf("&"+name+"=");
		if (start==-1) return false;
		start++;

		// throw away all the stuff before.
		queryString = queryString.substring(start+(name.length) + 1);
		var end = queryString.indexOf("&");
		if (queryString.indexOf("&")==-1) end = queryString.length;
		return queryString.substring(0,end);
	},
	getSelectedValue : function(selectElement, optionalAttributeName) {
		var option = selectElement.options[selectElement.selectedIndex];
		if (optionalAttributeName) return option.getAttribute(optionalAttributeName);
		else if (option.value != "") return option.value;
		else return option.text 
	},


	setSelectedValue : function(selectElement, value, optionalAttributeName) {
		value = value.toLowerCase();
		var lim = selectElement.options.length;
		for (var i=0; i<lim; i++ ) {
			var option = selectElement.options[i];
			if ((optionalAttributeName && option.getAttribute(optionalAttributeName) == value) || !optionalAttributeName && (option.value.toLowerCase() == value)) {
				selectElement.selectedIndex = i;
				return true;
			}
		}
		return false;
	},
	getEventDate : function(eventRow) {
		try {
			var divNodes = eventRow.getElementsByTagName("DIV")
			var timestampRow = divNodes[0];
			var lim = divNodes.length;
			for (var i=0; i<lim; i++) {
				if (util.isOfClass(divNodes[i], "timestamp")) {
					timestampRow = divNodes[i].childNodes[0];
					break;
				}	
			}
			var foo = Date.parseDate(util.getTextContent(timestampRow), window.SEARCH_RESULTS_TIME_FORMAT);
			//D.debug("xxz getEventDate's fault");
			//D.debug("found timestamp of " + util.getTextContent(timestampRow));
			//D.debug("and parsed it as " + foo);
			if (foo.getYear() < 75)  foo.setYear(foo.getYear() + 2000);
			return foo
			
			
		}
		catch (e) {
			return new Date()
		}
		
	},
	getLeft : function(element) {
		var walker = element;
		var totalOffsetLeft = 0;
		while (walker && walker.offsetParent) {
			totalOffsetLeft += walker.offsetLeft;
			walker = walker.offsetParent;
		}
		return totalOffsetLeft;
	},
	getRight : function(element) {
		return util.getLeft(element) + element.offsetWidth;
	},
	getTop : function(element) {
		var walker = element;
		var totalOffsetTop = 0;
		
		while (walker && walker.offsetParent) {
			//debugMsg(util.getTagName(walker)+ "." + walker.className + ", id " + walker.getAttribute("id") + " offsetTop " + walker.offsetTop)
			totalOffsetTop += walker.offsetTop;
			walker = walker.offsetParent;
		}
		
		return totalOffsetTop;
	},

	/**
	 * Gets the current/computed style for a specific element.
	 * 
	 * CAUTION: see http://erik.eae.net/archives/2007/07/27/18.54.15/
	 * 		for full caveats
	 *
	 * @param {DOMElement} el The element to inspect
	 * @param {String} prop The property to return
	 *
	 */
	getStyle: function(el, prop) {
		if (document.defaultView && document.defaultView.getComputedStyle) {
			return document.defaultView.getComputedStyle(el, null)[prop];
		} else if (el.currentStyle) {
			return el.currentStyle[prop];
		} else {
			return el.style[prop];
		}
	},
	
	
	/**
	 * Parses a string CSS color value into an integer.  Accepts color definitions
	 * in both #AABBCC and rgb(10,20,30) format.
	 *
	 * @param {String} color The color value to parse
	 *
	 */
	parseCSSColor: function(color) {
		if (color.startswith('#')) {
			if (color.length == 4) 
				color = color.substring(1,2) + color.substring(1,2) + color.substring(2,3) + color.substring(2,3) + color.substring(3,4) + color.substring(3,4);
			else 
				color = color.substring(1);
			return parseInt(color, 16);
		}
		var re = /rgb\(([0-9]{1,3}),\s*([0-9]{1,3}),\s*([0-9]{1,3})\)/;
		var m = color.match(re);
		if (!m) return false;
		return parseInt(m[1],10) << 16 | parseInt(m[2],10) << 8 | parseInt(m[3],10);
	},
	
	/**
	 * Makes a deep copy of a DOM node and copies it as a child of the
	 * destination (target) node.
	 *
	 * @param {DOMElement} target The destination node in which to insert the
	 * 				new elements.
	 * @param {DOMElement} sourceReference The element node to deep copy.
	 *
	 */
	copyIntoDocument : function(target, sourceReference) {
		
		var targetNode = (typeof(target) == "object") ? target : $(target);

		if (false && target.parentNode) {
			alert('using new turboInnerhtml hotness for ' + target.id);
			var newEl = target.cloneNode(false);
			newEl.innerHTML = sourceReference.innerHTML;
			target.parentNode.replaceChild(newEl, target);
			return newEl;
		// for the endless pager in particular, there is no parentNode since we just clone 
		// in the entire XmlHttpRequest response,  ricepaper style.  Thus right now im just 
		// using the old tried and true implementation that we've had for years. (below)
		} else {
			targetNode.innerHTML = '';  
			// it's like a quarter second faster to just append on the inplace reference,
			// rather than clone the whole thing like we were doing before.
			if (document.all) {
				var outerHTML = sourceReference.outerHTML;
				if (outerHTML)  targetNode.innerHTML = sourceReference.outerHTML;
				else {
					var lim = sourceReference.childNodes.length;
					for (var i=0; i<lim;  i++) {
						var newFloatingClonedNode = sourceReference.childNodes[i].cloneNode(true);
						targetNode.appendChild(newFloatingClonedNode);
						delete newFloatingClonedNode;	
					}
				}
			}
			else {
				var newFloatingClonedNode;
                // oldschool deep copy with importNode. I think this was tried as a sol'n to safari issues?
				//newFloatingClonedNode = target.ownerDocument.importNode(sourceReference, true);
                
				
                // http://www.oreillynet.com/xml/blog/2008/01/thoughts_on_firefox_30.html
                // Firefox 3 enforces proper foo.ownerDocument, and provides adoptNode to switch it.
                // the trouble is, firefox 2 also implements document.adoptNode too, 
                // but it throws NS_ERROR_NOT_IMPLEMENTED, which is weak. 
                //if ("adoptNode" in document) {
                //   newFloatingClonedNode = document.adoptNode(sourceReference);
                //} else newFloatingClonedNode = document.importNode(sourceReference,true);
                newFloatingClonedNode = document.importNode(sourceReference,true);
                
                   
                
                targetNode.appendChild(newFloatingClonedNode);
                
				
				delete newFloatingClonedNode;
			}
		}
		/*
		// another dead-simple, even older implementation. was 19.8% faster, by my somewhat painstaking calculation,  than cloning and appending the node.
		//but doesnt work when the content is heavily nested.   in these cases it starts enforcing XHTML rules like not allowing nested labels. 
		// which unfortunately wreaks some havoc with our product because we use label as a generic flavorless 'segment' tag, and nest them a lot.
		var targetNode = (typeof(target) == "object") ? target : $(target);
		targetNode.innerHTML = sourceReference.innerHTML;
		return true;	
		*/

	},
	
	setCookie : function(cookieName,cookieValue,nDays) {
		var today = new Date();
		var expire = new Date();
		if (nDays==null || nDays==0) nDays=1;
		expire.setTime(today.getTime() + 3600000*24*nDays);
		document.cookie = cookieName+"="+escape(cookieValue) + ";expires="+expire.toGMTString();
	},
	getCookie : function(cookieName) {
		var cookieString=""+document.cookie;
		var index1=cookieString.indexOf(cookieName);
		if (index1==-1 || cookieName=="") return ""; 
		var index2=cookieString.indexOf(';',index1);
		if (index2==-1) index2=cookieString.length; 
		return unescape(cookieString.substring(index1+cookieName.length+1,index2));
	},
	// a little global function that returns a reasonably cross-browser reference to the originating object of the given mozEvent. 
	getEvent : function(mozEvent) {
		return mozEvent || window.event;
	},

	setLeft : function(element, left) {
		(document.all) ? element.style.posLeft = left : element.style.left = left + "px";
	},
	setTop : function(element, top) {
		(document.all) ? element.style.posTop = top : element.style.top = top + "px";
	},
	setWidth : function(element, width) {
		(document.all) ? element.style.posWidth = width: element.style.width = width+ "px";
	},
	setHeight : function(element, height) {
		(document.all) ? element.style.posHeight = height: element.style.height = height+ "px";
	},
	firstIsParentOf : function(first, second) {
		var walker = second;
		while (walker.parentNode && walker !=first) {
			walker = walker.parentNode;
		}
		if (walker==first) return true;
		else return false;
	},
	trimString : function(sInString) {
		sInString = sInString.replace(util.re.leadingSpace, "" );// strip leading
		sInString = sInString.replace("\n","");
		return sInString.replace(util.re.trailingSpace, "" );     // strip trailing
	},
	toTitleCase : function(sInString) {
		var ls = sInString.toLowerCase();
		//turn it into an array by splitting at spaces
		var la = ls.split(" ");
		//loop through word array
    var lim = la.length;
		for (var i = 0; i < lim; i++ ) {
			//replace first letter with uppercase version
      var item = la[i];
			la[i] = item.charAt(0).toUpperCase() + item.slice(1);
		}
		//rejoin words and return the new string
		return la.join(" ");
	},
	containsTerm : function(needle, haystack) {

		// is the needle a KV pair?
		var isKV = (needle.indexOf("=") != -1) || (needle.indexOf("::") != -1);
		if (isKV) {
			// quotes are done away with for this comparison, :: and = are replaced with _
			// since the colonectomy, all sorts of :: and = silliness is permitted.
			var nNeedle   =   needle.toLowerCase().replace(/::/g, '_').replace(/=/g, '_').replace(/"/g, '');
			var nHaystack = haystack.toLowerCase().replace(/::/g, '_').replace(/=/g, '_').replace(/"/g, '');
			//D.debug('XXX ' + nNeedle + ' | ' + nHaystack);

			if (nHaystack.indexOf(nNeedle) != -1) {
				return true;
			}
			else return false;
			
		} else {
			// if the needle wasn't a KV, don't have to worry about the un/quoted silliness
			var haystackComparator = " " + this.trimString(haystack.toLowerCase()) + " ";
			var needleComparator = " " + this.trimString(needle.toLowerCase()) + " ";	

			if (haystackComparator.indexOf(needleComparator) !=-1) {
				return true;
			}
			else return false;
		}
	},
	removeTerm : function(needle, haystack) {
		if (!this.containsTerm(needle, haystack)) return haystack;
		var loweredNeedle = needle.toLowerCase();
		var loweredHaystack = " " + haystack.toLowerCase() + " ";
		// finds indexes of the offending word,  and slices it out.  Need to do this because we dont want to change haystack's casing.
		var replacementStartIndex = loweredHaystack.indexOf(" " + loweredNeedle + " ")
		if (replacementStartIndex != -1) {
			var replacementEndIndex = replacementStartIndex + loweredNeedle.length + 1
			var newTermString = haystack.substring(0,replacementStartIndex)  + haystack.substring(replacementEndIndex, haystack.length)
			return this.trimString(newTermString);
		}
		else return this.trimString(haystack);
	},

  // optimized to use cached value
	getWindowDimString : function() {
    if (!window.dimString)
    {
      if (!is.opera && this.innerHeight) window.dimString = "self.inner";
      else if (!is.opera && document.documentElement && document.documentElement.clientHeight) window.dimString = "document.documentElement.client";
      else if (document.body) window.dimString = "document.body.client";
    }
    
    return window.dimString;
	},
	getWindowHeight : function() {
		return eval(this.getWindowDimString() + "Height");
	},
	getWindowWidth : function() {
		return eval(this.getWindowDimString() + "Width");
	},
	belowTheFold : function(element) {
		try {        return (this.getTop(element) > this.getWindowHeight());      }
		catch (e) {  return false;  }
	},

	getIframeDocument : function() {
		var iframe = $("queryFetcher");
		var iframeDoc = (iframe.contentWindow || iframe.contentDocument);
		if (iframeDoc.document) return iframeDoc.document;
		return iframeDoc;
	},
	getListeningPort : function() {
		if (window.listeningPort) return window.listeningPort;
		else {
			var listeningPort = $("footer").getAttribute("listeningPort");
			if (listeningPort) return window.listeningPort = listeningPort;
			D.error("Could not get port number that is needed to retrieve the required session key.");
		}
	},
	getBuildNumber : function() {
		return window.SPLUNK_BUILD_NUMBER
	},
	getStaticPath : function() {
		return "/static_oxiclean_"+ util.getBuildNumber();
	},
	isNumeric : function(candidate) {
		return (candidate == parseInt(candidate, 10));
	},
	zeroPad : function(intVal, places) {
		while (intVal.length < places) {
			intVal = "0" + intVal; 
		}
		return intVal;
	},
	undoXMLEscaping : function(xmlSafeStr) {
		var s=xmlSafeStr.replace(/\&lt;/g,"<");
		s=s.replace(/\&gt;/g,">");
		s=s.replace(/\&amp;/g,"&");
		s=s.replace(/\&quot;/g,"\"");
		return s.replace(/\&apos;/g,"'");
	},
        isXmlSafe : function(isXMLSafeStr) {
                xmlUnsafeChar = "<>&\"'";
                for (var i = 0; i < xmlUnsafeChar.length; i++) {
                        if (isXMLSafeStr.indexOf(xmlUnsafeChar[i]) != -1 ) return false;
                }
                return true;
        },
	getTextContent : function(element)  {
		
		if (!element) {
			throw new Error('util.getTextContent - null element passed');
		}
		
		// we are careful, cause the element reference might get garbage-collected and be sorta kinda working when it gets here, but have no content.  
		// strange cases, but reproducible. Seems like DOM3 stuff gets killed first. 
		try {
			if (element.textContent) {
				return element.textContent;
			} else if (element.innerText) {
				return element.innerText;
			} else if (element.text) {
				return element.text;
			}
		}
		// right now the only known cases are if the elements are being cleared 
		catch (e) {
			if ((element.childNodes.length == 1 ) && element.childNodes[0].nodeType == 3) {
				return element.innerHTML;
			}
			else {
				// TODO:
			}
		}
		return ""
	},
	getOverflow : function(field) {
		var overflow = field.scrollHeight - field.offsetHeight;
		return overflow;
	},
	scrollToId : function(id, center) {
		var selectedEvent = $(id);
		var resultsScroller = $("resultsScroller");
		var scrollerOffset = util.getTop(resultsScroller);
		var windowHeight = util.getWindowHeight();
		var newScrollTop = util.getTop(selectedEvent) - scrollerOffset;
		if (center) newScrollTop -= ((windowHeight- scrollerOffset)/2);

		resultsScroller.scrollTop = Math.round(newScrollTop)
	},

	// from prototype.js 1.6
	cumulativeOffset: function(element) {
		var valueT = 0, valueL = 0;
		do {
			valueT += element.offsetTop  || 0;
			valueL += element.offsetLeft || 0;
			element = element.offsetParent;
		} while (element);
		return { left: valueL, top: valueT };
	},

	// from prototype.js 1.6
	// return the cumulative scroll offset of an element
	cumulativeScrollOffset: function(element) {
		var valueT = 0, valueL = 0;
		do {
			valueT += element.scrollTop  || 0;
			valueL += element.scrollLeft || 0;
			element = element.parentNode;
		} while (element);
		return { left: valueL, top: valueT };
	},
	
		
	setSelectionRange : function(input, selectionStart, selectionEnd) {
	  if (input.setSelectionRange) {
		input.focus();
		input.setSelectionRange(selectionStart, selectionEnd);
	  }
	  else if (input.createTextRange) {
		var range = input.createTextRange();
		range.collapse(true);
		range.moveEnd('character', selectionEnd);
		range.moveStart('character', selectionStart);
		range.select();
	  }
	},
	/*
	escapeBarInEllipses : function(text, open, close) {
    
		var re = new RegExp("(?![\\\\])(\\" + open + '|\\' + close + ')');
		text = text.replace(/\\\"/, "__ELVIS__");
		D.debug("argument " + text);

	
		var sepSubs = text.split(re);

		alert(sepSubs);
		var result = ""
		var depth = 0
		for (var i=0; i<sepSubs.length; i++) {
			var val = sepSubs[i]
			// handle quotes and other ellipses that only have depth of 1. alternate between 0, 1
			if ((val == open) && (val == close)) {
				depth = (depth+1) % 2;
			} else if (val == open) {
				depth += 1;
			} else if (val == close) {
				depth -= 1;
			}
				
			if (depth == 0) {
				result += val;
			} else {
				result += val.replace("|", "XXX");
			}
		}
		result = result.replace(/__ELVIS__/, "\\\"");
		D.debug("result " + result);
		return result;
	},
	*/

	
	setDefaultIndex : function(newIndex)	{this._defaultIndex = newIndex;},
	getDefaultIndex : function()			{return this._defaultIndex;},
	setServerTimezone : function(offset)	{this._timezoneOffset = 	offset;},
	getServerTimezone : function()			{return this._timezoneOffset;},
	setInstanceName : function(name)		{this._instanceName = name;},
	getInstanceName : function()			{return this._instanceName;},
	setProductName : function(name)			{this._productName = name},
	getProductName : function()				{return this._productName},
	
	/**
	 * Completely stops all further event behavior.
	 *
	 */
	extinguishAllEvents: function(evt) {
		if (window.event) {
			window.event.cancelBubble = true;
			window.event.returnValue  = false;
		}
		if (evt.stopPropagation) evt.stopPropagation();
		if (evt.preventDefault) evt.preventDefault();
	},
	cancelEventBubbling : function(mozEvent) {	
		if (document.all)  window.event.cancelBubble = true;
		else if (mozEvent) {
			if (mozEvent.stopPropagation) mozEvent.stopPropagation();
			else D.warn("event has no stopPropagation method. util.cancelEventBubbling failed.");
		}
		else D.warn("cancelEventBubbling was called but took no action.");
	},
	isLastDayOfMonth : function(oDate)  {
		oLocalDateCopy = new Date(oDate)
		var month = oLocalDateCopy.getMonth();
		var day   = oLocalDateCopy.getDate();
		oLocalDateCopy.setDate(oLocalDateCopy.getDate() + 1);
		D.debug("toConciseString - isLastDayOfMonth " + oLocalDateCopy.getMonth() + " == " + month);
		return (oLocalDateCopy.getMonth() != month );
	}, 
	
	
	/**
	 * Takes a given DOM node, and recursively appends 'idSuffix' to the 'id'
	 * attribute of any element that already has one assigned.
	 *
	 */
	renameNodeIds: function(node, idSuffix) {
		if (node.id) node.id += idSuffix;
		var lim = node.childNodes.length;
		for (var i=0, l=lim; i<l; i++) {
			var child = node.childNodes[i];
			if (child.hasChildNodes) {
				util.renameNodeIds(child, idSuffix);
			} else if (child.id) {
				child.id += idSuffix;
			}
		} 
	}, 
	
	
	/**
	 * Encodes a search term for use in a general search string.  The search
	 * language needs slashes and quotes to be escaped
	 *
	 * @param {String} termStr The search term to encode
	 * @param {Boolean} forceOuterQuotes Indicates whether or not to always 
	 * 		quote the returned string
	 *
	 * @param {String} The encoded search term
	 *
	 */
	encodeSearchTerm : function(termStr, forceOuterQuotes) {
		// replace backslash chars with backslash-escaped backslash chars.
		termStr = termStr.replace(/\\/g, '\\\\');
		// replace quote chars with backslash-escaped quote chars. 
		termStr = termStr.replace(/\"/g, '\\"');

		
		
		// vars for clarity - this is a weird case of catching unmatched opening or unmatched closing parens. (not both)
		var hasOpeningParen = termStr.charAt(0)!="(";
		var hasClosingParen = termStr.charAt(termStr.length -1)!=")";

		// furthermore, if it contains either opening or closing square bracket or pipe anywhere,  wrap it in quotes. (escaping square brackets doesnt work)
		if ((termStr.indexOf("[") !=-1  ) || (termStr.indexOf("]") !=-1) || (termStr.indexOf("|") !=-1)  || (hasOpeningParen != hasClosingParen) || (termStr.indexOf("<") !=-1  ) || (termStr.indexOf(">") !=-1  ) || (termStr.indexOf("!=") !=-1  )) {
			termStr = '"' + termStr + '"';
		}
		// if the term doesn't already begin and end with quotes,
		else if ((termStr.charAt(0)!='"' || termStr.charAt(termStr.length-1)!='"')) {
			// and the client asked that it be quoted.  Or it contains spaces.
			if (forceOuterQuotes || termStr.indexOf(" ") !=-1 || termStr.indexOf("=") !=-1)  {
				termStr = '"' + termStr + '"';
			}

		}
		//D.debug("xxy encodeSearchTerm " + termStr);
		return termStr;
	},

	getLastRefreshedTimeFormat : function() {
		//return "%m.%d.%Y %H:%M:%S"
		return window.SEARCH_RESULTS_TIME_FORMAT.replace(/\//g, '.')
		
	},

	/*
		Creates a k-v pair in the search language format of key="value"

		@returns key="value" if both k and v are strings, k=value if v is number, false if either k or v are undefined
		@param {string} k is the key
		@param {string} v is the value
	*/
	makeSearchKV: function(k, v, forceOuterQuotes) {

		// by default we will have quotes in non-numeric
		var quotes;
		if (forceOuterQuotes == undefined) quotes = true;
		else quotes = forceOuterQuotes;

		if ( (k == undefined) || (v == undefined) ) return false;
		if ( (k.toString().length < 1) || (v.toString().length < 1) ) return false;
    
		// is the v numeric?
		if ( (v == parseInt(v, 10)) || !quotes) {
			return k + '=' + v.toString();
		}
		else {
			return k + '=' + util.encodeSearchTerm(v, true);
		}
	},
	
	/**
	 * Takes a k=v pair and explodes it into an array
	 *
	 * @returns an array with the original k and v
	 * @param {string} userString is the string to be tested
	 */
	unmakeSearchKV: function(kv) {
		if ( (kv==undefined) || (kv.length < 1) ) return undefined;

		var colonIdx = kv.indexOf("::");
		var equalIdx = kv.indexOf("=");
		var k, v, idx, sepLen;
		var kvArr = [];

		// calc offset of separator
		if (colonIdx != -1) { sepLen = 2; idx = colonIdx;}
		else if (equalIdx != -1) { sepLen = 1; idx = equalIdx; }
		else return undefined;

		// parse out the pair
		k = kv.substring(0, idx);
		v = kv.substring(idx + sepLen);

		// if k/v isn't found, replace it with undefined
		k = (k.length > 0) ? k : undefined;
		v = (v.length > 0) ? v : undefined;

		// return what we have
		kvArr.push(k);
		kvArr.push(v);
		return kvArr;
	},

	/**
	 * Takes a string and returns T/F depending if its quoted or not.
	 *
	 * @returns True if the string is quoted, false if not
	 * @param {string} userString is the string to be tested
	 */
	isQuoted: function(userString) {
		if ( (userString == undefined) || (userString.length <= 0) ) return undefined;
		var q = "\"";
		var s = userString.trim();
		if ( s.startswith(q) && s.endswith(q) ) return true;
		else return false;
	},

	/**
	 * Takes a user entered search clause then cleans up its quoting
	 *
	 */
	quoteLiterals: function(userClause) {
		var quotables = ['=', '[', ']', '|'];
		var u = userClause.split(" ");
		for (var i = 0; i < u.length; i++) {
			D.debug("xx " + u[i] );
		}
	},

	/**
	 * Record (and submit) clickstream
	 * for now just prints to debug
	 * 
	 * @returns true on record success, false otherwise
	 * @param {mozEvent} originator is the click event
	 *
	 */
	recordClick: function(originator){
	},

	parseQueryString: function(uri) {
		var parts = uri.split('?');
		if (parts.length == 2) {
			var pairs = parts[1].split('&');
			var output = {};
			var p;
			for (var i=0,l=pairs.length; i<l; i++) {
				p = pairs[i].split('=');
				if (p.length == 2) {
					output[p[0]] = decodeURIComponent(p[1]);
				}
			}
			return output;
		} else {
			return {};
		}
	},
	
	/**
	 * Within containerElt, grabs the first <tagName> element 
	 * and focuses on it.  Does not pay attention to visibility or display 
	 * properties.
	 * @returns true if it found an element to focus on. False otherwise.
	 * @param {containerElt} HTMLElement. 
	 * @param {tagName} optional string specifying tagName.
	 *
	 */
	focusFirstField: function(containerElt, tagName) {
		if (!tagName) tagName = "INPUT";
		var inputNodes = containerElt.getElementsByTagName(tagName);
		for (var i=0; i < inputNodes.length; i++) {
			if ((tagName != "INPUT" || (inputNodes[i].type == "text")) && !inputNodes[i].disabled && !inputNodes[i].readonly) {
				try {
					inputNodes[i].focus();
				} catch(e) {
					D.error("unable to set input focus to " + inputNodes[i].name);
					return false;
				}
				D.debug("focusing on " + inputNodes[i].name);
				return true;
			}
		}
		return false;
	},
	
	/**
	 * retrieves the proper session token for use with security check
	 */
	getCurrentSessionKey: function() {
		return this.getCookie('SPLUNK_SESSION_' + util.getListeningPort());
	}
}


// shorthand for document.getElementById
function $(element) {
	return document.getElementById(element);
	/*
	var elements = new Array();

	for (var i = 0; i < arguments.length; i++) {
		var element = arguments[i];
		if (typeof element == 'string')
			element = document.getElementById(element);

		if (arguments.length == 1)
			return element;

		elements.push(element);
	}
	return elements;
	*/
}

function getElementsByClassName(node, classname)
{
    var a = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var els = node.getElementsByTagName("*");
    var lim = els.length;
    for (var i=0,j=lim; i<j; i++)
        if (re.test(els[i].className))a.push(els[i]);
    return a;
}


/**
 * Serializes the property names of an object literal into a space-delimited
 * string.
 *
 * @param {object} hash The object literal to serialize
 * @return {string} The space-delimited string of key names
 *
 */
function prop2str(hash) {
	var o = '';
	for (x in hash) {
		if(hash.hasOwnProperty(x)) o += " " + x;
	}
	return o.substring(1);
}

/**
 * Serializes the property names of an object literal into an array
 *
 * @param {object} hash The object literal to serialize
 * @return {string} The new array
 *
 */
function prop2array(hash) {
	var o = [];
	for (x in hash) {
		if(hash.hasOwnProperty(x)) o.push(x);
	}
	return o;
}

/**
 * Converts an object literal to an encoded querystring key/value string.
 *
 */
function propToQueryString(dictionary) {
	var o = [];
	var val;
	for (prop in dictionary) {
		val = '' + dictionary[prop];
		o.push(encodeURIComponent(prop) + '=' + encodeURIComponent(dictionary[prop]));
	}
	return o.join('&');
}


/**
 * Converts a flat querystring into an object literal
 *
 */
function queryStringToProp(args) {
	args = args.trim('&\?');
	
	var parts = args.split('&');
	var output = {};
	var pair;
	var lim = parts.length;
	for (var i=0,l=lim; i<l; i++) {
		pair = parts[i].split('=');
		output[pair[0]] = decodeURIComponent(pair[1]);
	}
	return output;
}


/**
 * Comopacts a normal JS object literal to a flattened oject literal that
 * uses dotted-separator notation for use with bundle confs
 *
 */
function propToConf(prop, prefix) {
	var o = {};
	prefix = prefix || ''
	_propToConfRecurse(prop, prefix, o);
	return o;
}
function _propToConfRecurse(prop, prefix, destination) {
	var name;
	for (key in prop) {
		if (prop.hasOwnProperty(key)) {
			name = (prefix ? prefix + '.' : '') + key;
			if (typeof(prop[key]) == 'string' || typeof(prop[key]) == 'number') destination[name] = clone(prop[key]);
			else _propToConfRecurse(prop[key], name, destination);
		}
	}
}


/**
 * Expands a dotted-separator object literal into a fully nested JS object
 * literal
 *
 */
function confToProp(conf) {

	var o = {};
	var cursor;
	for (key in conf) {
		if (conf.hasOwnProperty(key)) {
			keyParts = key.split('.');
			if (keyParts.length == 1) {
				o[key] = clone(conf[key]);
			} else {
				cursor = o;
				var lim = keyParts.length;
				for (var i=0,l=lim; i<l; i++) {
					var part = keyParts[i];
					if (i == l-1) {
						cursor[part] = clone(conf[key]);
					} else {
						if (!cursor.hasOwnProperty(part)) cursor[part] = {};
						cursor = cursor[part];
					}
				}
			}
		}
	}

	return o;
}


/**
 * Crops a string to a maximum length by removing characters in the middle.
 * Ex: cropString('abcdefghij', 6) ==> 'abc...hij'
 *
 * @param {String} inputText The string to crop
 * @param {int} maxLength The maximum number of characters to show
 *
 * @return {String} The cropped text
 *
 */
function cropString(inputText, maxLength) {
	
	if (!inputText || !maxLength || inputText.length <= maxLength) return inputText;

	var leftSize = Math.round(maxLength/2);
	var rightSize = maxLength - leftSize;
	
	return inputText.substring(0, leftSize) + '...' + inputText.substring(inputText.length - rightSize);
	
}


////////////////////////////////////////////////////////////////////////////////
// Missing Javascript prototype methods
////////////////////////////////////////////////////////////////////////////////

if (!String.prototype.trim) {
	// from http://blog.stevenlevithan.com/archives/faster-trim-javascript
	// profiler shows this is much faster than the previous implementation in both IE and Firefox
	String.prototype.trim = function(delim)
	{
		if (delim) return this.replace(new RegExp("^[\\s" + delim + "]+"),'').replace(new RegExp("[\\s" + delim + "]+$"), '');
		else return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
	}
}



if (!String.prototype.strip) {
	String.prototype.strip = function() {
		return this.replace(/^\s+/, '').replace(/\s+$/, '');
	}
}


/**
 * Checks if string is a member of either an array, or object literal (keys).
 *
 *
 * @param {Object} iterable The array or object literal to inspect
 * @param {Boolean} useStrictEquality Indicates whether or not to use strict 
 * 				equality comparison when inspecting arrays
 * @return {Boolean} True if string is a member, false otherwise
 *
 */
if (!String.prototype.isMemberOf) {
	String.prototype.isMemberOf = function (iterable, useStrictEquality) {
		if (!iterable) return false;
		if(iterable instanceof Array) {
			for(var i=0,l=iterable.length; i<l; i++) {
				if(useStrictEquality && this === iterable[i]) return true;
				else if(this == iterable[i]) return true;
			}
		} else {
			if(iterable.hasOwnProperty && iterable.hasOwnProperty(this)) return true;
		}
		return false;
	}
}


if (!String.prototype.startswith) {
	String.prototype.startswith = function (substring) {
		if(substring == null) throw new Error('String.startswith - substring is null');
		if(!substring) return true;
		if(this.length < substring.length) return false;
		return (this.substring(0,substring.length) == substring);
	}
}

if (!String.prototype.endswith) {
	String.prototype.endswith = function (substring) {
		if(substring == null) throw new Error('String.endswith - substring is null');
		if(!substring) return true;
		if(this.length < substring.length) return false;
		return (this.substring(this.length-substring.length) == substring);
	}
}


/**
 * Works like String.split(' '), but is aware of quoted parts; multiple spaces
 * are collapsed into 1
 *
 * @param {Boolean } preserveQuotes Indicates whether or not the split parts
 *		contain the original quotes
 *
 * @return {Array} The array of string parts, split by "
 *
 */
if (!String.prototype.quotedSplit) {
	String.prototype.quotedSplit = function (preserveQuotes) {

		// redirect non-quoted conditions
		if(
			this == ''
			|| (this.indexOf('"') == -1)
		) {
			return this.split(' ');
		}

		// step over all characters and split on
		var output = [];
		var inQuote = false;
		var part = '';
		for(var i=0,l=this.length; i<l; i++) {
			switch(this[i]) {
				case '"':
					if(inQuote) {
						inQuote = false;
						output.push(part + (preserveQuotes ? this[i] : ''));
						part = '';
					} else {
						inQuote = true;
						if(part.length) {
							output.push(part);
						}
						part = (preserveQuotes ? this[i] : '');
					}
					break;
					
				default:
					if(inQuote) {
						part += this[i];
						break;
					} else {
						if(this[i] == ' ') {
							if(part.length) output.push(part);
							part = '';
						} else {
							part += this[i];
						}
					}
			}
			
			if(i == l-1 && part) output.push(part);
		}
		
		return output;
		
	}
}



if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function (obj, fromIndex) {
		if (fromIndex == null) {
			fromIndex = 0;
		} else if (fromIndex < 0) {
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		var lim = this.length;
		for (var i = fromIndex; i < lim; i++) {
			if (this[i] === obj)
				return i;
		}
		return -1;
	};
}


/**
 * Same as Array.push(), but doesn't append if the incoming object is null
 *
 */
if (!Array.prototype.lazyPush) {
	Array.prototype.lazyPush = function (obj) {
		if(obj != null) this.push(obj);
	};
}

/**
 * Returns new array with elements from toRemove subtracted from array
 *
 */
if (!Array.prototype.subtract) {
	Array.prototype.subtract = function(toRemove) {
		var o = this.concat();
		for(var i=0,l=toRemove.length; i<l; i++) {
			for(var j=0; j<o.length; j++) {
				if(toRemove[i] == o[j]) {
					o.splice(j,1);
					j--;
				}
			}
		}
		return o;
	}
}

/**
 * Returns new array with elements from toIntersect intersected with array
 * TODO: optimize
 */
if (!Array.prototype.intersect) {
	Array.prototype.intersect = function(toIntersect) {
		var o = [];
		for(var i=0,l=toIntersect.length; i<l; i++) {
			if(this.indexOf(toIntersect[i]) > -1) o.push(toIntersect[i]);
		}
		return o;
	}
}


/**
 * Removes all elements in an array that match an object
 *
 */
if (!Array.prototype.remove) {
	Array.prototype.remove = function(toRemove) {
		for(var i=this.length-1; i>=0; i--) {
			if(this[i] == toRemove) {
				this.splice(i, 1);
			}
		}
	}
}	
	
	
/**
 * Returns the index of the first matched element
 *
 */
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function(toMatch) {
		for(var i=0,l=this.length; i<l; i++) {
			if(this[i] == toMatch) {
				return i;
			}
		}
		return -1;
	}
}


/**
 * Adds an element to the array, if element does not exist.
 *
 * @param {object} toAdd The object to add to the array.
 * @return {boolean} True if object was added; false if object already exists
 *
 */
if (!Array.prototype.add) {
	Array.prototype.add = function(toAdd) {
		if(this.indexOf(toAdd) > -1) return false;
		return this.push(toAdd);
	}
}



/**
* Iterate over the array, calling fn for each item.  From mootools-core, MIT license.
* @param {function} fn  The function to call for each item
* @param {object} bind  An object to call the function in context of
*/
if (!Array.prototype.each) Array.prototype.each = function(fn, bind)
{
  var lim = this.length;
  for (var i = 0, l = lim; i < l; i++) fn.call(bind, this[i], i, this);
}

/**
* Compare the given array against the current array.
* @param {Array} testArr Another array to test against
* @return {boolean} True of the arrays contain the same members, false otherwise.
*/
if (!Array.prototype.compare) {
    Array.prototype.compare = function(testArr) {
	if (this.length != testArr.length) return false;
	for (var i = 0; i < testArr.length; i++) {
	    if (this[i].compare) { 
		if (!this[i].compare(testArr[i])) return false;
	    }
	    if (this[i] !== testArr[i]) return false;
	}
	return true;
    }
}


/**
 * Takes 2 or more arrays and merges the values of the arrays into 1 array, with
 * an element count equal to the largest input array. Values are separated by a
 * space.
 *
 * Ex: Array.condense(['a','b','c'], ['x','y']) => ['a x', 'b y', 'c']
 *
 * @param {Array} 2 or more input arrays to condense into 1
 *
 * @return {Array} The condensed array
 *
 */
if (!Array.condense) {
	Array.condense = function(a, b) {
		var o = [];
		var arrayCount = arguments.length;
		var rowCount = 0;
	
		// get length of largest input array
		for (var i=0; i<arrayCount; i++) {
			rowCount = Math.max(rowCount, arguments[i].length);
		}
	
		var rowValue;
		for (var i=0; i<rowCount; i++) {
			rowValue = '';
			for (var j=0; j<arrayCount; j++) {
				if (arguments[j][i] != null) rowValue += (j ? ' ' : '') + arguments[j][i];
			}
			o.push(rowValue);
		}
	
		return o;
	}
}



/**
 * XSLTProcessor.fixWebKitIncludes 12/06/07 jthane
 * Current versions of WebKit don't support XSLT include or import tags.  We fix this by using XMLHttpRequests to retrieve any stylesheets
 * specified in the href attribute of nested include or import tags and insert the contained elements in the XML document instead.
 * Note: the current implementation here treats <xsl:import/> tags identically to <xsl:include/> tags - this is out of spec and may need to be changed in the future.
 * See http://www.devguru.com/Technologies/xslt/quickref/xslt_element_include.html for details on the difference between <xsl:import/> and <xsl:include/>.
 *
 * @param {XMLDocument} xsl  an XSL document to check for and fix includes in.
 * @return {undef}.  This method modifies the parameter XSL document.
 */
if (typeof XSLTProcessor != 'undefined') XSLTProcessor.prototype.fixWebKitIncludes = function(xsl)
{
  if (navigator.appVersion.indexOf('AppleWebKit') == -1) return;

  var replaceInclude = function(node)
  {
    var parent = node.parentNode;

    try
    {
      var href = util.getStaticPath() + '/xsl/' + node.getAttribute('href');
      var xhr = new XMLHttpRequest();
      xhr.open('GET', href, false);
      xhr.send(true);
      
      // first include any included includes by recursion.
      this.fixWebKitIncludes(xhr.responseXML);
	 
      $A(xhr.responseXML.documentElement.childNodes).each(function(n) {
	    n = xsl.importNode(n, true);
		parent.insertBefore(n, node);	// insert the imported stylesheet in the correct position.
      });
    }
    catch(e) { /* simply proceed if an included xsl was not found, or contained errors */ }
	
	parent.removeChild(node);	// remove the import or include node
  };
  
  $A(xsl.getElementsByTagName('include')).each(replaceInclude, this);
  $A(xsl.getElementsByTagName('import')).each(replaceInclude, this);
}

// diagnostic function to prompt a string representation of an XML object
function promptXML(xml)
{
  if (XMLSerializer)
  {
	var s = new XMLSerializer().serializeToString(xml);
	prompt('',s);
	if (console) console.log(s);
	}
}




/**
 * Parses a Splunk-issued datetime string and returns a Date() object
 *
 */
if (!Date.fromSplunkString) {
	Date.fromSplunkString = function(dateString) {
		var re = /(\d{1,2})\/(\d{1,2})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/;
		var parts = re.exec(dateString);
		if(!parts) return null;
		return new Date(parts[3], parseInt(parts[1],10)-1, parts[2], parts[4], parts[5], parts[6]);
	}
}

var $A = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}

/**
 * Black magic
 *
 */
Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}


/**
 * Returns a deep copy of an object.  Assumes that any object with toJSONString
 * is a Storage() objecct.
 *
 */
function clone(obj) {
	if(typeof(obj) != 'object') return obj;
	if(obj instanceof Array) return obj.concat();
	if(!(obj)) return obj;
	if(obj.toJSONString) {
		var o = new Storage();
	} else {
		var o = {}
	} 
	for (prop in obj) {
		//TODO. verify that recursive deep-copying isnt complete insane.
		if(obj.hasOwnProperty(prop)) o[prop] = clone(obj[prop]);
	}
	return o;
}



function getCommaFormattedNumber(nStr) {
	nStr += '';
	x = nStr.split('.');
	x1 = x[0];
	x2 = x.length > 1 ? '.' + x[1] : '';
	var rgx = /(\d+)(\d{3})/;
	while (rgx.test(x1)) {
		x1 = x1.replace(rgx, '$1' + ',' + '$2');
	}
	return x1 + x2;
}

////////////////////////////////////////////////////////////////////////////////
// Browser Detection
////////////////////////////////////////////////////////////////////////////////

// general purpose Browser detection object.
function Browser(){
	var ieRegex = /\bMSIE (\d\.\d)\b/;
	this.dom = document.getElementById?1:0;
	this.ie =  document.all?1:0;
	if (ieRegex.test(navigator.appVersion)) {
		this.ieVersion = parseFloat((navigator.appVersion.match(ieRegex)[1]));
	}
	this.mac = (navigator.appVersion.indexOf('Mac') == -1)?0:1;
	this.ua = navigator.userAgent.toLowerCase();
	this.safari = (this.ua.indexOf("safari") != -1)?1:0;
	this.opera  = (this.ua.indexOf("opera") != -1)?1:0;
}
Browser.prototype.checkSupported = function() {

	var alertString = ""
	if (!this.dom) alertString += 'Splunk requires Internet Explorer 6, Mozilla or Firefox to work properly.';
	
	//else if (this.ie) alertString += 'Splunk 3.0 beta currently does not support Internet Explorer. We recommend using Firefox. The released version of Splunk 3.0 will support IE 6 & 7.';

	else if (this.opera) alertString += 'Splunk does not support Opera yet. We support Mozilla, Netscape and Firefox browsers.';

	// TESTING SAFARI //  else if (this.safari)  alertString += 'Splunk does not support Safari yet. We support Mozilla, Netscape and Firefox browsers for Macs.';

	if (alertString != "") alert(alertString) ;

}
Browser.prototype.onLoadHandler = function() {
	if (this.mac && $("outerWrapper")) {
		var wrapperDiv = $("outerWrapper")
		util.addClass(wrapperDiv, "macCSSOverride");
	}
}

// init browser detection script
window.is = new Browser();
window.is.onLoadHandler();

/*
// maybe my memory is faulty but the usual tricks here dont seem to work, 
// and performance impact of getAttribute vs hasAttribute on mozilla seems 
// negligible so im just replacing latter with the former. -n
if (window.is.ie) {
	create an element to make sure everything is ready.
	document.createElement("DIV");
	HTMLElement.prototype.hasAttribute = function(attName) {
		if (this.getAttribute(attName)) return true;
		else return false;
	}
}
*/

////////////////////////////////////////////////////////////////////////////////
// TimeRange objects
////////////////////////////////////////////////////////////////////////////////

/**
 * General purpose object to hold both absolute and relative time ranges.
 */
function TimeRange(startTime, endTime) {
	if (startTime) this.startTime = startTime;
	else this.startTime = false;
	if (endTime)   this.endTime = endTime;
	else this.endTime   = false;
}
/**
 * returns the startTime as a JS Date object.
 *
 */
TimeRange.prototype.getStartTime = function() {
	return this.startTime;}
/**
 * returns the endTime as a JS Date object.
 *
 */
TimeRange.prototype.getEndTime = function() {
	return this.endTime;}
/**
 * Serializes the timeRange. Generally for debug statements. For human-readable purposes, use toConciseString()
 *
 */
TimeRange.prototype.toString = function() {
	var str = "";
	if (this.startTime) {
		str += this.startTime.strftime("\n" + util.getLastRefreshedTimeFormat() + ",  ");
	}
	if (this.endTime) {
		str += this.endTime.strftime(util.getLastRefreshedTimeFormat());
	}
	if (this.relativeTerm) {
		str += " " + this.relativeTerm;
	}
	return str;
}
/**
 * returns a compact, natural language description of the range.
 *
 */
TimeRange.prototype.toConciseString = function(optionalRelativePrefix) {
	var duration = this.getDuration();
    var startTime = new Date();
    var endTime = new Date();
    
    if (this.startTime) {
        if (this.startTime.valueOf) {
            startTime.setTime(this.startTime.valueOf())
        }
        
    }
    if (this.endTime) {
        if (this.endTime.valueOf) {
            endTime.setTime(this.endTime.valueOf())
            endTime.setSeconds(endTime.getSeconds()-1);
        }
        
    }
    			
    
	var relativePrefixStr = (optionalRelativePrefix)? optionalRelativePrefix : "in the past";
	
	var valueUnitArr = this.getRelativeValueAndUnit();
	// duration is -1 in both relative modes, 
	// or when only one absolute endpoint is set, or in 'ALL_TIME' mode.
	if ((duration == -1)) {
		if (this.startTime && !this.endTime) {
			return startTime.strftime("since %l:%M:%S %p %B %e %Y");
		}
		else if (!this.startTime && this.endTime) {
			return endTime.strftime("before %l:%M:%S %p %B %e %Y");
		}
		else if (!valueUnitArr[0] && !valueUnitArr[1]) {
			return "over all time"
		}
		else if (valueUnitArr[0] == 1) {
			return relativePrefixStr + " " + valueUnitArr[1];
		}
		else {
			return relativePrefixStr + " " + valueUnitArr[0] + " " + valueUnitArr[1] + "s";
		}
	}
	else {
		var zipperTogetherUpTo  = false;
		//zipperTogetherUpTo = second  | minute | hour | day | month
		var rangeIsSingleUnitOf = false;
		//rangeIsSingleUnit = second | minute | hour | day | month
		var rangeIsIntegerUnitsOf = false;

		if (startTime.getFullYear() == endTime.getFullYear()) {
			zipperTogetherUpTo = "year";
			if (startTime.getMonth() == endTime.getMonth()) {
				//D.debug("toConciseString - A")
				zipperTogetherUpTo = "month";
				// within one day
				if (startTime.getDate() == endTime.getDate()) {
					//D.debug("toConciseString - B")
					zipperTogetherUpTo = "day";
					// within one hour
					if (startTime.getHours() == endTime.getHours()) {
						//D.debug("toConciseString - C")
						zipperTogetherUpTo = "hour";
						// within one minute
						if (startTime.getMinutes() == endTime.getMinutes()) {
							//D.debug("toConciseString - D")
							zipperTogetherUpTo = "minute";
							// ONE SINGLE SECOND
							if (startTime.getSeconds() == endTime.getSeconds()) {
								// this is redundant case to set this var, but lets do it anyway for consistency
								//D.debug("toConciseString - E")
								zipperTogetherUpTo = "second"
								// No further analysis is necessary as the timeline only goes down to seconds.
								rangeIsSingleUnitOf = "second"
							}
							// x number of seconds within 1 minute, x > 1
							else {
								// ONE SINGLE MINUTE
								if (startTime.getSeconds()==0 && endTime.getSeconds() == 59) {
									//D.debug("toConciseString - F")
									rangeIsSingleUnitOf = "minute"
								}
								else {
									//D.debug("toConciseString - G")
									rangeIsIntegerUnitsOf = "second"
								}
							}
						}
						else {
							// ONE SINGLE HOUR
							if (startTime.getMinutes()==0 && endTime.getMinutes() == 59) {
								//D.debug("toConciseString - H")
								rangeIsSingleUnitOf = "hour"
							}
							else if (startTime.getSeconds()==0 && endTime.getSeconds() == 59) {
								//D.debug("toConciseString - I")
								rangeIsIntegerUnitsOf = "minute";
							}
						}
					}
					else {
						// ONE SINGLE DAY
						if (startTime.getHours()==0 && endTime.getHours() == 23) {
							//D.debug("toConciseString - J")
							rangeIsSingleUnitOf = "day"
						}
						else if (startTime.getMinutes() == 0 && endTime.getMinutes() == 59) {
							//D.debug("toConciseString - K")
							rangeIsIntegerUnitsOf = "hour";
						}
					}
				}
				else {
					// ONE SINGLE MONTH
					if (startTime.getDate()==1 && util.isLastDayOfMonth(endTime)) {
						//D.debug("toConciseString - L")
						rangeIsSingleUnitOf = "month"
					}
					else if (startTime.getHours() == 0 && endTime.getHours() == 23) {
						//D.debug("toConciseString - M")
						rangeIsIntegerUnitsOf = "day"
					}
				}
			}
			//same year, not same month.
			else {
				//integer number of months, bounded by a single year.
				if (startTime.getDate()==1 && util.isLastDayOfMonth(endTime)) {
					rangeIsIntegerUnitsOf = "month"
				}
			}
		}
		if (rangeIsSingleUnitOf && ! rangeIsIntegerUnitsOf) rangeIsIntegerUnitsOf = rangeIsSingleUnitOf;

		//D.debug("toConciseString - rangeIsSingleUnitOf " + rangeIsSingleUnitOf);
		//D.debug("toConciseString - rangeIsIntegerUnitsOf " + rangeIsIntegerUnitsOf);
		//D.debug("toConciseString - zipperTogetherUpTo " + zipperTogetherUpTo);
		/* step 1
			easy cases of range = 1 exact day, 1 exact hour, etc...
		*/
		var startTimeFormatStr = false;
		var endTimeFormatStr = false;
		switch (rangeIsSingleUnitOf) {
			case "month" :
				startTimeFormatStr = "during %B %Y";
				break;
			case "day" : 
				startTimeFormatStr = "on %A %B %e %Y";
				break;
			case "hour" :
				startTimeFormatStr = "at %l %p on %A %B %e %Y";
				break;
			case "minute" :
				startTimeFormatStr = "at %l:%M %p %A %B %e %Y";
				break;
			case "second" :
				startTimeFormatStr = "at %l:%M:%S %p on %A %B %e %Y";
				break;
			default : 
				/*  step 2 harder weirder corner cases where the range satisfies 
				  a)  it is an integer number of X where x is months | days | hours | minutes | seconds
				  b)  the range does not span a boundary of X's parent Y.  
				*/
				switch (rangeIsIntegerUnitsOf) {
					case "month" :
						startTimeFormatStr = "%B";
						endTimeFormatStr   = " through %B %e %Y"
						break;
					case "day" :
						startTimeFormatStr = "%B %e";
						endTimeFormatStr   = " through %B %e %Y"
						break;
					case "hour" :
						startTimeFormatStr = "%l %p";
						endTimeFormatStr   = " through %l %p %A %B %e %Y";
						break;
					case "minute" :
						startTimeFormatStr = "%l:%M %p";
						endTimeFormatStr   = " through %l:%M:%S %p on %A %B %e %Y";
						break;
					// "second" case is useless, redundant, confusing, you name it.  We fall through to step 3
					//case "second" : 
					//	startTimeFormatStr = "from %H:%M:%S %p"
					//  endTimeFormatStr   = " through %H:%M:%S %p on %B %e %Y";
					default :
						switch (zipperTogetherUpTo) {
							// redundant, nonsense case.
							//case "second" :
							//	return startTime.strftime("between %H:%M  and ") +  endTime.strftime("%H:%M on %B %e %Y");
							case "year" :
							case "month" :
								startTimeFormatStr = "between %l:%M:%S %p %B %e and ";
								endTimeFormatStr   = "%l:%M:%S %p %B %e %Y";
								break;
							case "day" : 
							case "hour" :
							case "minute" :
								startTimeFormatStr = "between %l:%M:%S %p and ";
								endTimeFormatStr   = "%l:%M:%S %p on %A %B %e %Y";
								break;
							//total fallback. No special cases detected.  Print times with full precision.
							default : 
								startTimeFormatStr = "between %l:%M:%S %p %B %e %Y and ";
								endTimeFormatStr   = "%l:%M:%S %p %B %e %Y";
						}
				}
		}
		
		var isEuropeanDateFormat = (window.SEARCH_RESULTS_TIME_FORMAT.indexOf("%d") < window.SEARCH_RESULTS_TIME_FORMAT.indexOf("%m"));
		//D.debug("xxe global.js " + isEuropeanDateFormat);
		if (isEuropeanDateFormat) {
			startTimeFormatStr = startTimeFormatStr.replace("%B %e", "%e %B");
			if (endTimeFormatStr) endTimeFormatStr   = endTimeFormatStr.replace("%B %e", "%e %B");
		}

  
		if (endTimeFormatStr) {
			return startTime.strftime(startTimeFormatStr)  + endTime.strftime(endTimeFormatStr);
		}
		else {
			return startTime.strftime(startTimeFormatStr);
		}
	}
}
/**
 * public method useful for the moment. Used to split the relative term 
 * into a value and a unit.  Obviously wont generalize well to 
 * relativeTerm properties that are more than one term.
 *
 */
TimeRange.prototype.getRelativeValueAndUnit = function() {
	if (!this.relativeTerm) {
		return false
	}
	var unit  = false;
	var value = false;
	var unitsToTestFor = ["seconds", "minutes", "hours", "days", "months"]
	
  var lim = unitsToTestFor.length;
	for (var i=0;i<lim;i++) {
		var u = unitsToTestFor[i];
		if (this.relativeTerm.indexOf(u)!=-1) {
			unit = u.substring(0, u.length-1);
		}
	}
	try {
		if (this.relativeTerm.indexOf("=") != -1) {
			value = this.relativeTerm.substring(this.relativeTerm.indexOf("=")+1);
		} else {
			value = this.relativeTerm.substring(this.relativeTerm.indexOf("::")+2);
		}
	}
	catch (e) { 
		D.error("error within TimeRange. Failed to extract relative term value");
		return false;
	}
	value = value.trim('"');
	value = value.trim("'");
	return [value,unit];
}
/**
 * attempts to return the number of minutes elapsed in this TimeRange.  
 * NOTE: for relative values, this implementation is a rough approximation, 
 * treating all months as 30 days, ignoring leap years, DST, etc..
 */
TimeRange.prototype.getLengthInMinutes = function() {
	if (this.relativeTerm) {
		var valueUnitArr = this.getRelativeValueAndUnit();
		var approxRangeMinutes = parseInt(valueUnitArr[0], 10);
		switch (valueUnitArr[1]) {
			case "month" :
				approxRangeMinutes *= 30;
			case "day" :
				approxRangeMinutes *= 24;
			case "hour" :
				approxRangeMinutes *= 60;
			case "minute" :
				break;
			case "second" : 
				approxRangeMinutes = approxRangeMinutes / 60;
		}
		return approxRangeMinutes;
	} else if (this.startTime && this.endTime) {
		var exactRangeInMinutes = this.endTime.getTime() - this.startTime.getTime() / 60;
		return exactRangeInMinutes;
	}
	return NaN;
}
/**
 * very useful method, that is given a REFERENCE to a termlist. 
 * It modifies the list in place, pulling out time terms that it finds, 
 * and using them to populate itself. 
 * Returns true if the given terms contained any time terms. false otherwise.
 */
TimeRange.prototype.absorbTimeTerms = function(termList) {
	var relativeTerms = [];
	var lengthBefore = termList.length;
	for (var i=termList.length-1; i>=0; i--) {
		var term = termList[i];
		if (term.match(util.re.endTimeTerm)) {
			var endDate = term.trim().replace(/(endtime|=|::|\")/g, "");
			
			// inject a colon to dates with spaces to be compatible with search
			endDate = endDate.replace(/(\d+\/\d\d+\/\d+)\s/, '$1:');

			this.endTime = Date.parseDate(endDate, window.SEARCH_TERM_TIME_FORMAT);
			termList.splice(i,1);
		}
		else if (term.match(util.re.startTimeTerm)) {
			var startDate = term.trim().replace(/(starttime|=|::|\")/g, "");

			// inject a colon to dates with spaces to be compatible with search
			startDate = startDate.replace(/(\d+\/\d\d+\/\d+)\s/, '$1:');

			this.startTime = Date.parseDate(startDate, window.SEARCH_TERM_TIME_FORMAT);
			//D.debug("xxt timeline start " + startDate + " - " + term.match(util.re.startTimeTerm)[0]);
			termList.splice(i,1);
		}
		else if ( (term.substr(0,5) != "start") && (term.substr(0,3) != "end") ) {
			//because of limitations with js regex, this is a 2 stage test, the first not using the regex.
			if (term.match(util.re.legacyRelTimeTerm)) {
				//alert("legacy relative time term matched.");
				relativeTerms.push(term.replace('::','='));
				termList.splice(i,1)
			}
		}
		// until we can actually widgetize endtimes and durations throughout the system, lets leave them alone.
		//else if (term.match(util.re.relStartTimeTerm) || term.match(util.re.relEndTimeTerm) || term.match(util.re.durationTerm)) {
		else if (term.match(util.re.relStartTimeTerm)) {
			relativeTerms.push(term.replace('::','='));
			termList.splice(i,1)
		}
	}
	if (relativeTerms.length > 0) {
		this.relativeTerm = relativeTerms.join(" ");
	}
	
	return lengthBefore != termList.length;
}
/**
 * Copies a new TimeRange instance and returns.  Useful for when you 
 * want to pass a TimeRange property, but you dont entirely trust the 
 * receiver to leave it unchanged. 
 */
TimeRange.prototype.copy = function() {
	//var previousStr = this.toString();
	var range = new TimeRange(this.startTime, this.endTime);
	if (this.relativeTerm) range.relativeTerm = this.relativeTerm;
	//if (previousStr != range.toString()) D.error("TimeRange.copy - something went horribly wrong");
	return range;
}
/**
 * returns true if this timeRange contains the given date.  For relative 
 * timeranges this will not be entirely accurate. 
 * For instance it assumes currently that there are 30 days in every month. 
 */
TimeRange.prototype.containsTime = function(dateObj) {
	var startTime = this.getStartTime();
	var endTime   = this.getEndTime();
	if (this.relativeTerm) {
		var endTime = new Date();
		var currentRangeLengthInMS = this.getLengthInMinutes() * 60000;
		startTime = new Date();
		startTime.setTime(endTime.getTime() - currentRangeLengthInMS);
	// if no relativeTerm and no startTime endTime, then this is all_time. 
	} else if (!startTime && !endTime) return true;
	return (startTime <= dateObj  && endTime >= dateObj);
}
/**
 * returns true if the given range is contained by or equal to this instance.  
 * In the case where this is a relative range, it will always assume that it contains 
 * absolute ranges.  Because of how the timeline is implemented, this never seems to 
 * come up as a shortcoming.  
 * TODO - fix using this.containsTime
 */
TimeRange.prototype.containsRange = function(range) {
		// if this is a range over all time, then it contains all time ranges. (including other "all time" ranges )
		// All time contains everybody, including other All Time
		if (this.isAllTime()) return true;
		// Nobody can contain All Time, except others which hit the case above.
		if (range.isAllTime()) return false;
		// if these ranges are identical, then true.
		if (this.equalToRange(range)) return true;
		
		// Currently relative ranges will ALWAYS claim to contain absolute ranges.
		if (this.relativeTerm  && !range.relativeTerm) return true;
		// trivial but expensive case of them being identical.  happens a lot. 
		if (this.getSearchTerms().join(" ") == range.getSearchTerms().join(" ")) return true;
		
		if (this.getStartTime() > range.getStartTime())  return false;
		else if (this.getEndTime() < range.getEndTime()) return false;
		
		return true;
	try {}
	catch(e) {
		D.error("exception in TimeRange.containsRange\nthrown by \nrange=" + range.toString() + "\nthis=" + this.toString());
		return false;
	}
}
/**
 * returns true if the given range is exactly equal to this instance.
 */
TimeRange.prototype.equalToRange = function(range) {
	if (this===range) return true;
	if (this.relativeTerm != range.relativeTerm) return false;

	if (typeof(this.startTime) != typeof(range.startTime)) return false;
	if (typeof(this.endTime) != typeof(range.endTime)) return false;
	
	var rangeStartTime = range.getStartTime();
    
    if (this.startTime && rangeStartTime &&  this.startTime.getTime() != rangeStartTime.getTime()) return false;

	var rangeEndTime   = range.getEndTime();
    if (this.endTime && rangeEndTime && this.endTime.getTime() != rangeEndTime.getTime()) return false;
	return true;
}

/** 
 * @returns -1 if the timeRange isnt governed by a relative searchTerm at all. 
 * Otherwise an integer representing the number of milliseconds between startTime and endTime.
 */
TimeRange.prototype.getDuration = function() {
	if (this.relativeTerm) return -1;
	else if (this.endTime && this.startTime) {
		return this.endTime - this.startTime;
	}
	else return -1;
}
TimeRange.prototype.isAllTime = function() {
    return (this.endTime || this.startTime || this.relativeTerm) ? false : true;
}
/** 
 * @returns the starttime:: and endtime:: search terms that match the object's time values.
 */
TimeRange.prototype.getSearchTerms = function() {
	var timeTerms = [];
	if (this.relativeTerm) timeTerms.push(this.relativeTerm);
	else {
		if (this.startTime) timeTerms.push(util.makeSearchKV("starttime", this.startTime.strftime(window.SEARCH_TERM_TIME_FORMAT) ) );
		if (this.endTime) timeTerms.push(util.makeSearchKV("endtime", this.endTime.strftime(window.SEARCH_TERM_TIME_FORMAT) ) );
	}
	//D.debug("TimeRange.getSearchTerms - returning " + timeTerms);
	return timeTerms;
}

/**
 * This stub enables persistence via JSON
 *
 */
TimeRange.prototype.toJSONString = function() {
	
	return this.getSearchTerms().toJSONString();
}



////////////////////////////////////////////////////////////////////////////////
// Hashtable object
////////////////////////////////////////////////////////////////////////////////

function Hash() {
	this._hash = {};
}

Hash.prototype = {
	get: function(key) {
		if(this._hash.hasOwnProperty(key)) return this._hash[key];
		else return null;
	},
	set: function(key, value) {
		this._hash[key] = value;
	},
	remove: function(key) {
		return (delete this._hash[key]);
	},
	containsKey: function(key) {
		return this._hash.hasOwnProperty(key);
	},
	keys: function() {
		var o = [];
		for (var x in this._hash) {
			if(this._hash.hasOwnProperty(x)) o.push(x);
		}
		return o;
	}
}
Hash.prototype.put = Hash.prototype.set;
Hash.prototype.getKeys = Hash.prototype.keys;


/**
 * This is an alternate version of Hash(), which is JSON compatible; we probably
 * should switch everything over to this soon.
 *
 */
function Storage() {
}
Storage.prototype.append = function(objectLiteral) {
	for(key in objectLiteral) {
		this[key] = objectLiteral[key];
	}
}
Storage.prototype.toJSONString = function () {
    var a = ['{'],  // The array holding the text fragments.
        b,          // A boolean indicating that a comma is required.
        k,          // The current key.
        v;          // The current value.

    function p(s) {

// p accumulates text fragment pairs in an array. It inserts a comma before all
// except the first fragment pair.

        if (b) {
            a.push(',');
        }
        a.push(k.toJSONString(), ':', s);
        b = true;
    }

// Iterate through all of the keys in the object, ignoring the proto chain.

    for (k in this) {
        if (this.hasOwnProperty(k)) {
            v = this[k];
            switch (typeof v) {

// Serialize a JavaScript object value. Ignore objects that lack the
// toJSONString method. Due to a specification error in ECMAScript,
// typeof null is 'object', so watch out for that case.

            case 'object':
                if (v) {
                    if (typeof v.toJSONString === 'function') {
                        p(v.toJSONString());
                    }
                } else {
                    p("null");
                }
                break;

            case 'string':
            case 'number':
            case 'boolean':
                p(v.toJSONString());

// Values without a JSON representation are ignored.

            }
        }
    }

// Join all of the fragments together and return.

    a.push('}');
    return a.join('');
};

////////////////////////////////////////////////////////////////////////////////
// Update checker functions
////////////////////////////////////////////////////////////////////////////////

// return true if it tried to create a scriptlet based on the url found in search.xml
// return false if no url was found. 
function createUpdateCheckerScriptlet(checkerLocation) {
	if (!$("footer")) return false;
	var scriptlet = document.createElement("SCRIPT");
	scriptlet.type = "text/javascript";
	bannerURLSegments = [];
	// 'pro' | 'free'
	bannerURLSegments[0] = (util.isOfClass($("outerWrapper"),"proVersion")) ? "pro" : "free";
	// '2.1b2' | '2,0' etc.. 
	bannerURLSegments[1] = $("footer").getAttribute("versionNumber");
	// 'home' | 'login'
	bannerURLSegments[2] = checkerLocation
	// 'prod' | 'trial'
	bannerURLSegments[3] = $("footer").getAttribute("installType");
	// 'basic' | 'black' | various user created skins..
	bannerURLSegments[4] = skinManager.getActiveSkinTitle();
	var updateCheckerBaseURL = "";
	try {
		var serviceURLContainer = $("serviceURLs");
		updateCheckerBaseURL = serviceURLContainer.getAttribute("updateCheckerBaseURL");
	}
	catch (e) {
		D.warn("could not find updateCheckerBaseURL");
	}
	// only send the updateChecker to the url if it IS a url.  If it's '0'  or 'no', send nothing.
	if ((updateCheckerBaseURL.indexOf("http://")!=-1) || (updateCheckerBaseURL.indexOf("https://")!=-1)) {
		scriptlet.src = updateCheckerBaseURL + bannerURLSegments.join("/");	
		document.getElementsByTagName('head')[0].appendChild(scriptlet);
		return true;

	}
	else {
		D.debug("user has disabled the updateChecker by setting updateCheckerBaseURL to " + updateCheckerBaseURL);
		if (window.cannotConnectTimeout) clearTimeout(window.cannotConnectTimeout);
		return false;
	}
}


function checkForUpdateServerResponse() {
	var updateCheckerIframe = $("updateChecker")
	if ($("bannerContainer").style.display == "block") {
		// this means the quickdraw js request succeeded, so quickdraw has the ball now. we're done. it'll call displaySpot, which will point the iframe at quickdraw
		return false;
	}
    else showCannotConnectToSplunkComMessage();
}
function showCannotConnectToSplunkComMessage() {
    var updateCheckerIframe = $("updateChecker");
	$("bannerContainer").style.display = "block";
	updateCheckerIframe.src = updateCheckerIframe.getAttribute("failureSrc");
	updateCheckerIframe.style.visibility = "visible";
	updateCheckerIframe.style.height = "95px";
	updateCheckerIframe.style.width  = "728px";
	updateCheckerIframe.style.frameborder = "0";
}

function initUpdateChecker(){
    // we need there to be a skinManager singleton loaded, because we send the skin with the updateChecker request. 
	window.skinManager = new SkinManager();
	var product = ((util.isOfClass($("outerWrapper"),"proVersion")) ? "pro" : "free");
	if (product=="pro") document.loginForm.usr.focus();
	
    if (createUpdateCheckerScriptlet("login")) {
        window.cannotConnectTimeout = setTimeout('checkForUpdateServerResponse()', 5000);
	}
    // if createUpdateCheckerScriptlet returns false it means the user has disabled it. Not an error. 
    return true;
}



////////////////////////////////////////////////////////////////////////////////
// Debug/timing functions
////////////////////////////////////////////////////////////////////////////////

// init the logging level
if(util.getCookie('LOG_LEVEL')) {
	window.LOG_LEVEL = parseInt(util.getCookie('LOG_LEVEL'), 10);
} else {
	window.LOG_LEVEL = 10;
}

// init html debug popup window
if(util.getCookie('LOG_POPUP')) {
	window.LOG_POPUP = true;
}

var gTimingStartedAt = new Date().getTime();

/**
 * Internal. Outputs a debug message into a popup window.
 *
 * Usage: call setPopupLogger() to enable this popup window (is persisted via cookie)
 *
 */
function debugMsg(msg, timingArgument){
	
	if(!window.LOG_POPUP) return;
	
	var now = new Date();
	var hours = now.getHours();
	var minutes = now.getMinutes();
	var seconds = now.getSeconds();
	var ms = now.getMilliseconds();
	
	
	if (!top.debugWindow || top.debugWindow.closed){	
		top.debugWindow = window.open("", "debugWindow", 'scrollbars, resizable, width=800,height=700');
    
    if (!top.debugWindow && (typeof('messageController') != 'undefined')) // a popup blocker is blocking the debug window
    {
      messageController.setNotification(1, 'WARNING: the debug window was stopped by a popup blocker.  To see debug messages, please disable the popup blocker.', true);
      return;
    }
    
		top.debugWindow.document.write('<div style="position:fixed;top:0;left:0;z-index:10;width:100%;height:18px;font:11px sans-serif;background:#eee;padding:4px"><a href="javascript:document.write(\'<br /><hr /><br />\');window.scrollBy(0,100 )">Mark</a></div><p>&nbsp;</p><pre>');
		//top.debugWindow.moveTo(0,0);
	}

	var duration = -1;
	if (timingArgument == "startTiming") {
		gTimingStartedAt = now;
	}
	

	top.debugWindow.document.write('<span style="font-family:Monaco,Courier Bold,Courier New,Courier,monospace; color: #444; font-size: 10px;">');
	top.debugWindow.document.write('<br />[' +  hours + ':' + minutes + ':' + seconds + '.' + ms + '] ');
	
  if (msg.indexOf('<?xml') > -1)
  {
    // if it's XML, log it so we can actually read it.
    top.debugWindow.document.write( '<textarea style="font: 10px arial; border: 1px solid red; width: 100%; height: 120px;">' + msg.replace(/\>/g, '&gt;').replace(/\</g, '&lt;') + '</textarea><br />' );
  }
  else
  {
    top.debugWindow.document.write( msg );
  }
  
  
	if (timingArgument == "endTiming") {
		top.debugWindow.document.write(" -- TOOK " + (now.getTime() - gTimingStartedAt) + " ms");
		gTimingStartedAt = now.getTime();
	}
	top.debugWindow.document.write('</span>');
	
	top.debugWindow.scrollBy( 0,100 );

	if (!window.justFocused){
		//top.debugWindow.focus();
		//top.debugWindow.opener.focus();
		window.justFocused = true;
		window.setTimeout('window.justFocused=false', 1000);
	}
	return true;
}

/**
 * Outputs messages to the logging framework.  This is meant to be used with the
 * Firebug extension for Firefox.
 *
 */
var D = {
	msg: function(level, m, t) {
		if (window.console) {
			try {
			    if (window.console.debug) eval("window.console." + level.trim() + "(m);");
			} catch (e) {
			    // this error is weird
			    //alert(e.text);
			}
			//else if(window.console.log && typeof(m) == 'string') console.log(level.toUpperCase() + ' ' + m.replace(/\n+/g, ' '));
		}
		return debugMsg(level.toUpperCase() + '  ' + m,t);
	},
    debug:  function(m,t) { if(window.LOG_LEVEL>0) return false; return this.msg('debug  ', m,t); },
    debugj: function(m,t) { if(window.LOG_LEVEL>5) return false; return this.msg('debug  ', m,t); },
    info:   function(m,t) { if(window.LOG_LEVEL>10) return false; return this.msg('info  ', m,t); },
    warn:   function(m,t) { if(window.LOG_LEVEL>20) return false; return this.msg('warn  ', m,t); },
    error:  function(m,t) { if(window.LOG_LEVEL>30) return false; return this.msg('error  ', m,t); },
	group: function(m) { if(window.console && window.console.debug) window.console.group(m); },
	groupEnd: function(m) { if(window.console && window.console.debug) window.console.groupEnd(); },
    r:      function(m,t) {
	    var o = "[Property listing]\n";
    	for (var i in m) o += "\t" + i + " => " + m[i] + "\n";
    	if (window.console) console.debug(o);
    	return debugMsg('DEBUG  ' + o,t);
    },
    a:      function(m,t) {
	    var o = "[Array listing]\n";
    	for (var i=0,l=m.length;i<l;i++) o += "\t" + i + " => " + m[i] + "\n";
    	if (window.console) console.debug(o);
    	return debugMsg('DEBUG  ' + o,t);
    }
}

/**
 * Sets the logging level (via Firebug) for the current browser.  Logging levels
 * are as follows:
 * 0 = DEBUG
 * 5 = DEBUG (jv)
 * 10 = INFO
 * 20 = WARN
 * 30 = ERROR
 *
 */
function setLoggingLevel(level) {
	if(isNaN(parseInt(level, 10))) return false;
	window.LOG_LEVEL = level;
	util.setCookie('LOG_LEVEL', level, 1000);
	return level;
}


/**
 * Turns the popup debug window on and off
 */
function setPopupLogger(showPopup, skipSettingCookie) {
	if(showPopup) {
		window.LOG_POPUP = true;
		if (!skipSettingCookie) util.setCookie('LOG_POPUP', '1', 1000);
		return true;
	} else {
		window.LOG_POPUP = false;
		if (!skipSettingCookie) util.setCookie('LOG_POPUP', '0', -1000);
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Window-level event handler functions
////////////////////////////////////////////////////////////////////////////////

var mouseWheelScroller  = function(mozEvent) {
	var st = scrollingPanelManager.resultsScroller.scrollTop + (mozEvent.detail * 12);
	scrollingPanelManager.resultsScroller.scrollTop = st < 0 ? 0 : st;
	mozEvent.preventDefault();
	if (window.popupLayerManager) window.popupLayerManager.closeAll();
	if (document.body.scrollTop != 0) document.body.scrollTop = "0px";
}


////////////////////////////////////////////////////////////////////////////////
// Global window onload/onunload hooks
////////////////////////////////////////////////////////////////////////////////

/**
 * Holds functions to run global onload
 *
 */
window.onloads = new Array();

/**
 * Adds an onload action to the global window onload event
 *
 */
window.addOnload = function(somethingToDo) {
	window.onloads[window.onloads.length] = somethingToDo;
}

/**
 * Links the custom window.onloads array into the global window.onload
 * event handler.
 *
 * this could be made to respect embedded onload handlers on the body tag, but 
 * currently it will not. It will overwrite them. 
 *
 */
window.onload = function() {
  var lim = window.onloads.length;
	for (var i=0; i<lim; i++) {
		//D.debug("RUNNING ONLOAD FOR " + window.onloads[i]);
		eval(window.onloads[i]);
	}
	return true
}




/**
 * Collect methods to fire when window is resized
 *
 */
window.onresizes = new Array();
window.addOnresize = function(somethingToDo) {
	window.onresizes[window.onresizes.length] = somethingToDo;
}
window.onresize= function() {
	var lim = window.onresizes.length;
	for (var i=0; i<lim; i++) {
		eval(window.onresizes[i]);
	}
}

////////////////////////////////////////////////////////////////////////////////
// environment init functions
////////////////////////////////////////////////////////////////////////////////

/**
* Alias attachEvent(IE5+) as addEventListener(DOM-2).
*/
if (!document.addEventListener && document.attachEvent)
document.addEventListener = function(type, listener, useCapture){this.attachEvent("on"+type, listener);};

/**
* Alias detachEvent(IE5+) as removeEventListener(DOM-2).
*/
if (!document.removeEventListener && document.detachEvent)
document.removeEventListener = function(type, listener, useCapture){this.detachEvent("on"+type, listener);};



////////////////////////////////////////////////////////////////////////////////
// Event attachment/detachment methods
////////////////////////////////////////////////////////////////////////////////

// credit to John Resig, illustrious winner of addEvent, removeEvent contest.  (this is his winning entry)
function addEvent (obj, type, fn ) {
	if (obj) {
	    if (obj.addEventListener)
		    obj.addEventListener( type, fn, false );
	    else if (obj.attachEvent) {
		    obj["e"+type+fn] = fn;
		    obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
		    obj.attachEvent( "on"+type, obj[type+fn] );
	    }
	}
}
function removeEvent (obj, type, fn) {
	if (obj) {
	    if (obj.removeEventListener)
		    obj.removeEventListener( type, fn, false );
	    else if (obj.detachEvent && obj[type+fn]) {
		    obj.detachEvent( "on"+type, obj[type+fn] );
		    obj[type+fn] = null;
		    obj["e"+type+fn] = null;
	    }
	}
}


////////////////////////////////////////////////////////////////////////////////
// JSON - this is the reference implementation from JSON.org, without the
// object.prototype.toJSONString method
////////////////////////////////////////////////////////////////////////////////

Array.prototype.toJSONString = function () {
    var a = ['['],  // The array holding the text fragments.
        b,          // A boolean indicating that a comma is required.
        i,          // Loop counter.
        l = this.length,
        v;          // The value to be stringified.

    function p(s) {

// p accumulates text fragments in an array. It inserts a comma before all
// except the first fragment.

        if (b) {
            a.push(',');
        }
        a.push(s);
        b = true;
    }

// For each value in this array...

    for (i = 0; i < l; i += 1) {
        v = this[i];
        switch (typeof v) {

// Serialize a JavaScript object value. Ignore objects thats lack the
// toJSONString method. Due to a specification error in ECMAScript,
// typeof null is 'object', so watch out for that case.

        case 'object':
            if (v) {
                if (typeof v.toJSONString === 'function') {
                    p(v.toJSONString());
                }
            } else {
                p("null");
            }
            break;

// Otherwise, serialize the value.

        case 'string':
        case 'number':
        case 'boolean':
            p(v.toJSONString());

// Values without a JSON representation are ignored.

        }
    }

// Join all of the fragments together and return.

    a.push(']');
    return a.join('');
};
Array.prototype.dedupe = function() {
	var tmp = new Array();
	for (i=0;i<this.length;i++){
		if (tmp.indexOf(this[i])==-1){
			tmp[tmp.length]=this[i];
		}
	}
	return tmp;
}

Boolean.prototype.toJSONString = function () {
    return String(this);
};


Date.prototype.toJSONString = function () {

// Ultimately, this method will be equivalent to the date.toISOString method.

    function f(n) {

// Format integers to have at least two digits.

        return n < 10 ? '0' + n : n;
    }

    return '"' + this.getFullYear() + '-' +
            f(this.getMonth() + 1) + '-' +
            f(this.getDate()) + 'T' +
            f(this.getHours()) + ':' +
            f(this.getMinutes()) + ':' +
            f(this.getSeconds()) + '"';
};


Number.prototype.toJSONString = function () {

// JSON numbers must be finite. Encode non-finite numbers as null.

    return isFinite(this) ? String(this) : "null";
};


(function (s) {

// Augment String.prototype. We do this in an immediate anonymous function to
// avoid defining global variables.

// m is a table of character substitutions.

    var m = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    };


    s.parseJSON = function (filter) {

// Parsing happens in three stages. In the first stage, we run the text against
// a regular expression which looks for non-JSON characters. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we will reject all
// unexpected characters.

        try {
            if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
                    test(this)) {

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                var j = eval('(' + this + ')');

// In the optional third stage, we recursively walk the new structure, passing
// each name/value pair to a filter function for possible transformation.

                if (typeof filter === 'function') {

                    function walk(k, v) {
                        if (v && typeof v === 'object') {
                            for (var i in v) {
                                if (v.hasOwnProperty(i)) {
                                    v[i] = walk(i, v[i]);
                                }
                            }
                        }
                        return filter(k, v);
                    }

                    j = walk('', j);
                }
                return j;
            }
        } catch (e) {

// Fall through if the regexp test fails.

        }
        throw new SyntaxError("parseJSON");
    };


    s.toJSONString = function () {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can simply slap some quotes around it.
// Otherwise we must also replace the offending characters with safe
// sequences.

        if (/["\\\x00-\x1f]/.test(this)) {
            return '"' + this.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                var c = m[b];
                if (c) {
                    return c;
                }
                c = b.charCodeAt();
                return '\\u00' +
                    Math.floor(c / 16).toString(16) +
                    (c % 16).toString(16);
            }) + '"';
        }
        return '"' + this + '"';
    };
})(String.prototype);


 

// @name      The Fade Anything Technique
// @namespace http://www.axentric.com/aside/fat/
// @version   1.0-RC1
// @author    Adam Michela
var Fat = {
	make_hex : function (r,g,b) 
	{
		r = r.toString(16); if (r.length == 1) r = '0' + r;
		g = g.toString(16); if (g.length == 1) g = '0' + g;
		b = b.toString(16); if (b.length == 1) b = '0' + b;
		return "#" + r + g + b;
	},
	fade_all : function ()
	{
		var a = document.getElementsByTagName("*");
		for (var i = 0; i < a.length; i++) 
		{
			var o = a[i];
			var r = /fade-?(\w{3,6})?/.exec(o.className);
			if (r)
			{
				if (!r[1]) r[1] = "";
				if (o.id) Fat.fade_element(o.id,null,null,"#"+r[1]);
			}
		}
	},
	fade_element : function (id, fps, duration, from, to) 
	{
		if (!fps) fps = 30;
		if (!duration) duration = 3000;
		if (!from || from=="#") from = "#fff5bf";
		if (!to) to = this.get_bgcolor(id);
		
		var frames = Math.round(fps * (duration / 1000));
		var interval = duration / frames;
		var delay = interval;
		var frame = 0;
		
		if (from.length < 7) from += from.substr(1,3);
		if (to.length < 7) to += to.substr(1,3);
		
		var rf = parseInt(from.substr(1,2),16);
		var gf = parseInt(from.substr(3,2),16);
		var bf = parseInt(from.substr(5,2),16);
		var rt = parseInt(to.substr(1,2),16);
		var gt = parseInt(to.substr(3,2),16);
		var bt = parseInt(to.substr(5,2),16);
		
		var r,g,b,h;
		while (frame < frames)
		{
			r = Math.floor(rf * ((frames-frame)/frames) + rt * (frame/frames));
			g = Math.floor(gf * ((frames-frame)/frames) + gt * (frame/frames));
			b = Math.floor(bf * ((frames-frame)/frames) + bt * (frame/frames));
			h = this.make_hex(r,g,b);
		
			setTimeout("Fat.set_bgcolor('"+id+"','"+h+"')", delay);

			frame++;
			delay = interval * frame; 
		}
		setTimeout("Fat.set_bgcolor('"+id+"','"+to+"')", delay);
	},
	set_bgcolor : function (id, c)
	{
		var o = document.getElementById(id);
		if(o) o.style.backgroundColor = c;
	},
	get_bgcolor : function (id)
	{
		var o = document.getElementById(id);
		while(o)
		{
			var c;
			if (window.getComputedStyle) c = window.getComputedStyle(o,null).getPropertyValue("background-color");
			if (o.currentStyle) c = o.currentStyle.backgroundColor;
			if ((c != "" && c != "transparent") || o.tagName == "BODY") { break; }
			o = o.parentNode;
		}
		if (c == undefined || c == "" || c == "transparent") c = "#FFFFFF";
		var rgb = c.match(/rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/);
		if (rgb) c = this.make_hex(parseInt(rgb[1]),parseInt(rgb[2]),parseInt(rgb[3]));
		return c;
	}
}



/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 3.0 Copyright (C) Paul Johnston 1999 - 2009.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
	var bkey = str2binl(key);
	if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

	var ipad = Array(16), opad = Array(16);
	for(var i = 0; i < 16; i++)
	{
		ipad[i] = bkey[i] ^ 0x36363636;
		opad[i] = bkey[i] ^ 0x5C5C5C5C;
	}

	var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
	return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
	var lsw = (x & 0xFFFF) + (y & 0xFFFF);
	var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
	var bin = Array();
	var mask = (1 << chrsz) - 1;
	var lim = str.length * chrsz;
	for(var i = 0; i < lim; i += chrsz)
		bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
	return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
	var str = "";
	var mask = (1 << chrsz) - 1;
	for(var i = 0; i < bin.length * 32; i += chrsz)
		str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
	return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}
/*
 * function that implements the @onstate functionality from checkbox elements in the admin section. 
 * (when elements of typeAs="checkbox" have onstate attributes,  then this gets called appropriately
 * to add or remove the given state.
 */
function pushAdminStateFromCheckbox(className, checked) {
	if (checked) addAdminState(className);
	else removeAdminState(className);
}
function injectViewStateFields(formReference) {
	if (typeof(viewStateManager) != "undefined") {
		var snapshot = viewStateManager.snapshotCurrent();
		var flattened = propToConf(snapshot);
		for (key in flattened) {
			if (flattened.hasOwnProperty(key)) {
				D.debug("creating element for " + key);
				var inputNode = document.createElement("INPUT");
				inputNode.setAttribute("name", "viewstate." + key);
				inputNode.setAttribute("type","hidden");
				inputNode.className = "hidden";
				inputNode.setAttribute("value",flattened[key]);
				formReference.appendChild(inputNode);
			}
		}
	}
	return true;
}

function validateSavedSearchForm(formReference) {
	var myFormName = formReference.getAttribute("name");
	var errStr = "";
	var isEnabled = false;
	var allowPost = true;
	//var isEventType = "wtf?";
	var isEventType = formReference.isEventType.value;
	
        // refs to form elements
	var searchName = formReference.name.value;
	
	// name field is sometimes disabled (for editing).
	if (typeof(searchName)!="undefined") {
		if (searchName == "") {
			message = "You must enter a Saved Search name.";
			errStr == "" ? errStr += message : errStr += "\n" + message;
			allowPost = false;
		}else{
			// make sure the saved search names only contain alpha numeric chars,
			// spaces, underscores and hypens.
			if (isEventType == 1) {
				// event types can not have spaces.
				var re = /^[a-zA-Z0-9-_%]+$/g;
			}else{
				var re = /^[a-zA-Z0-9\s-_%]+$/g;
			}
			regSearch = searchName.search(re);
			if ( regSearch == -1 ) {
				if (isEventType == 1){
					message = "Event type names must contain only\nalphanumeric characters, underscores, hyphens and percent signs.";
				}else{
					message = "Saved search names must contain only\nalphanumeric characters, underscores, hyphens, percent signs and spaces.";
				}
				errStr == "" ? errStr += message : errStr += "\n" + message;
				allowPost = false;
			}
		}
	}
	
	if (formReference.search.value == "") {
		message = "You must enter Saved Search terms."
		errStr == "" ? errStr += message : errStr += "\n" + message;
		allowPost = false;
	}
	
	if (formReference.schedule) {
		if (formReference.basicOrCron_cron.checked) {
                        // validate the cron string
                        if (!CronFieldController.validateCronString(formReference.cronschedule.value) ) {
                            message = "Invalid cron string entered.";
                            errStr == "" ? errStr += message : errStr += "\n" + message;
                            allowPost = false;
                        }

			formReference.schedule.value = formReference.cronschedule.value;
			//D.debug('its cron ' + formReference.schedule.value);
		} else {
			
			formReference.schedule.value = util.getSelectedValue(formReference.basicschedule, "value");
			//D.debug('its basic ' + formReference.schedule.value)
		}
	}
	if (formReference.enableSched_hidden) {
		isEnabled = formReference.enableSched_hidden.value;
	}
	
	if (isEnabled == 1) {
		messagePrefix = "You have enabled a scheduled search, ";
		if (formReference.schedule.value == "") {
			message = messagePrefix + "You must set a schedule.";
			errStr == "" ? errStr += message : errStr += "\n" + message;
			allowPost = false;
		}

		var counttype = util.getSelectedValue(formReference.counttype);
		var relation = util.getSelectedValue(formReference.relation);
		var quant = formReference.quantity.value;

		if (counttype != "always") {
			
			if (quant != "" || counttype != "none" || relation != "Choose...") {
				if (quant == "" || counttype == "none" || relation == "Choose..."){
					alertMessagePrefix = "You have selected an Alert If condition, but not all of them.";
					errStr == "" ? errStr += alertMessagePrefix : errStr += "\n" + alertMessagePrefix;
				}
				if (quant == "") {
					message = "You must set a quantity in the Alert if row.";
					errStr == "" ? errStr += message : errStr += "\n" + message;
					allowPost = false;
				}
				if (counttype == "" || counttype == "none") {
					message = "You must set a count type in the Alert if row.";
					errStr == "" ? errStr += message : errStr += "\n" + message;
					allowPost = false;
				}
				if (relation == "" || relation == "Choose...") {
					message = "You must set a relation in the Alert if row.";
					errStr == "" ? errStr += message : errStr += "\n" + message;
					allowPost = false;
				}
			}
		}
		// TODO - we could use TimeRange and CronConverter methods, to detect whether the searchterms' time range 
		// matches the boundaries of the cron schedule, and if it doesnt, ask them if they're sure they want this.
		var conversionErrorMessage = "";
		if (conversionErrorMessage!="" && !window.confirm(conversionErrorMessage)) {
			return false;
		}
	}
	
	if (errStr.length > 0) alert(errStr);
	if (allowPost) {
		// calls to set the dashboard keys in preferences.   

		// SPL-14096: IE doesn't seem to see the 'dashboard' checkboxes via the DOM
		// dot-accessor method. Not sure why; might have to do with how the group of
		// checkboxes is broken up across serveral <divs/>.
		var inputs = formReference.getElementsByTagName('input');
		var j=inputs.length;
		for (var i=0; i < j; i++) {
		    if (inputs[i].name == "dashboard") {
		        handleDashboardSaving(formReference);
			break;
		    }
		}

		// we need to pack away the current viewstate....
		injectViewStateFields(formReference);
		// splunkWebAdapter handles the serialization and submission, 
		// this allows us to put a callback on what looks like a plan html form. 
		if (typeof(splunkWebAdapter)!="undefined") {
			// main ui uses the ajaxy method to submit the form
			splunkWebAdapter.handleForm(formReference, handleSavedSearchFormResponse);
		}else{
			// admin section don't
			return true;
		}
	}
	return false;
}


/*
 * form validation script used in admin section as well as saved search from UI
 *
 */
function quickValidate(formReference) {
	
	var myFormName = formReference.getAttribute("name");
	var myFormId = formReference.getAttribute("id");
	var allowPost = true;
	var errStr = "";
	var message = "";
	
	// prefs form popuplayer
	// TODO: change to use myFormId, pref form needs an id though.
	if (myFormName == "preferencesForm") {
		var maxResults = formReference.maxResults.value;

		// util.isNumeric() isnt really suitable here.   Instead the below is basically isIntegral()
		if ("" + parseInt(maxResults, 10) != "" + maxResults ) {
			message = "Max Results must be an integer value.";
			errStr == "" ? errStr += message : errStr += "\n" + message;
			allowPost = false;
		}
		else if (parseInt(maxResults, 10) <1) {
			message = "Max Results must be a positive integer.";
			errStr == "" ? errStr += message : errStr += "\n" + message;
			allowPost = false;
		}
		else if (parseInt(maxResults, 10) > 500000000) {
			message = "Please set your max results lower.  Max results values above 500,000,000 are limited by settings in limits.conf.";  //TK ESD 5/16/08 added by ncm for SPL-13291
			errStr == "" ? errStr += message : errStr += "\n" + message;
			allowPost = false;
		}
	}
	
	
	
	// needed for the file updoaded add data page
	if (formReference.logFile) {		
		formReference.logFile_filename.value = formReference.logFile.value;
	}
	
	if (errStr.length > 0) alert(errStr);
	
	return allowPost;
}


// More narrowly defined functions for visibility changes
// those changes are hooked up by two attributes on xml nodes. 
// onstate="someClassName"  and offstate="someClassName".  
// Xsl handles the event handlers and initial setup accordingly.
// RADIO BUTTONS (to be called directly from onclicks on radio buttons)
function addAdminState(className) {
	if ($("adminHeader")) util.addClass(document.getElementById('outerWrapper'), className);
	else if (typeof(stateManager)!="undefined") stateManager.stateChange(className,true);
	else D.error("addAdminState - UI is in an unknown and unactionable state. Exiting.");
}
function removeAdminState(className) {
	if ($("adminHeader")) util.removeClass(document.getElementById('outerWrapper'), className);
	else if (typeof(stateManager)!="undefined") stateManager.stateChange(className,false);
	else D.error("removeAdminState - UI is in an unknown and unactionable state. Exiting.")
}

/*
 * Currently used only in the Alerts forms (both search UI and admin UI)
 * Will check the selectReference, and if it's set to any of the values in the Array valueToTriggerDisabling, 
 * it will bust out some disabling mojo on the element it finds 
 * in the reference fieldToDisable
 */
function checkForDisablingConditions(selectReference, fieldToDisable, valuesToTriggerDisabling) {
	var value = util.getSelectedValue(selectReference);
	var isDisabling = false;
	for (var i=0; i<valuesToTriggerDisabling.length; i++) {
		if (value == valuesToTriggerDisabling[i]) isDisabling = true;
	}
	
	try {
		// push the class 'disabled'  into the element. 
		if (isDisabling) util.addClass(fieldToDisable, "disabled");
		else util.removeClass(fieldToDisable, "disabled");
		// set DOM properties that make the field actually non-editable by the user
		fieldToDisable.disabled = isDisabling;
		//if (!document.all) fieldToDisable.readOnly = isDisabling;
	} catch(e) {
		D.error('Exception conditionally disabling a field from a select.onchange ' + e);
	}
	return true;
}
function checkForOutputFieldDisabling(selectReference, magicValue) {
	var value = util.getSelectedValue(selectReference);
	var isDisabling = (value==magicValue);
	var form = selectReference.form;
	var outputFieldNames = ['action_rss_checkbox', 'action_email', 'sendresults_checkbox', 'action_script'];
	for (var i=0; i<outputFieldNames.length; i++) {
		var fieldToDisable = form[outputFieldNames[i]];
		if (isDisabling) util.addClass(fieldToDisable, "disabled");
		else util.removeClass(fieldToDisable, "disabled");

		fieldToDisable.disabled = isDisabling;
	}
	if (!isDisabling)  {
		addAdminState("showSavedSearchOutputFields");
	} else {
		removeAdminState("showSavedSearchOutputFields");
	}
}

function JSErrorLogger(httpBeacon){
    var self = this;
    self.handler = function(msg, url, line){
        var log = {
            "date":new Date(),
            "type":"jserror",
            "line":line,
            "msg":msg,
            "url":url
        }
        var logStr = "";
        for(var i in log){
            logStr += i + ":" + log[i] + " ";
        }
        var imgObj = new Image();
        imgObj.src = httpBeacon + "?" + logStr;
    };
    self.JSErrorLogger = function(){
        window.onerror = self.handler;
    }();
}
