// Input Mask javascript plug-in - Version 1.0
// Written by Brian Laframboise June 2003
// Updated Nov 2003 by Daniel Urquhart
	//Note: RealExpression matching has been improved in the Java code.  This code has been updated to
	//handle the same functions, but it does NOT use the new (optimal) algorithm.  Or is still 'greedy'.
// Last update March 2006 by Chris King
	//Real Expressions now use the built in JS RegExp class and functions, and are now passed in as valid 
	//JS expressions. This greatly reduces the time spent loading up documents on the screen as we are no
	//longer parsing each individual expression

/* Conceptual flow of program:
 * Example html: <input type="text"		regExp="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"
 										inProgExp="[0-9]*"
 										mask="$****.**"
 *										onFocus="loadMask(this)"
 *										onKeyDown="return handleCharacter()"
 *										onBlur="validateMask()">
 *
 * 1. Once the page is initially loaded, all of the input masks are created and global info is loaded into the
 *     input mask-tracking hash for later retrieval (onFocus).
 * 2. When a input field with a defined real exp gains focus, its information is loaded from the hash.
 * 3. When a character is inputted, handleCharacter() checks to see if adding that character to the valid input results
 *     in a string which could still be valid input. If so, the gui and the script results are updated.
 *     Since backspaces, etc. are handled only by onKeyDown, we act on all key events on the "onKeyDown"
 * 	   event.
 * 4. When the focus is lost on the input box, validateMask() ensures the entire input string matches its mask.
 *     Specific contextual correctness (dates like 2000/02/31 for example) are handled server side, not here.
*/



// GLOBAL VARIABLES

/*RegExp*/	var regExp				// Regular Expression used to validate the text in the input box
/*RegExp*/	var inProgExp			// If specified, Regular Expression used to validate the value as it is being entered in the Inputbox
/*String*/	var inputMask;			// input mask used to format the input box's value
/*Results*/ var lastMatch;			// outcome of the valid validation attempt on inputMask
/*String*/	var curBoxID;			// current unique box id needed for saving and loading on focus/blur
/*Input*/	var inputBox;			// reference to the input box using the input mask

// CONSTANTS / STATIC VARIABLES
/*boolean*/ var initing = false;			// whether or not we should focus/select on boxes (avoids graphics glitches)
/*Hash*/	var boxes = new Object();		// Objects are hashes
/*int*/		var VALID = 1;					// constants used for real expression validation
/*int*/		var UNDETERMINED = 0;   		// ..
/*int*/		var INVALID = -1;				// ..
/*boolean*/	var allSelected = false;		// for use when tabbing into an input box			

var keyMapping = new Array();
var keyMappingShift = new Array();
keyMapping[32] = 32; // ' ' space
keyMapping[187] = 61; // =
keyMapping[188] = 44; // ,
keyMapping[189] = 45; // -
keyMapping[190] = 46; // . 
keyMapping[110] = 46; // . (110 = numpad decimal point)
keyMapping[191] = 47; // /
keyMapping[220] = 92; // \
keyMapping[222] = 39; // '

keyMappingShift[187] = 43; // +
keyMappingShift[188] = 60; // <
keyMappingShift[189] = 95; // _
keyMappingShift[190] = 62; // >
keyMappingShift[191] = 63; // ?
keyMappingShift[220] = 124; // |
keyMappingShift[222] = 34; // "
keyMappingShift[47] = 33; // !
keyMappingShift[48] = 64; // @
keyMappingShift[49] = 35; // #
keyMappingShift[50] = 36; // $
keyMappingShift[51] = 37; // %
keyMappingShift[52] = 94; // ^
keyMappingShift[53] = 38; // &
keyMappingShift[54] = 42; // *
keyMappingShift[54] = 40; // (
keyMappingShift[54] = 41; // )


												///////////////////////
												///      MAIN		///
												///////////////////////

/* initMasks - Loops through all the input type="text"s on the page, checks for src tags (we're using as mask data)
 * 			   and tries to mask the initial value of the input field.
 * Note: Should always be called onLoad of the page using input masking.
*/
function /*void*/ initMasks(/*bool */ refresh)
{	
	initing = true;
	var allBoxes = document.getElementsByTagName("input");	//get all input boxes
	for(i = 0; i < allBoxes.length; i++)
	{
		var obj = allBoxes[i];
		//src tags are not defined for input boxes, so we're using them to store the input's mask
		if(obj.type == "text" && obj.getAttribute('realExp') != null)
		{	createNewInputMask(obj, stripLiterals(obj, obj.value)); //make a new mask for the input and compile its RegExp
			validateMask(true);
			if(refresh == true)
			{
				loadMask(obj);
				validateMask(true);
			}
		}
	}
	initing = false;
}

/* resetMask - Clears the stored lastMatch data for a given input box.
 * theInputBox: Reference to the input box to reset
 * returns: True if there was information to reset.
 *			False if there was no masking information for the input box.  This
 *			might indicate that the input box has not had its mask information initialized.
 * Note: The purpose of this function is to clear the stored information 
 *       used in a particular match.  This should be used when both the 
 *       masked an unmasked text of an input box changes.  This allows a
 *       new lastMatch to be created when the user clicks into theInputBox
 *       that will not rely on stale data since the text might have completely
 *       changed since it was last created.  If either of the masked or unmaksed
 *       data is not changed, this function should not be used because it will
 *       NOT synch the data.  This function can be called multiple times in sequence
 *       with no ill-effect.
*/
function /*boolean*/ resetMask(/*Input*/ theInputBox)
{
	var stateInfo = boxes[theInputBox.name];
	if(stateInfo)
	{
		stateInfo[1] = undefined;
		boxes[theInputBox.name] = stateInfo;
		return true;
	}
	return false;
}

/* loadMask - Loads all the global variables back into the system for the specified inputBox
 * theInputBox: Reference to the input box we'll be masking for
 * returns: false to prevent bubbling up?
*/
function /*boolean*/ loadMask(/*Input*/ theInputBox)
{
	curBoxID = theInputBox.name; //obtain the hash key for this box
	var stateInfo = boxes[curBoxID];//boxes.get(curBoxID); //get the data from the hash based on our key
	allSelected = false;	// we've just gained focus so we need to select everything

	regExp	 	= stateInfo[0]; //load all the nice variables
	lastMatch 	= stateInfo[1];
	inputBox 	= stateInfo[2];
	inProgExp	= stateInfo[3];
	inputMask	= stateInfo[4];
	
	var text;
	// This is the initial validation step.  We fetch the unmasked text from the
	// "umValue" attribute.  The value in the input box is a masked version.
	if(lastMatch == undefined)
	{	
		//Need to use inputBox.getAttribute since Firefox doesn't like "inputBox.umValue"
		if(inputBox.getAttribute('umValue'))
			text = inputBox.getAttribute('umValue');
		else
			text = "";
		if (inputMask == null || inputMask == "")
		{	
		    inputMask = inputBox.getAttribute('mask');
		}
		
		text = stripLiterals(inputBox, text);
		
		if (inputMask == "**/**/****")
		{	text = text.replace("/", "");
			text = text.replace("/", "");
		}
		var results = regExp.exec(text);

		if (!results)
		{		
			var newText = applyMask(false);
			lastMatch = new MatchResult(newText, text, 0);
		}
		else
		{
			lastMatch = new MatchResult("", text, 0);
			if (inputMask == null || inputMask == "")
				var newText = text;
			else
				var newText = applyMask(true);
			lastMatch.maskedText = newText;
		}
	}
	else
		text = lastMatch.unmakedText;

	if (theInputBox.readOnly || theInputBox.disabled)
	{
		//Set the masked text.  updateMask() does this, but it
		//also selects the text which look stupid for disabled or read-only text.
		
		if(initing && text == "")
			inputBox.value = "";
		else
			inputBox.value = lastMatch.maskedText;
		return;
	}

	updateMask(); //restore all variables and update gui
}

/* applyMask - Applys an input mask to the unmasked value of an input box
		NOTE: We assume that lastMatch.unmaskedText exists
 * success: true if the unmasked value pass the regExp check
*/
function applyMask(/*Boolean*/ success)
{
	if (!success && (inputMask == null || inputMask == ""))
	{	return "";	
	}

	if (lastMatch != undefined)
	{	if (regExp == "/([A-Z][0-9][A-Z][0-9][A-Z][0-9]|[0-9][0-9]*)/" && startsWithNum(lastMatch.unmaskedText))
			inputMask = "**********";
		else if (regExp == "/([A-Z][0-9][A-Z][0-9][A-Z][0-9]|[0-9][0-9]*)/" && !startsWithNum(lastMatch.unmaskedText))
			inputMask = "*** ***";
	}

	var tempMask = inputMask;
	var newText = inputMask;
	if (!success)	// Replace all "*" with an "_"
	{
		if (newText.indexOf("$") != -1 && newText.indexOf(".") != -1)
			newText = "$0.00";
		else if (newText.indexOf("$") != -1)
			newText = "$0";
		if (newText.indexOf("%") != -1 && newText.indexOf(".") != -1)
			newText = "0.00%";
		else if (newText.indexOf("%") != -1)
			newText = "%";	
		while (newText.indexOf("*") != -1)
		{	newText = newText.replace("*", "_");
		}
		newText = newText.replace("~", "");
	}
	else
	{
		var rightToLeft = inputMask.indexOf("~");
		var text = lastMatch.unmaskedText.toString();
		
		var hitPlaceHolder = false;
		
		newText = "";
		var textLoc = 0;
		var maxLen = text.length;
		if (rightToLeft != -1)		// Apply mask from left to right
		{
			var maskDecimalLoc = tempMask.indexOf(".");	// Check to see if mask with decimal values
			var textDecimalLoc = text.indexOf(".");	// Check to see if text has decimal value

			if (maskDecimalLoc != -1 && textDecimalLoc != -1)
			{	
				textLoc = textDecimalLoc;
				for (var j = maskDecimalLoc; j < tempMask.length; j++)
				{	if (tempMask.charAt(j) != '*')
					{	newText = newText + tempMask.charAt(j);
						textLoc++;					
					}
					else
					{	
						if (textLoc < maxLen)
						{	newText = newText + text.charAt(textLoc);
							textLoc++;
						}
					}
				} 

				maxLen = textDecimalLoc;
				var startPos = maskDecimalLoc - 1;
			}
			else if (maskDecimalLoc != -1)
				var startPos = maskDecimalLoc - 1;
			else
				var startPos = tempMask.length - 1;
			
			if (textDecimalLoc == -1 && maskDecimalLoc != -1) // Search for any literal chars in mask after the decimal (ie: $ in French currency notation)
			{
				for (var i = maskDecimalLoc + 1; i < tempMask.length; i++)
				{	if (tempMask.charAt(i) != '*')
						newText = newText + tempMask.charAt(i);
				}
			}

			textLoc = maxLen - 1;
			maxLen = 0;

			for (var i = startPos; i > 0; i--)	// Go until i > 0 so we do not print out the "~"
			{
				if (tempMask.charAt(i) != '*' && ((textLoc >= maxLen) || tempMask.charAt(i) == '$'))
				{	newText = tempMask.charAt(i) + newText;
					if (hitPlaceHolder)
						lastMatch.insertPoint++;
					if (tempMask.charAt(i) == '.')
						textLoc--;
				}
				else
				{	
					hitPlaceHolder = true;
					if (textLoc >= maxLen)
					{	newText = text.charAt(textLoc) + newText;
						textLoc--;
						lastMatch.insertPoint++;
					}
				}
			}
		}
		else		// Apply mask from right to left
		{
			for (var i = 0; i < tempMask.length; i++)
			{
				if (tempMask.charAt(i) != '*' && (textLoc < maxLen))
					newText = newText + tempMask.charAt(i);
				else
				{	
					if (textLoc < maxLen)
					{	newText = newText + text.charAt(textLoc);
						textLoc++;
					}
				}
			}	
			lastMatch.insertPoint = newText.length;
		}
	}
	
	return newText;
}

/* loadMaskByIvar - Used by Ivars to dynamically update a masked field's value
 *					Note: Called from Ivar.js
 * widget: The input box to update
 * value: New input box value
*/
function /*void*/ loadMaskByIvar(/*Input*/ widget, /*String*/ value)
{
	delete boxes[widget.name]; //remove the widget from the box hash
	initing = true; //turn on initing so we don't focus on the change
	curBoxID = widget.name; //grab the widget's id
	createNewInputMask(widget, stripLiterals(widget, value)); //make the new mask and validate
	validateMask(true);
	loadMask(widget);
	initing = false; //turn off initing
}

/* stripLiterals - Strips literals (non-metas) from the text based on theInputBox's mask
 * theInputBox: The input box containing the src field that determines how to strip the literals
 * text: The string from which we'll strip the literals
 * returns: A string of the theInputBox's value field without literals
*/
function /*String*/ stripLiterals(/*Input*/ theInputBox, /*String*/ text)
{
	if(text == undefined || text == "")	return "";
	
	var temp = theInputBox.getAttribute('realExp');
	//Note, these patterns should be synchronized with those in RealExpression.java
	if(temp == "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*")
		var regex = /[() x_-]/g; //use special phone number regex
	else
		var regex = /[() $%_]/g; //use default

	return text.replace(regex, "");
}

/* createNewInputMask - Generates a brand new input mask with default values and compiles the new RegExp(s)
 * theInputBox: Reference to the input box we'll be masking for
 * curText: Starting valid input for the input box
*/
function /*void*/ createNewInputMask(/*Input*/ theInputBox, /*String*/ curText)
{
	var startText = "";
	if(curText != undefined) startText = curText;

	inputBox = theInputBox; //input box we'll be dealing with
	curBoxID = theInputBox.name; //obtain the hash key for this box

	// compile the RegExp(s)
	regExp = new RegExp (inputBox.getAttribute('realExp'));
	inProgExp = new RegExp (inputBox.getAttribute('inProgExp'));
	inputMask = inputBox.getAttribute('mask');
	
	// We now receive unmasked data from the server.  We don't want to validate the data
	// until we actually need to.  The initial validation is now done in the loadMask function.
	// lastMatch should be checked for undefined before being used.
	lastMatch = undefined;
}

/* validateChar - handles onKeyPress events for input boxes and validates its addition to the current accepted input
 * charCode: Specific unicode value to validate adding to the current input.
 *			 Defaults to the key pressed by the user, overridden specifying the parameter.
 * returns:	false, to prevent the onKeyPress event from bubbling up
*/
function /*boolean*/ validateChar(/*int*/ charCode)
{
	//get the character typed
	if(charCode == null)	var inputChar = String.fromCharCode(ASCIICode(event));
	else					var inputChar = String.fromCharCode(charCode);

	if (inputBox.disabled || inputBox.readOnly)
		return false;

	if(inputChar != "")	//if validating an actual character
	{
		var tempResults = inputMask.validate(lastMatch.unmaskedText + inputChar); //get validation
	
		if(tempResults.status > INVALID) //if the character added to existing input is acceptable
		{
			lastMatch = tempResults;
			updateMask();
		}
	}
	return false; //must return false to prevent the input box from adding the character itself
}



/* handleCharacter - handles onKeyDown events for the inputBox.
 * This function has been overhauled to handle "ALL" characters, not only special characters.
 * The reason this was done is so that the code for deletion of selected text could be reused
 *    easily, as well as handling various things like caret position etc.
 * Since we are selecting only specific event keyCode values handling the events on the 
 *	  "onKeyDown" event is fine. (The only difference between onKeyDown and onKeyPress
 *	  is that onKeyDown also recognizes the <SHIFT>,<Backspace>,<DELETE>, etc. keys...)
*/
function /*boolean*/ handleCharacter(e)
{
	var bubble_up = true;
	if (inputBox.disabled || inputBox.readOnly)
		return false;
	
	if (window.event)
		keynum = e.keyCode;	//IE
	else if (e.which)
		keynum = e.which;	//Mozilla/Firefox
		
	
	if ( !(keynum == 18 || keynum == 17) && // don't try to handle modifiers
			( (keynum == 8 ) ||  // backspace
	 		  (keynum == 46) ||  // delete
	 		  (ASCIICode(keynum)) )       // (undefined (ie false) if there is no mapping defined.)
		)
	{
		bubble_up = false;
		
		if (document.selection)
		{	var selectionRange = document.selection.createRange().duplicate();
			var characters_in_selection = selectionRange.text.length;
			var characters_before_selection = (-1)*(selectionRange.moveStart("character",-1 * 1024));
			var characters_after_selection = selectionRange.moveEnd("character",1024);
		}
		else
		{	
			var characters_in_selection = inputBox.selectionEnd - inputBox.selectionStart;
			var characters_before_selection = inputBox.selectionStart + 1;
			var characters_after_selection = inputBox.value.length - inputBox.selectionEnd;
		}
		
		var internal_pointer_position;

		var remaining_valid_str = "";
		var mask_index = 0;
		var valid_index = 0;
		var mask_length = lastMatch.maskedText.length;
		var valid_length = lastMatch.unmaskedText.length;
		if (inputBox.maxLength == -1)
			inputBox.maxLength = 999;
		
		// "characters_before_selection" is the number of unmasked characters that are present in the input box
		//     before the section which has been selected.  
		// This loop iterates throught these characters and starts to build an unmasked copy of the text which 
		//     has not been selected.
		// Making sure that "mask_index" and "valid_index" are less than "mask_length" and "valid_length" respectively
		//     stops running this code when there is no more input to examine.
		while ((mask_index < characters_before_selection) && (mask_index < mask_length) && (valid_index < valid_length))
		{
			if (lastMatch.maskedText.substring(mask_index, mask_index+1) == lastMatch.unmaskedText.substring(valid_index, valid_index+1))
			{	// found a matching character.
				// copy this character into the new unmasked string (remaining_valid_str) and incremente the "valid_index" to examine the next character.
				remaining_valid_str += lastMatch.unmaskedText.substring(valid_index, valid_index+1);
				valid_index++;
			}
			mask_index++; // next masked character.
		}
		// default the internal pointer to the position right before the selected block.
		internal_pointer_position = valid_index;

		///// HANDLES Removing Selected Text  /////////////////////
		// Note: Cursor placement might rely of what text exists after it
		//       so this code should remain before the input specific sections.
		if (characters_in_selection != 0)
		{
			while ((mask_index < characters_in_selection + characters_before_selection) && (mask_index < mask_length) && (valid_index < valid_length))
			{
				if (lastMatch.maskedText.substring(mask_index, mask_index+1) == lastMatch.unmaskedText.substring(valid_index, valid_index+1))
				{
					valid_index++;
				}
				mask_index++;
			}
		}

		///// HANDLES "Delete" and "Backspace"  ///////////////////
		// Backspace and delete should only do extra work when there is no selection 
		//    (The selected region will always be removed for any of the valid input values)
		if ( ((keynum == 8 ) || (keynum == 46)) && (characters_in_selection == 0)) 
		{
			if (keynum == 46) 
			{
				if (valid_index < valid_length) 
				{ // skip the current character leaving all others to append onto the end.
					valid_index++;
				}
			}
			else if (keynum == 8) 
			{ 
				if (remaining_valid_str.length > 0) // backspace is only valid if our caret is not at the beginning of the valid text.
				{
					// removes the last character from the "remaining_valid_str" (the "Backspaced" character)
					remaining_valid_str = remaining_valid_str.substring(0,remaining_valid_str.length-1);
					internal_pointer_position--;
				}
			}
		}
		///// HANDLES All other characters. /////////////////////
		// only adds characters if valid length does not exceed the input box's max length
		// or if replacing selected characters
		else if ( ASCIICode(keynum) && (valid_length < inputBox.maxLength || characters_in_selection != 0) )
		{//*/
  			if (isValidString(remaining_valid_str + String.fromCharCode(ASCIICode(keynum)))) 
			{
				internal_pointer_position++;
				remaining_valid_str += String.fromCharCode(ASCIICode(keynum));
			}//else char is not valid so don't add it.
		}
		
		////// HANDLES re-adding the remaining characters   ///////////
		lastMatch.unmaskedText = lastMatch.unmaskedText.toString();
		var end_characters = lastMatch.unmaskedText.substring(valid_index);
		// Loops through the remaining characters and validates the string as if the user typed
		//    each character in succession.  In this way most of the time the longest substring of
		//    valid data will be kept.
		
		// First attempt to short circuit append.
		var requested_string = remaining_valid_str+end_characters;
		
		if (regExp == "/([A-Z][0-9][A-Z][0-9][A-Z][0-9]|[0-9][0-9]*)/")
			requested_string = requested_string.toUpperCase();
		
		if (requested_string.length <= getNumPlaceHolders())
		{	
			if (requested_string.length == 0)
			{	lastMatch.unmaskedText = requested_string;
				lastMatch.maskedText = requested_string;
				lastMatch.insertPoint = 0;
			}
			else
			{
				if (inProgExp && (inProgExp != "/null/"))
				{	
					var result = findMatchInProg(requested_string);
					if (result)
					{	lastMatch.unmaskedText = requested_string;
						if (inputMask == null || inputMask == "")
						{	lastMatch.maskedText = requested_string;
							lastMatch.insertPoint = lastMatch.maskedText.length;
						}
						else
							lastMatch.maskedText = applyMask(true);
					}
				}
				else
				{	
					var result = findMatch(requested_string);
					if (result)
					{	
						lastMatch.unmaskedText = requested_string;
						if (inputMask == null || inputMask == "")
						{	lastMatch.maskedText = requested_string;
							lastMatch.insertPoint = lastMatch.maskedText.length;
						}
						else
							lastMatch.maskedText = applyMask(true);
						
					}
				}
			}
		}
	
		/*if(tmpMatch.status > INVALID)
		{
			lastMatch = tmpMatch;
		}
		else { // attempt to use as many existing characters as possible.
			// Try adding as many as you can.
			for (end_character_index=0; end_character_index<end_characters.length; end_character_index++) 
			{
				if (isValidString(remaining_valid_str + end_characters.substring(end_character_index, end_character_index+1))) 
				{
					remaining_valid_str += end_characters.substring(end_character_index, end_character_index+1)
				}
			}
			lastMatch = inputMask.validate(remaining_valid_str);
		}*/
		updateMask();
		// We ONLY want to explicity set the pointer if we are manipulating the field at a place other than the end.
		//    OR if we have recieved a non-input event (like "backspace" or "delete")
		if ((end_characters.length!=0) || (isCharOrNum(keynum) == false))
		{
			var new_caret_position = convertInternalPosToMaskPos(internal_pointer_position);
			setSelectionRange(new_caret_position, new_caret_position); // sets the caret position.
		}
	}
	return bubble_up;
}

/* returns true if the key_event represents a standard typed character or digit */
function isCharOrNum(key_event) 
{
return (((key_event >=65 ) && (key_event <= 90) && (key_event != 17))  || // Ctrl
		((key_event >=48 ) && (key_event <= 57)) ||
		((key_event >=96 ) && (key_event <= 105)));
}

/* The keyCode values from javascript are not mapped to ASCII values (ie. the same
 * key say 48 will be either ASCII 48 ('1') or ASCII 33 ('!') )
 * The following rules determine the ASCII Value from the keyCode and shiftKey value
 */
function /*int*/ ASCIICode(key_event) 
{
	if (((key_event >= 96 ) && (key_event <= 105))) // numpad.
	{
		return key_event - 48;
	}
	if (((key_event >= 48 ) && (key_event <= 57)) && (key_event != 16)) //Shift
	{
		return key_event;
	}
	else if ((key_event <= 90) && (key_event >= 65)) 
	{   // NOTE: this code will fix the input of characters to match what is actually typed, but
		//       may not be suitable for what is wanted for postal codes etc...
		// If capitals are always required simply un-comment the next line.
		// return key_event.keyCode;  /*
		if (key_event == 16) 
		{
			return key_event;
		}
		else 
		{
			return key_event+32;
		} //*/
	}
	else 
	{
		if (key_event != 16) 
		{
			return keyMapping[key_event];
		}
		else 
		{
			return keyMappingShift[key_event];
		}
	}
}

/* convertInternalPosToMaskPos  - calculates an integral value for the new caret position
 * @param: internal_position: the index position within "lastMatch.validInput" where the caret should appear.
 * RETURNS: the index position within "lastMatch.maskedText" where the caret will be shown.
 * NOTE: internal_position holds the index value of the character that the caret should appear before.
 *   ie.  for "532|34", where "|" is the caret, would give "internal_position" of 3.
 */
function /*int*/ convertInternalPosToMaskPos(internal_position) 
{
	var mask_index=0;
	var valid_index=0;
	// loops throught the first "internal_position" characters in "lastMatch.validInput" matching each 
	//     character to it's corresponding "Masked" character.  This allows us to find the masked_index
	//     which corresponds to the "internal_position" character of the "validInput" string
	while ((valid_index < internal_position) && (mask_index < lastMatch.maskedText.length) && (valid_index < lastMatch.unmaskedText.length))
	{	
		if (lastMatch.maskedText.substring(mask_index, mask_index+1) == lastMatch.unmaskedText.substring(valid_index, valid_index+1)) 
		{// If they match move to next valid character.  Otherwise, continue until all character's checked or match found.
			valid_index++;
		}
		mask_index++;
	}
	// returns the number of masked characters required to show the first "internal_position" unmasked characters.
	return mask_index;
}


/* validateMask - validates the user's input upon exiting the input box
 * return: false, to prevent the onBlur event from bubbling up
*/
function /*boolean*/ validateMask( /*boolean*/ ignoreChanges)
{
	var preValue = inputBox.value;
	
	if(lastMatch != undefined && (lastMatch.unmaskedText == "") && !inputBox.disabled && !inputBox.readOnly)
	//if the last validation of this input box was not VALID
	{	inputBox.value = ""; 			//clear the box

		//boxes.remove(curBoxID); 		//remove all state info from box/mask-tracking array
	}
	
	if (lastMatch != undefined)
	{	var result = regExp.exec(lastMatch.unmaskedText);
		if (!result)
		{	inputBox.value = "";
			lastMatch.unmaskedText = "";
			lastMatch.maskedText = applyMask(false);
		}
	}
	
	var stateInfo = new Array(5);
	stateInfo[0] = regExp;
	stateInfo[1] = lastMatch;
	stateInfo[2] = inputBox;
	stateInfo[3] = inProgExp;
	stateInfo[4] = inputMask;

	boxes[curBoxID] = stateInfo;
	
	if(ignoreChanges)
		return;
	
	if (preValue != inputBox.value)
		setHasChanged(inputBox);
	
	//allSelected = false;
	inputBox.setAttribute('umValue', getUnmaskedText(inputBox));
}


/* validateMaskUponSet - This function takes and validates a given value *newVal*
 * that has just been set to the the InputBox *obj*.  
 */
function /*boolean*/ validateMaskUponSet(/*InputBox*/obj, newVal)
{
	var stateInfo = boxes[obj.name]; // retrieves the current state information from the has map.
	
	regExp	 	= stateInfo[0]; //load all the nice variables
	lastMatch 	= stateInfo[1];
	inputBox 	= stateInfo[2];
	inProgExp	= stateInfo[3];
	inputMask	= stateInfo[4];
	
	var result = findMatch(newVal);
	
	if (lastMatch == undefined)
	{	lastMatch = new MatchResult("", newVal, 0);
	}
	
	if (stateInfo[1] != undefined)
	{	if (!result)
		{	
			stateInfo[1].unmaskedText = "";
			stateInfo[1].maskedText = applyMask(false);
		}
		else
		{
			stateInfo[1].unmaskedText = newVal;
			stateInfo[1].maskedText = applyMask(true);
		}
	}
	else
	{
		if (result)
		{	var newText = applyMask(true);
			stateInfo[1] = new MatchResult(newText, newVal, 0);
		}
		else
			stateInfo[1] = new MatchResult(applyMask(false), "", 0);
	}
	
	stateInfo[0] = regExp;
	stateInfo[2] = inputBox;
	stateInfo[3] = inProgExp;
	stateInfo[4] = inputMask;

	//stateInfo[1] = tempLastMatch;
	boxes[obj.name] = stateInfo; // updates the boxes hash
	obj.value = stateInfo[1].maskedText; // sets the visible value of the field to be the newly masked string.
	obj.setAttribute('umValue', stateInfo[1].unmaskedText); // sets the unmasked value
}


function getUnmaskedText(obj) // This function unmasks text (added for the getValue function in Message.js) 
{
	var tempStateInfo = boxes[obj.name];//get this object's state info
	var tempResults = tempStateInfo[1]; //get this object's last match results
	if(tempResults == undefined)
	{
		var box = tempStateInfo[2];
		return box.getAttribute('umValue');
	}
	else
		if (tempStateInfo[4] == "**/**/****" && tempResults.unmaskedText != "")
			return tempResults.maskedText;
		else
			return tempResults.unmaskedText; //set obj's value as last match's valid input (not masked text)
}


/* submitMasks - Replaces all of input boxes using input masking on the page with it's valid input (not masked text)
 * Note: Should always be called onSubmit of the page using input masking.
*/
function submitMasks()
{	var allBoxes = document.getElementsByTagName("input");	//get all input boxes
	for(i = 0; i < allBoxes.length; i++) //foreach box
	{	var obj = allBoxes[i];

		if(obj.type == "text" && obj.getAttribute('realExp') != "" && obj.value != "") //if this text box has a mask with valid input
		{
			//We now send masked text to the server.  It handles unmasking it.
			//var tempStateInfo = boxes[obj.name];//get this object's state info
			//var tempResults = tempStateInfo[1]; //get this object's last match results
			//obj.value = tempResults.validInput; //set obj's value as last match's valid input (not masked text)
		}
		else if (obj.type=="checkbox")
		{// look at check boxes too, since IE uses ON instead of true
			if (obj.checked)
				obj.value = true;
			else
			{
				/*var newElement = document.createElement("INPUT");
				newElement.value = "false";
				newElement.name = obj.name;
				newElement.type = "hidden";
				obj.name ="";
				obj.insertAdjacentElement("afterEnd", newElement);*/
			}
		}
	}
}

/* Sets the inputBox value to our lastMatch's maskedText, and sets the selection range*/
function /*void*/ updateMask()
{
	inputBox.value = lastMatch.maskedText;
	var caret = lastMatch.insertPoint;
	if(allSelected)
	{	//setSelectionRange(caret, caret + 1*/[0-9A-Za-z_]/.test(lastMatch.maskedText.substring(caret, caret + 1)));
		setSelectionRange(caret, caret);
	}
	else
	{	setSelectionRange(0, lastMatch.maskedText.length);
		allSelected = true;
	}
}
												///////////////////////
												///      MAIN	   	///
												///				   	///
												/// CURSOR METHODS 	///
												///////////////////////

/* setSelectionRange - Selects text in the input box, or moves the caret if both parameters the same
 * selectionStart: Index in inputBox to move the caret and start selection
 * selectionEnd: Index in inputBox to end selection
*/
function /*void*/ setSelectionRange(/*int*/ selectionStart, /*int*/ selectionEnd)
{
	if(!initing)
	{
		try {
			if (inputBox.createTextRange)
			{	var range = inputBox.createTextRange();
				if (range != null) {
					range.collapse(true);
					range.moveEnd('character', selectionEnd);
					range.moveStart('character', selectionStart);
					range.select();  //during first load, don't fly through all the boxes (graphical html glitch)
				}
			}
			else if (inputBox.setSelectionRange)
			{	inputBox.setSelectionRange(selectionStart, selectionEnd);
			}
		}
		catch (ex) {
			//alert("fail!!");
			// Why do we need to reload the entire page when this fails?
			// Is there a way to get around this? What problems does it cause
			// if it fails?
			//submitForm();
		}
	}
}

												///////////////////////
												/// CURSOR METHODS 	///
												///				   	///
												/// PARSING METHODS ///
												///////////////////////

												///////////////////////
												/// PARSING METHODS	///
												///				   	///
												///     RealExp		///
												///////////////////////


												///////////////////////
												///     RealExp		///
												///				   	///
												///   MatchResult	///
												///////////////////////

/* CLASS - MatchResult - A container for a variety of information needed when validating
 * maskText:	Text that this node has masked
 * valInput:	User-entered text that this node has determined to be valid
 * insPoint:	Number of spaces that this node wishes to move the insertpoint
*/
function MatchResult(/*String*/maskText, /*String*/valInput, /*Integer*/ insPoint)
{
	this.maskedText = maskText;
	this.unmaskedText = valInput;
	this.insertPoint = insPoint;
}

/* toString - Returns a string listing all the information of this match
 * return: String representation of each match property
*/
MatchResult.prototype.toString = function()
{
	//Output all properties of the match result.
	var out = 'MatchResult {';
	for(var i in this)
	{
		out += ' ' + i + ':' + this[i];
	}
	out += ' }';
	return out;
	
}

												///////////////////////
												///   MatchResult	///
												///				   	///
												///      Stack		///
												///////////////////////

/* CLASS - Stack - Used for parsing
 * Contains five generic stack functions: peek, push, pop, isEmpty, and toString
 * Also has two parse-specific methods, getTopRealExp and concatenate, to make parsing easier
 * params: A list of params will be stacked (left-most becomes bottom-most, etc.)
*/
function /*Stack*/ Stack()
{
	this.items = new Array();
	this.index = 0;
	for(var i=0; i < arguments.length; i++)
	{	this.items[this.index++] = arguments[i];
	}
}
/* peek - Returns the a reference to the top item on the Stack*/
Stack.prototype.peek =	/*Object*/ function()
{	return this.items[this.index];
}
/* push - Places the obj on top of the stack */
Stack.prototype.push = /*void*/ function(/*Object*/ obj)
{	this.items[this.index++] = obj;
}
/* pop - Removes and returns the top item from the Stack */
Stack.prototype.pop = /*Object*/ function()
{	if(this.index > 0) return this.items[--this.index];
	else return null;
}
/* isEmpty - Returns true iff there are no items on the Stack */
Stack.prototype.isEmpty = /*boolean*/ function()
{	return (this.index == 0);
}
/* toString - Returns a listing of items on the stack (bottom to top) in a left-to-right string */
Stack.prototype.toString = /*String*/ function()
{
	var retStr = "";
	for(var i=0; i < this.index; i++)
	{	retStr += (this.items[i].toString() + ", ");
	}
	if(retStr.length > 2) retStr = retStr.substring(retStr.length -2);
	return retStr;
}
/* getTopRealExp - Returns an epsilon literal if there are no RealExps on the stack, the top RealExp otherwise */
Stack.prototype.getTopRealExp = /*RealExp*/ function()
{
	if(this.index == 0) return new RealExp("L", "", "", null, null); //epsilon
	else return this.pop();
}
/* concatentate - Concatenates all items on the stack together, and places that RealExp on top of the Stack */
Stack.prototype.concatenate = /*void*/ function()
{
	if(this.isEmpty()) return;

	var temp = this.getTopRealExp();
	while(!this.isEmpty())
		temp = new RealExp("C", "+", "", this.getTopRealExp(), temp);

	this.push(temp);
}

												///////////////////////
												///      Stack		///
												///				   	///
												///      Hash		///
												///////////////////////

/* CLASS - Hash - Used to store unique box IDs for each input box for persistent state into
*/
function Hash()
{
	this.hashMap = new Array();
	this.length = 0;
}
/* set - (Re)sets key to the value of item in the Hash */
Hash.prototype.set = /*void*/ function(/*Object*/key, /*Object*/item)
{
	if(typeof(this.hashMap[key]) == undefined) this.length++
	this.hashMap[key] = item;
}
/* get - Returns a reference to the value of the key, undefined key is not present */
Hash.prototype.get = /*Object*/ function(/*Object*/ key)
{
	return this.hashMap[key];
}
/* remove - Deletes the key from the Hash */
Hash.prototype.remove = /*void*/ function(/*Object*/ key)
{	if(typeof(this.hashMap[key]) != undefined)
	{	this.length--;
		delete this.hashMap[key];
	}
}
												///////////////////////
												///       Hash		///
												///				   	///
												///       END		///
												///////////////////////

/* isValidString - can be used to determine if the given character is valid for the current mask.
 * charCode: Specific unicode value to validate.
 * returns:	true if this character is valid for the mask.  False otherwise.
 *
 * Note:: This function differs from validateChar in TWO IMPORTANT WAYS::
 *     1) This function does not input the character
 *     2) This function does not determine if the character is valid for the "NEXT"
 *            character but rather, whether the input string is valid for the "FIRST"
 *            n characters in the input.
 */
function /*boolean*/ isValidString(/*String*/ inputStr) 
{
	if(inputStr != "")	//if validating an actual character
	{
		//var tempResults = inputMask.validate(inputStr); //get validation

		//if(tempResults.status > INVALID) //if the character added to existing input is acceptable
		//{
			return true;
		//}
		//else
		//{
		//	return false;
		//}
	}
}

function /*integer*/ getNumPlaceHolders()
{
	var num = 0;
	
	if (inputMask == null || inputMask == "")
		return 9999;
	
	for (var i = 0; i < inputMask.length; i++)
	{	if (inputMask.charAt(i) == '*')
			num = num + 1;
	}
	
	return num;
}

function /*Boolean*/ startsWithNum(/*string*/ text)
{
	var char = text.charAt(0);
	if (char == "1" || char == "2" || char == "3" || char == "4" || char == "5" || char == "6" || char == "7" || char == "8" || char == "9" || char == "0")
		 return true;
	else
		return false;
}

function findMatch(/*string*/ text)
{
	//alert(regExp + " " + text);
	var results = regExp.exec(text);
	if (!results)
	{	return false;
	}
	else
	{
		for (var i = 0; i < results.length; i++)
		{	
			/*if (text.length >= 2)
			{	alert(results[i] + " " + text)
		
			}*/
			if (results[i] == text)
				return true;
		}
	}
	
	return false;
}

function findMatchInProg(/*string*/ text)
{
	//alert(inProgExp);
	var results = inProgExp.exec(text);
	if (!results)
	{	return false;
	}
	else
	{
		for (var i = 0; i < results.length; i++)
		{		
			if (results[i] == text)
				return true;
		}
	}
	
	return false;
}

function setHasChanged(element)
{	
	hasChanged = true;
}

function setCurLoc(newLoc) {
	curLoc = newLoc;
}

