/************************************************************************
 TEUtilities.js
 jpl 5/31/05
 
	Suite of general purpose JavaScript functions
	
	Data Validation:
		boolean	isInteger(stringValue)
		boolean	isPositiveInteger(stringValue)
		boolean	isNumeric(stringValue)
		boolean	isDate(stringValue)
		boolean	isValidEmailAddress(stringValue)
		boolean	isValidDomainName(stringValue)
	
	String Manipulation:
		int		countInStringMatches(theString, theTarget)
	
	Cookie Interaction:
		String	GetCookie(name)
		SetCookie(name, value)
	
	Window Opening:
		OpenWindow(URL, windowName, width, height,
				   horizontalPosition, verticalPosition)
	
	
	HTML Generation:
		writePaging(theSpan, clickAction, currentPage, totalPages, options)
	
************************************************************************/


var TrueEdge = {};

function properInt(theValue, badReturnValue) {
	if(isInteger(theValue + ''))
		return parseInt(theValue);
	else
		return badReturnValue;
}
function properNumber(theValue, badReturnValue) {
	if(isNumeric(theValue + ''))
		return parseFloat(theValue);
	else
		return badReturnValue;
}

function isInteger(stringValue) {
	return stringValue.match(/^-?\d+$/);
}

function isPositiveInteger(stringValue) {
	return (stringValue.match(/^\d+$/)  &&  !stringValue.match(/^0+$/));
}

function isNonNegativeInteger(stringValue) {
	return stringValue.match(/^\d+$/);
}

function isNumeric(stringValue) {
	return stringValue.match(/^-?\d*(\.\d+)?$/);
}

function isPositiveNumeric(stringValue) {
	return stringValue.match(/^\d+(\.\d+)?$/);
}

function isValidEmailAddress(stringValue) {
	return stringValue.match(/^(\w|-|_|\.)+@(\w|-|_|\.){3,}\.\w{2,5}$/)
}

function isValidDomainName(stringValue) {
	return stringValue.match(/(\w|-|_|\.){3,}\.\w{2,5}$/)
}


	function countInStringMatches(theString, theTarget) {
		
		var index = 0;
		var findCount = 0;
		while (index >= 0) {
			index = theString.indexOf(theTarget, index)
			if (index >= 0) {
				findCount++;
				index++;
			}
		}
		return findCount;
	}
	
	
	function reverse(string){
		returnString = '';
		for (i = string.length; i >= 0; i--)
			returnString += string.charAt(i);
		return returnString;
	}

	// left is just a rename of substring()
	function left(string, count){
		return string.substring(0, count);
	}

	// right actually uses reverse() and left()
	function right(string, count){
		return reverse(left(reverse(string), count));
	}


	function rtrim(string) {
		return string.replace(/\s+$/,'');
	}
	function ltrim(string){
		return string.replace(/^\s+/,'');
	}
	function trim(string){
		return ltrim(rtrim(string));
	}
	
	function ucase(string){
		return string.toUpperCase();
	}
	function lcase(string){
		return string.toLowerCase();
	}

	function showLoad() {
		$('loadingMessage').style.display = 'block';
	}
	function hideLoad() {
		setTimeout('$(\'loadingMessage\').style.display = \'none\'', 200);
	}

/**
 * DHTML date validation script. Courtesy of SmartWebby.com (http://www.smartwebby.com/dhtml/)
 *
 * Modified 12/20/2003 by John Larson to allow MM/DD and MM/DD/YY format in addition to
 * MM/DD/YYYY already implemented.
 */
// Declaring valid date character, minimum year and maximum year
var dtCh= "/";
var minYear=1900;
var maxYear=2100;

function stripCharsInBag(s, bag){
	var i;
    var returnString = "";
    // Search through string's characters one by one.
    // If character is not in bag, append to returnString.
    for (i = 0; i < s.length; i++){   
        var c = s.charAt(i);
        if (bag.indexOf(c) == -1) returnString += c;
    }
    return returnString;
}

function extractCharsInBag(s, bag){
	var i;
    var returnString = "";
    // Search through string's characters one by one.
    // If character is in bag, append to returnString.
    for (i = 0; i < s.length; i++){   
        var c = s.charAt(i);
        if (bag.indexOf(c) != -1) returnString += c;
    }
    return returnString;
}

function daysInFebruary (year){
	// February has 29 days in any year evenly divisible by four,
    // EXCEPT for centurial years which are not also divisible by 400.
    return (((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0))) ? 29 : 28 );
}
function DaysArray(n) {
	for (var i = 1; i <= n; i++) {
		this[i] = 31
		if (i==4 || i==6 || i==9 || i==11) {this[i] = 30}
		if (i==2) {this[i] = 29}
   } 
   return this
}

function isDate(dtStr){
	var daysInMonth = DaysArray(12)
	var pos1=dtStr.indexOf(dtCh)
	var pos2=dtStr.indexOf(dtCh,pos1+1)
	var strMonth=dtStr.substring(0,pos1)
	var strDay=dtStr.substring(pos1+1,pos2)
	var strYear=dtStr.substring(pos2+1)

	var strTodaysYear = new String(new Date().getFullYear());
	if (pos2 < 0) // no year given, so assume this year
		strYr = strTodaysYear;
	else if (strYear.length == 2) // assume only last two digits given, so append first two
		strYr = strTodaysYear.substring(0, 2) + strYear;
	else if (strYear.length == 4) // standard format.
		strYr=strYear
	else
		return false;  // bad year

	if (strDay.charAt(0)=="0" && strDay.length>1) strDay=strDay.substring(1)
	if (strMonth.charAt(0)=="0" && strMonth.length>1) strMonth=strMonth.substring(1)
	for (var i = 1; i <= 3; i++) {
		if (strYr.charAt(0)=="0" && strYr.length>1) strYr=strYr.substring(1)
	}
	month=parseInt(strMonth)
	day=parseInt(strDay)
	year=parseInt(strYr)

	if (pos1==-1){
		//alert("The date format should be : mm/dd/yyyy")
		return false
	}
	if (strMonth.length<1 || month<1 || month>12){
		//alert("Please enter a valid month")
		return false
	}
	if (strDay.length<1 || day<1 || day>31 ||
      (month==2 && day>daysInFebruary(year)) || day > daysInMonth[month]){
		//alert("Please enter a valid day")
		return false
	}
	if (year<minYear || year>maxYear){
		//alert("Please enter a valid 4 digit year between "+minYear+" and "+maxYear)
		return false
	}
	if (!isInteger(stripCharsInBag(dtStr, dtCh))) {
		alert("Please enter a valid date")
		return false
	}
	return true
}














function getCookieVal (offset) 
   {
   var endstr = document.cookie.indexOf (";", offset);
   if (endstr == -1)
      endstr = document.cookie.length;
   return unescape(document.cookie.substring(offset, endstr));
   }

function GetCookie(name) {
   var arg = name + "=";
   var alen = arg.length;
   var clen = document.cookie.length;
   var i = 0;
   while (i < clen) 
      {
      var j = i + alen;
      if (document.cookie.substring(i, j) == arg)
         return getCookieVal (j);
      i = document.cookie.indexOf(" ", i) + 1;
      if (i == 0) break; 
      }
   return null;
}

function SetCookie(name, value) {
   var argv = SetCookie.arguments;
   var argc = SetCookie.arguments.length;
   var expires = (argc > 2) ? argv[2] : null;
   var path = (argc > 3) ? argv[3] : null;
   var domain = (argc > 4) ? argv[4] : null;
   var secure = (argc > 5) ? argv[5] : false;
   document.cookie = name + "=" + escape (value) +
        ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
        ((path == null) ? "" : ("; path=" + path)) +
        ((domain == null) ? "" : ("; domain=" + domain)) +
        ((secure == true) ? "; secure" : "");
}
















function OpenWindow(URL, windowName, width, height, horizontalPosition, verticalPosition) {

	var xOffset, yOffset;
	// The biggest task we have is to determine the x and y offsets for positioning
	// this pop up window.  This will be determined by horizontalPosition and
	// verticalPosition, which we'll let come in either as a percentage or
	// straight pixel amount.
	
	if (horizontalPosition.substring(horizontalPosition.length-1, horizontalPosition.length) == "%") {
		// horizontal position given to us as a percentage
		var horizontalPercentage = horizontalPosition.substring(0, horizontalPosition.length-1)
		
	    if (document.all)  // MSIE
	        var xMax = screen.width;
	    else {
	        if (document.layers) // NN
	            var xMax = window.outerWidth;
	        else // dunno, so we'll guess a 600x800 resolution:
	            var xMax = 800;
	    }
		xOffset = (xMax - width)*(horizontalPercentage/100);
	}
	else // we've been given a straight amount in pixels
		xOffset = horizontalPosition;
	    
	
	
	if (verticalPosition.substring(verticalPosition.length-1, verticalPosition.length) == "%") {
		// vertical position given to us as a percentage
		var verticalPercentage = verticalPosition.substring(0, verticalPosition.length-1)
		
	    if (document.all)  // MSIE
	        var yMax = screen.height;
	    else {
	        if (document.layers) // NN
	            var yMax = window.outerHeight;
	        else // dunno, so we'll guess a 600x800 resolution:
	            var yMax = 600;
	    }
		yOffset = ((yMax-25)- height)*(verticalPercentage/100); // -25 for the task bar probably there
	}
	else // we've been given a straight amount in pixels
		yOffset = verticalPosition;


	

	window.open(URL, windowName,
	            'Height=' + height  + 'px, ' +
	            'Width='  + width   + 'px, ' +
				'left='   + xOffset + 'px, ' +
				'top='    + yOffset + 'px, ' +
				'center=1, help=0, status=1, scrollbars=1');
}





function attributeDiagnostic(element)
{
	var attributes = element.attributes;
	var i = 0;
	var display = ""
	for (var item in attributes) {
		display = display + item + ": " + element.getAttribute(item) + "\n";
		
		i++;
		
		if (i >= 35) { // clear out to make room
			alert("Attributes:\n" + display);
			display = ""
			i = 0;
		}
	}
		
	if (display != "")
		alert("Attributes:\n" + display);
}





	function toggleElementVisibility(whichEl) {	
		if (whichEl.style)
			whichEl.style.display = (whichEl.style.display == "none" ) ? "" : "none";
		else
			alert('No style for ' + whichEl.name);
	}
	
	function toggleElementVisibilityDeluxe(arrowImage, whichEl) {
		if (whichEl.style) {
			whichEl.style.display = (whichEl.style.display == "none" ) ? "" : "none";
			arrowImage.src = "images/arrow_" + ((whichEl.style.display == "none" ) ? "right" : "down") + ".gif"
		}
		else {		
			alert('No style for ' + whichEl.name);
		}
	}

	function toggleRowSetVisibility(rowSetName) {
		var toggleRows= document.getElementsByName(rowSetName);
		for (var i = 0; i < toggleRows.length; i++) {
			toggleElementVisibility(toggleRows[i])
		}
	}

	function toggleRowSetVisibilityDeluxe(arrowImage, rowSetName) {
		var toggleRows= document.getElementsByName(rowSetName);
		

		for (var i = 0; i < toggleRows.length; i++) {
			toggleElementVisibility(toggleRows[i])
		}
		// toggle the arrow based on the first row:
		arrowImage.src = "images/arrow_" + ((toggleRows[0].style.display == "none" ) ? "right" : "down") + ".gif"

	}
	
	
	function tidyPhoneNumber(textInput) {
		with (textInput) {
		  if (value != "") {
		    if (value.indexOf("x") == -1) {
				rawNumber = value;
				rawExtension = ""
			}
			else {  // we'll strip off the extension portion
				rawNumber    = value.substring(0, value.indexOf("x"));
				rawExtension = value.substring(value.indexOf("x"));
			}
			
			// let's now get the numerals from the number:
			rawDigits = extractCharsInBag(rawNumber, "0123456789");
			
			// Strip off a leading one if present:
			if (rawDigits.charAt(0) == '1')
				rawDigits = rawDigits.substring(1);
				
			// Now we expect the number to have 10 digits.
			if (rawDigits.length == 10)  // we can put it into the format (xxx) xxx-xxxx
				phoneNumber =  "(" + rawDigits.substring(0, 3) + ") " +
				               rawDigits.substring(3, 6) + "-" + rawDigits.substring(6, 10);
			else // we'll not mess around with it
				phoneNumber = rawNumber;
			
			// Now let's tend to the extension:
			extension = extractCharsInBag(rawExtension, "0123456789");
			if (extension.length > 0)
				extension = " x" + extension;
				
			// Both parts done, so let's set the value with our result:
			value = phoneNumber + extension;
		  }
		}
	}
	
	
	function isRadioChoiceMade(radioInput) {
		var result = false;
		if (radioInput.length) {
			for (var i=0; i < radioInput.length; i++) {
				result |= radioInput[i].checked;
			}
		}
		else
			result = radioInput.checked;
		
		return result;
	}
	
	function radioButtonValue(radioInput) {
		var result = '';
		if (radioInput.length) {
			for (var i=0; i < radioInput.length; i++) {
				if(radioInput[i].checked) {
					result = radioInput[i].value;
					break;
				}
			}
		}
		else
			result = (radioInput.checked) ? radioInput[i].value : '';
		
		return result;
	}
	
	retryCount = 0;
	function constrainImageToBox(imageID, boxHeight, boxWidth) {
		
		var image = $(imageID);
		
		if (!image) // bad call, false alarm
			return;
		
		if (!image.width  ||  !image.height) { // image hasn't loaded yet, so wait
			if (retryCount < 20) {  // try again soon!
				window.setTimeout("constrainImageToBox('" + imageID + "', " + boxHeight + ", " + boxWidth + ")", 300);
				retryCount += 1;
			}
			return;
		}
		
	//	alert('3: ' + image.height + '... width=' + image.width);
		if (image.height <= boxHeight  &&  image.width <= boxWidth) // no work to be done.
			return;
		
	//	alert('4');
		// If we're still here, we've got an image to size down:
		var nativeImageWidth = image.width;
		var nativeImageHeight= image.height;
		
		// from the aspect ratio we'll know which way to resize:
		if (nativeImageWidth / boxWidth  >  nativeImageHeight / boxHeight)
			image.width = boxWidth; // width sizing down is more extreme
		else
			image.height = boxHeight; // height sizing down is more extreme
		
		retryCount = 0;
	}
	
	function handleDateScrolling(dateInput, e) {
	
		var key = (window.Event) ? e.which : e.keyCode;
		if(isDate(dateInput.value)) {
			switch(key) {
				case 38: // up
				    var d = new Date(dateInput.value);
				    d.setDate(d.getDate() + 1);
				    dateInput.value = (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
					break;
				
				case 40: // down
				    var d = new Date(dateInput.value);
				    d.setDate(d.getDate() - 1);
				    dateInput.value = (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
					break;
			}
		}
	}
	
	

	function getQueryStringOfFormElements(formElements) {
		
		var queryString = [];
		formElements.each(function(el){
			var name = el.name;
			var value = el.getValue();
			if (value === false || !name || el.disabled) return;
			var qs = function(val){
				queryString.push(name + '=' + encodeURIComponent(val));
			};
			if ($type(value) == 'array') value.each(qs);
			else qs(value);
		});
		return queryString.join('&');
	}


	function writePaging(theSpan, clickAction, currentPage, totalPages, options) {
		
		options = $merge(options, {
			startPage:				1,
			windowSize:				20,
			skipIfOnePage:			true,
			prefix:					'Page: ',
			suffix:					'',
			separator:				' ',
			manageHistory:			true,
			pageLinkCurrentClass:	'pageLink-current',
			pageLinkClass:			'pageLink'
		});
		
		theSpan = $(theSpan);
		if(!theSpan)
			throw new Error('writePaging: theSpan (arg 1) is not a valid DOM reference.');
		
		TrueEdge.pagingInstanceCount++;
		
		if (options.manageHistory) {
			this.historyKey = 'genPaging' + TrueEdge.pagingInstanceCount;
			this.history = HistoryManager.register(
				this.historyKey,
				[this.options.currentPage],
				function(values) {
					this.scrollToPage(parseInt(values[0]));
				}.bind(this),
				function(values) {
					return this.historyKey + '(' + values[0] + ')';
				}.bind(this),
				this.historyKey + '\\((\\d+)\\)');
		}
		
		theSpan.empty(); // clear out whatever is already there
		
		if(options.skipIfOnePage  &&  totalPages <= 1)
			return;
		
		var endPage = Math.min(totalPages, options.startPage+totalPages-1);
		
		theSpan.appendText(options.prefix);
		
		for(var i=options.startPage; i <= endPage; i++) {
			
			thisPageNumber = new Element('a', {myPageNumber: i});
			thisPageNumber.addClass(i == currentPage ? 
				options.pageLinkCurrentClass : options.pageLinkClass);
			
			thisPageNumber.setText(i);
			thisPageNumber.addEvents({
				'click': function() {
					dbug.log('here we go, passing ' + this.getText());
					clickAction(this.getText());
					return false;
				}
			});
			theSpan.adopt(thisPageNumber);
			theSpan.appendText(options.separator);
		}
		
		theSpan.appendText(options.suffix);
	}
	
	
	TrueEdge.pagingInstanceCount = 0;
	
	
	TrueEdge.ticTimes = new Hash();
	TrueEdge.tocTimes = new Hash();
	TrueEdge.totalTimes = new Hash();
	function tic(which) {
		which = which || 'default';
		if(!TrueEdge.ticTimes.hasKey(which)) {  // initialize parallel data structures
			TrueEdge.tocTimes.set(which, 0);
			TrueEdge.totalTimes.set(which, 0);
		}
		TrueEdge.ticTimes.set(which, new Date().getTime());
	}
	
	function toc(which, label) {
		which = (which || 'default');
		if(TrueEdge.ticTimes.hasKey(which)) {
			var thisTime = new Date().getTime();
			var elapsed = thisTime - TrueEdge.ticTimes.get(which);
			dbug.log((label  ||  which) + ': ' + elapsed + 'ms elapsed.');
			
			// Keep total time.  We want it to be kept accurate as precisely the
			// total time between (1) the last in a string of 1 or more consecutive tics
			// and (2) the last in a string of 1 or more consecutive tocs following
			// the tic string:
			//
			// Wait, what?
			//
			// Visually, we want to total the time spans marked below (i for tic,
			// o for toc:
			//          <---------->    <---->         <------------->  <--->
			// start:   i    o     o    i    o   i i   i       o     o  i   o    fin
			
			var lastTotal = TrueEdge.totalTimes.get(which);
			
			// What's our last state of tic-toc?
			var lastTic = TrueEdge.ticTimes.get(which);
			var lastToc = TrueEdge.tocTimes.get(which);
			
			var additionalTime;
			if(lastToc > lastTic) { // we're calling 2+ tocs since last tic:
				additionalTime = thisTime - lastToc;
			}
			else {
				additionalTime = elapsed;
			}
			TrueEdge.totalTimes.set(which, lastTotal + additionalTime);
			
			// Set our last toc to now:
			TrueEdge.tocTimes.set(which, thisTime);
		}
	}
	
	function ticTocTotal(which) {
		which = (which || 'default');
		if(TrueEdge.ticTimes.hasKey(which)) {
			dbug.log((label  ||  which) + ': ' + TrueEdge.ticTimes.get(which) + 'ms elapsed total.');
			
		}
	}
	
	function ticTocSummary() {
		TrueEdge.totalTimes.each(function(theValue, which) {
			dbug.log((which) + ': ' + theValue + 'ms elapsed total.');
		});
	}