
    //////////
    // List //

function list_push(l, i) { l[l.length] = i; }
function list_pop (list) { var lastInx = list.length-1; var item = list[lastInx]; list.splice(lastInx, 1); return item; }
function list_index( list, item ) {	var inx; for (inx = 0; inx < list.length; inx++) { if ( item == list[inx] ) return inx; } return -1; }
function list_remove( list, item ) { var inx = list_index( list, item ); if (inx == -1) return list; return list_removeAtIndex( list, inx ); }
function list_removeAtIndex( list, index ) 
{
    // split
	var nlist = list.slice(0,index);
	var list2 = list.slice(index+1);

	// merge
	var inx;
	for (inx = 0; inx < list2.length; inx++)
		list_push(nlist, list2[inx]);

	return nlist;
}


    ////////////
    // String //

function dollarize( s ) { s = s+"";	if ( s.length == 0 )  return ""; if ( s.match(/\.\d\d\d+$/) ) s = s.replace(/\.(\d\d)\d+$/, ".$1"); while ( s.match(/\d\d\d\d/) ) s = s.replace(/(\d)(\d\d\d)(?!\d)/, "$1,$2"); if ( s.match(/\d\d\d$/) ) s += ".00"; return "$" + s; }
function undollarize( s ) { while ( s.match(/[\$,]/) ) s = s.replace(/[\$,]/, ""); return s; }
function percentize( num ) { num *= 100; var s = num+""; if ( s.match(/\.\d\d\d+$/) ) s = s.replace(/\.(\d\d)\d+$/, ".$1"); return s+"%"; }

function padLeft( s, c, len )
{
    while ( s.length < len )
        s = c+s;
    return s;
}
function padRight( s, c, len )
{
    while ( s.length < len )
        s += c;
    return s;
}

function escapeXML( text )
{
	text = text.replace(/&/g, "&amp;");
	text = text.replace(/</g, "&lt;");
	text = text.replace(/>/g, "&gt;");
	text = text.replace(/"/g, "&quot;");
	text = text.replace(/'/g, "&apos;");

	return text;
}

function unescapeXML( text )
{
	text = text.replace(/&lt;/g, "<");
	text = text.replace(/&gt;/g, ">");
	text = text.replace(/&amp;/g, "&");
	text = text.replace(/&quot;/g, "\"");
	text = text.replace(/&apos;/g, "'");

	return text;
}




    //////////
    // Date //

function Age( date, exact )
{
    return Duration( date, new Date(), exact );
}
function Duration(from, to, exact)
{
    var timeSpan = new TimeSpan( from, to );

    if (timeSpan.TotalHours() > 24)
        return timeSpan.days + " days" + (exact?" "+timeSpan.hours + " hours " + timeSpan.minutes + " min":"")+" ago";
    else if (timeSpan.TotalMinutes() > 60)
        return timeSpan.hours + " hours" + (exact?" "+timeSpan.minutes + " min " + timeSpan.seconds + " sec":"")+" ago";
    else if (timeSpan.TotalSeconds() > 60)
        return timeSpan.minutes + " min" + (exact?" "+timeSpan.seconds + " sec":"")+" ago";
    else
        return timeSpan.seconds + " sec ago";
}

function TimeSpan( from, to )
{
    var interval = to.getTime() - from.getTime(); // Difference in ms.

    // Establish larger units based on milliseconds.
    this.msecondsPerMinute = 1000 * 60;
    this.msecondsPerHour = this.msecondsPerMinute * 60;
    this.msecondsPerDay = this.msecondsPerHour * 24;

    // Calculate how many days the interval contains, then subtract that
    // many days from the interval to come up with a remainder.
    this.days = Math.floor( interval / this.msecondsPerDay );
    interval -= (this.days * this.msecondsPerDay );

    // Repeat the previous calculation on the remainder using hours,
    // then subtract the hours from the remainder.
    this.hours = Math.floor( interval / this.msecondsPerHour );
    interval -= (this.hours * this.msecondsPerHour );

    this.minutes = Math.floor( interval / this.msecondsPerMinute );
    interval -= (this.minutes * this.msecondsPerMinute );

    this.seconds = Math.floor( interval / 1000 );
    interval -= (this.seconds * 1000 );

    this.milliseconds = interval;
}

TimeSpan.prototype.TotalHours = function()
{
    return this.hours + (this.days * 24);
}
TimeSpan.prototype.TotalMinutes = function()
{
    return this.minutes + (this.TotalHours() * 60);
}
TimeSpan.prototype.TotalSeconds = function()
{
    return this.seconds + (this.TotalMinutes() * 60);
}

function firstOfNextMonth()
{
   var dtNextMonth = new Date();
   dtNextMonth.setMonth(dtNextMonth.getMonth() + 1 );
   dtNextMonth.setDate(1);
   return(dtNextMonth);
}
function lastOfMonth()
{
   var eomDate = new Date();
   eomDate.setMonth(eomDate.getMonth() + 1 );
   eomDate.setDate(1);
   eomDate.setDate( eomDate.getDate() - 1);
   return eomDate;
}

function lastFriday()
{
   var startDate = new Date();
   if(startDate.getDay() == 5)      // It's Friday today, just subtract one week.
      startDate.setDate( startDate.getDate() - 7);
   else 
      while(startDate.getDay() != 5)   // Walk backwards a day at a time until we get to Friday.
          startDate.setDate( startDate.getDate() - 1);
   return startDate;
}

function thanksgiving()
{
   var dtThanksgiving = new Date();
   dtThanksgiving.setMonth(10);  // November 1st
   dtThanksgiving.setDate(1);
   while( dtThanksgiving.getDay() != 4)     // Find first Thursday.
      dtThanksgiving.setDate( dtThanksgiving.getDate() + 1 ) ;
   dtThanksgiving.setDate( dtThanksgiving.getDate() + 21 ); // Add 3 weeks.
   return dtThanksgiving;
}



    ////////////
    // Events //

function addEvent( obj,         // the object to attach event to
                   evType,      // name of the event - DONT ADD "on", pass only "mouseover", etc
                   fn)          // function to call
{
 if (obj.addEventListener){
    obj.addEventListener(evType, fn, false);
    return true;
 } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
 } else {
    return false;
 }
}
function removeEvent(obj, evType, fn, useCapture)
{
  if (obj.removeEventListener){
    obj.removeEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.detachEvent){
    var r = obj.detachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
  }
}


function AppendEvent( control, eventName, func )
{
    control[eventName] = AppendFunction( control[eventName], func );
}

// This is really a function-chain.  We need to modify the function-chain, and
// since we cannot do that directly to the passed in argument, we must return
// the new function-chain.
function AppendFunction( functionChain, func )
{
	if (functionChain)
	{
	    functionChain = (function(func1, func2){
                return function()
                {
                    func1();
                    func2();
                }
            })(functionChain, func);
	}
	else
		functionChain = func;
    return functionChain;
}

var _hearbeatEvent = null;  // A function-chain. Append an event-handler to the chain.
function Heartbeat( rate /*beats per second*/ )
{
    if (_hearbeatEvent)
        _hearbeatEvent();

    setTimeout("Heartbeat("+rate+");", 1000/rate);
}





    //////////
    // HTML //

function $() 
{
	var id = arguments[0];
	if (typeof id == 'string') 
	{
		if (document.getElementById)
			return document.getElementById(id);
		else if (document.all)
			return document.all[id];
	}
	else
	    throw "!!?";
}


function HtmlStringLiteral( str )
{
    if ( str.match(/'/) )
    {
        if ( str.match(/"/) )
            return "\""+str.replace(/"/g, "&quot;")+"\"";
        else
            return "\""+str+"\"";
    }
    else
        return "'"+str+"'";
}

function NewLabel( text )
{
	var span = document.createElement("SPAN");
	span.innerText = text;
	return span;
}
function NewTextBox( size, id )
{
    var i = document.createElement("INPUT");
    i.setAttribute("type", "text");
    i.setAttribute("size", size);
    i.setAttribute("id", id);
    return i;
}
function NewTextArea( rows, cols, id )
{
    var i = document.createElement("TEXTAREA");
    i.setAttribute("rows", rows);
    i.setAttribute("cols", cols);
    i.setAttribute("id", id);
    return i;
}
function NewLink( onclickFunc )
{
    var a = document.createElement("A");
    a.style.color = "blue";
    a.style.textDecoration = "underline";
    return a;
}



	/////////////
	// XML DOM //

var XMLDOM_NODE_ELEMENT		= 1;
var XMLDOM_NODE_ATTRIBUTE	= 2;
var XMLDOM_NODE_TEXT		= 3;
var XMLDOM_NODE_COMMENT		= 8;
var XMLDOM_NODE_DOCUMENT	= 9;


function AddNode( parentNode, nodeName )
{
    var n = document.createElement(nodeName);
    parentNode.appendChild( n );
    return n;
}

function NewXmlDoc(sXml) 
{
  var xmlDoc;
  if (document.getElementById)
  {
    xmlDoc = new ActiveXObject("Microsoft.XMLDOM")
    xmlDoc.async = false;
    xmlDoc.loadXML(sXml);
    if (xmlDoc.parseError.errorCode != 0)
	alert( "Error parsing xml: [" + xmlDoc.parseError.reason+ "]" + "[" + xmlDoc.parseError.srcText + "]" );
  }
  else if (document.implementation && document.implementation.createDocument)
  {
    xmlDoc = (new DOMParser()).parseFromString(sXml, "text/xml");
  }

  return xmlDoc;
}



// A Recursive scanner for DOM elements with a certain attribute name & value.
function SelectNodes( tag, attributeName, attributeValue )
{
	var array = new Array();

	var getAttributeSupported = true;
	try{
		tag.getAttribute( attributeName );
	} catch (e) { getAttributeSupported = false; }


	if ( getAttributeSupported )
	{
		var aValue;
		aValue = tag.getAttribute(attributeName);

		if ( attributeValue == null )
		{
			if ( aValue != null && aValue != "" )
			{
				array[0] = tag;
			}
		}
		else if ( attributeValue.source )
		{
			if ( aValue != null && aValue.match(attributeValue) )
				array[0] = tag;
		}
		else if ( aValue == attributeValue )
			array[0] = tag;
	}

	// Scan child HTML tags...
	var inx;
	for (inx = 0; inx < tag.childNodes.length; inx++)
	{
		var c;
		c = tag.childNodes[inx];
		var subArray = SelectNodes( c, attributeName, attributeValue );
		var jnx;
		for (jnx = 0; jnx < subArray.length; jnx++)
			array[array.length] = subArray[jnx];
	}

	return array;
}

// The descendant's are scanned in document order (XML tags from top-to-bottom, left-to-right).
// This will not necessarily return the highest level descendant.
function GetFirstDescendantByName ( e, name )
{
	var inx = 0;
	for (inx = 0; inx < e.childNodes.length; inx++)
	{
		var child = e.childNodes[inx];
		if (child.nodeName == name)
			return child;

		var d = GetFirstDescendantByName( child, name );
		if (d)
			return d;
	}

	return null;
}


// The descendant's are scanned in document order (XML tags from top-to-bottom, left-to-right).
function GetDescendantByID( e, id )
{
	var inx = 0;
	for (inx = 0; inx < e.childNodes.length; inx++)
	{
		var child = e.childNodes[inx];
		if (child.id == id)
			return child;

		var d = GetDescendantByID( child, id );
		if (d)
			return d;
	}

	return null;
}
function GetDescendantsByID( e, id )
{
    var list = new Array();
    
	var inx = 0;
	for (inx = 0; inx < e.childNodes.length; inx++)
	{
		var child = e.childNodes[inx];
		if (child.id == id)
		    list_push(list, child);

		var ds = GetDescendantsByID( child, id );
		var jnx;
		for (jnx = 0; jnx < ds.length; jnx++)
		    list_push(list, ds[jnx] );
	}

	return list;
}

function ChildIndex ( c )
{
	var e = c.parentNode;
	var inx;
	for (inx = 0; inx < e.childNodes.length; inx++)
		if ( e.childNodes[inx] == c )
			return inx;
	throw "getChildIndex: what the heck!?";
}




function getFirstChildByName ( e, name )
{
	var child = e.firstChild;
	
	while ( child && child.nodeName != name )
		child = child.nextSibling;
	
	return child;
}

function getNextChildByName ( child )
{
	var origName = child.nodeName;
	child = child.nextSibling;
	
	while ( child && child.nodeName != origName )
		child = child.nextSibling;
	
	return child;
}

function getNthChildByName ( e, name, n )
{
	var child = e.firstChild;
	var count = 0;
	
	while ( child != null )
	{
		if (child.nodeName == name)
			count++;
		if (count == n)
			break;
		child = child.nextSibling;
	}
	
	return child;
}

function getFirstParent ( child, parentName )
{
	var e = child.parentNode;
	while ( e != null && e.nodeName != parentName )
		e = e.parentNode;	
	return e;
}




	///////////////////
	// Network Calls //

function GetURL(fileURL, completedFunc, sync)
{
    var xmlHttp = XmlHttp.create();
    xmlHttp.open("GET", fileURL, !sync);
    if (!sync)
    {
      xmlHttp.onreadystatechange = function() 
      {
	    if ( xmlHttp.readyState == 4 )
	    {
		    if ( xmlHttp.status == 200 || xmlHttp.status == 304 )
			    completedFunc( xmlHttp.responseText ) ;
		    else
			    alert( 'XML request error: ' + xmlHttp.statusText + ' (' + xmlHttp.status + ') ['+xmlHttp.responseText+']' ) ;
	    }
      }
    }

    try
    {
        xmlHttp.send();
    }
    catch (e)
    {
        alert(e.message);
    }

    if (sync)
    	completedFunc( xmlHttp.responseText ) ;
}

var _ignoreCallbackErrors = false;

function Callback(fileURL, requestTxt, completedFunc)
{
    var xmlHttp = XmlHttp.create();
    xmlHttp.open("POST", fileURL, /*async*/true);
    xmlHttp.onreadystatechange = function() 
    {
	    if ( xmlHttp.readyState == 4 )
	    {
		    if ( xmlHttp.status == 200 || xmlHttp.status == 304 )
			    completedFunc( NewXmlDoc(xmlHttp.responseText) ) ;
		    else if (!_ignoreCallbackErrors)
			    alert( 'XML request error: ' + xmlHttp.statusText + ' (' + xmlHttp.status + ') ['+xmlHttp.responseText+']' ) ;
	    }
    }

    try
    {
        xmlHttp.send(requestTxt);
    }
    catch (e)
    {
        alert(e.message);
    }
}

function CallbackSync(fileURL, requestTxt)
{
    var xmlHttp = XmlHttp.create();
    xmlHttp.open("POST", fileURL, /*async*/false);
    try
    {
        xmlHttp.send(requestTxt);
    }
    catch (e)
    {
        if (!_ignoreCallbackErrors)
            alert(e.message);
        return null;
    }

    if ( xmlHttp.status == 200 || xmlHttp.status == 304 )
        return xmlHttp.responseText;
    else if (!_ignoreCallbackErrors)
	    alert( 'XML request error: ' + xmlHttp.statusText + ' (' + xmlHttp.status + ') ['+xmlHttp.responseText+']' ) ;
    return null;
}

function CallbackText(fileURL, requestTxt, completedFunc)
{
    var xmlHttp = XmlHttp.create();
    xmlHttp.open("POST", fileURL, true);
    xmlHttp.onreadystatechange = function() 
    {
	    if ( xmlHttp.readyState == 4 )
	    {
		    if ( xmlHttp.status == 200 || xmlHttp.status == 304 )
			    completedFunc( xmlHttp.responseText ) ;
		    else
			    alert( 'XML request error: ' + xmlHttp.statusText + ' (' + xmlHttp.status + ') ['+xmlHttp.responseText+']' ) ;
	    }
    }

    try
    {
        xmlHttp.send(requestTxt);
    }
    catch (e)
    {
        alert(e.message);
    }
}


function XmlHttp() {}
XmlHttp.create = function ()
{
  try
  {
  	if ( window.XMLHttpRequest )		// Gecko
		return new XMLHttpRequest() ;
	else if ( window.ActiveXObject )	// IE
		return new ActiveXObject("Msxml2.XMLHTTP") ;
  }
  catch (ex) {}
  alert("Your browser does not support XmlHttp objects");
}


function CurrentUrlFolder()
{
    // Base URL needed for Images
    var r = window.location.pathname;
    var inx = r.lastIndexOf('/');
    if (inx <= 0)
    	r = "";
    else
    	r = r.substr(0, inx);
    r += "/";
    return window.location.protocol+"//"+ window.location.hostname+ r ;
}







	///////////////////////
	// Request.Arguments //
	///////////////////////

var Url = {

    // public method for url encoding
    encode : function (string) {
        return escape(this._utf8_encode(string));
    },

    // public method for url decoding
    decode : function (string) {
        return this._utf8_decode(unescape(string));
    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}

function Request()
{
}
Request.Arguments = new Object();
readArgs();
function readArgs()
{
  var argStr = window.location.search;
  if (argStr == null || argStr.length <= 1)
	return;
  
  var n = argStr.substring(1).split(/&/);
  var inx;
  for ( inx = 0; inx < n.length; inx++ )
  {
    var array = n[inx].split( /=/ );
    var key = array[0];
    var val = array[1];

    if (!val)
	val = "";



    Request.Arguments[key] = Url.decode(val);
  }
}




    ///////////
    // Misc. //
    ///////////


function LetterToIndex( c )
{
    c = c.toUpperCase();
    c = c.charCodeAt(0);
    return c - 0x41;
}

function ToHex2( num )
{
    return ToHex(num/16) + ToHex(num%16);
}
function ToHex( num )
{
    if (num < 10)
    {
        var zero = "0".charCodeAt(0);
        return String.fromCharCode( zero+num );
    }
    else
    {
        var a = "A".charCodeAt(0);
        return String.fromCharCode( a+(num-10) );
    }
}


function show_hash( hash )
{
	alert( hash_to_string( hash ) );
}
function hash_to_string( hash )
{
	var result = "";
	for (var key in hash) 
	{
		result += "["+key+"] = " + hash[key] + "\n";
	}
	return result;
}
function hash_to_string2( hash, keyFunc, valueFunc )
{
	var result = "";
	for (var key in hash) 
	{
		var val = hash[key];

		if (keyFunc != null)
			key = keyFunc(key);
		if (valueFunc != null)
			val = valueFunc(val);

		result += key + " = " + val + "\n";
	}
	return result;
}