var regexOnlyMaskChars = /[^\@#]/;

var MASK_CHAR_ALPHA = "@";
var MASK_CHAR_NUMERIC = "#";
var MASK_PROMPT_CHAR = "_";

/**
	Key Codes for keystrokes that we are providing
	different functionality for.
*/
var KEY_CODE_DELETE = 8;
var KEY_CODE_TAB = 9;
var KEY_CODE_ENTER = 13;
var KEY_CODE_END = 35;
var KEY_CODE_HOME = 36;
var KEY_CODE_LEFT_ARROW = 37;
var KEY_CODE_RIGHT_ARROW = 39;
var KEY_CODE_BACKSPACE = 46;

var ERROR_NOTIFICATION_TIME_OUT = 150; 	// this is used when an invalid is detected, the color of the text is changed to
										// ERROR_TEXT_COLOR and then reverted back to REGULAR_TEXT_COLOR after the timeout
										// timeout is given in milliseconds
var ERROR_TEXT_COLOR = "#FF0000";		// RED - color to change the text in the field when an error is detected
var REGULAR_TEXT_COLOR = "#000000";		// BLACK - color to revert back text in the field

var currentControl = undefined;			// the current control that is being filled in

function doMask(ctrl, hiddenCtrl, mask, evt) {
	// find the keycode that was pressed
	var keyCode = evt.which ? evt.which : evt.keyCode;

	// if the control is readonly return right away
	if (ctrl.readOnly) {
		return false;
	}

	// the keys that are let to pass through and the event is not handled here
	if(keyCode == KEY_CODE_ENTER || keyCode == KEY_CODE_TAB) {
		return;
	}

	var hasDataModelChanged = false;				// denotes whether the data model has been changed or not
	var isDataCharacter = new Array(mask.length); 	// stores boolean at each location, which states whether its a maskable character (# or @)
	var dataArray = new Array(mask.length);			// array that stores the actual data which is split character at a time
	var character;

	// iterate through the mask and parse the data and at the same time figure out
	// which positions in the mask should have characters and which characters are
	// just used for display purposes
	for (var i = 0; i < mask.length; i++) {
		character = mask.charAt(i);
		isDataCharacter[i] = (character == MASK_CHAR_ALPHA || character == MASK_CHAR_NUMERIC);

		if (isDataCharacter[i]) {
			if (ctrl.value.length > i) {
				dataArray[i] = ctrl.value.charAt(i);
			}
			else {
				dataArray[i] = MASK_PROMPT_CHAR;
			}
		}
		else {
			dataArray[i] = character;
		}
	}

	// now process events
	var selectionText;
	var cursorPosition;

	// figure out the cursor/caret position in the text field
	if (document.selection && document.selection.createRange) {  // IE on Windows
		var selection = document.selection.createRange();
		selectionText = selection.text;
		cursorPosition = ctrl.value.length;

		// replace the selection w/ a sentinel so we can figure out the cursor position
		var temp = ctrl.value;
		selection.text = "?"
		cursorPosition = ctrl.value.indexOf("?");
		ctrl.value = temp.replace(/\?/gi, "");
	}
	else if (ctrl.setSelectionRange){ // FireFox on PC & Mac, Netscape Support
		var endPosition = ctrl.selectionEnd;
		cursorPosition = ctrl.selectionStart;

		if (cursorPosition != endPosition) {
			selectionText = ctrl.value.substring(cursorPosition, endPosition);
		}
		else {
			selectionText = "";
		}
	}
	else { // find the next valid position
		cursorPosition = 0;

		while (cursorPosition < dataArray.length) {
			if (isDataCharacter[cursorPosition] && dataArray[cursorPosition] == MASK_PROMPT_CHAR) {
				break;
			}
			else {
				cursorPosition++;
			}
		}
	}

	if (keyCode == KEY_CODE_DELETE || keyCode == KEY_CODE_BACKSPACE) {
		// if either a delete or backspace key is detected remove appropriate data from
		// our data model (the dataArray)
		if (keyCode == KEY_CODE_DELETE) {
			cursorPosition--;
		}

		if (selectionText.length <= 1) {
			// if the number of characters selected is 0 or 1, then replace the text
			// in the current cursor position with the MASK_PROMPT_CHAR which is an
			// underscore
			if (isDataCharacter[cursorPosition]) {
				dataArray[cursorPosition] = MASK_PROMPT_CHAR;
			}
		}
		else {
			// if more than 1 character is selected, loop and replace the data in the model
			// with MASK_PROMPT_CHAR which is an underscore
			for (var i = cursorPosition; i <= (cursorPosition + selectionText.length); i++) {
				if (isDataCharacter[i])	{
					dataArray[i] = MASK_PROMPT_CHAR;
				}
			}
		}

		if (keyCode == KEY_CODE_BACKSPACE) {
			cursorPosition++;
		}

		hasDataModelChanged = true;
	}
	else if (keyCode == KEY_CODE_LEFT_ARROW) {
		// if left arrow key is detected, then move the cursor to the left by one
		while (cursorPosition >= 0) {
			if (isDataCharacter[--cursorPosition]) {
				break;
			}
		}
	}
	else if (keyCode == KEY_CODE_RIGHT_ARROW) {
		// if right arrow key is detected, then move the cursor to the right by one
		while (cursorPosition < dataArray.length) {
			if (isDataCharacter[++cursorPosition]) {
				break;
			}
		}
	}
	else if (keyCode == KEY_CODE_HOME) {
		cursorPosition = 0;
	}
	else if (keyCode == KEY_CODE_END) {
		cursorPosition = dataArray.length;
	}
	else if (keyCode == 0) {  // handles any event where there is no key pressed, ie.  onFocus
		cursorPosition = 0;
		// find the next valid place to put the character and set the cursor there
		while (cursorPosition < dataArray.length) {
			if (isDataCharacter[cursorPosition]) {
				break;
			}
			else {
				cursorPosition++;
			}
		}
	}
	else {
		// get character from keyCode
		var keyCharacter = getKeyCode(keyCode);

		// find the next valid place to put the character and set the cursor there
		while (cursorPosition < dataArray.length) {
			if (isDataCharacter[cursorPosition]) {
				break;
			}
			else {
				cursorPosition++;
			}
		}

		// check if key is a alphabet or digit (only those 2 are allowed for now)
		if ((mask.charAt(cursorPosition) == MASK_CHAR_ALPHA && regexOneOrMoreChar.test(keyCharacter)) ||
			(mask.charAt(cursorPosition) == MASK_CHAR_NUMERIC && regexOneOrMoreDigits.test(keyCharacter))) {
			if (cursorPosition < dataArray.length) {
				while (cursorPosition < dataArray.length) {
					if (isDataCharacter[cursorPosition]) {
						dataArray[cursorPosition] = keyCharacter;
						break;
					}
					else {
						cursorPosition++;
					}
				}
			}

			// find the next valid place to put the character and set the cursor there
			while (cursorPosition < dataArray.length) {
				if (isDataCharacter[++cursorPosition]) {
					break;
				}
			}

			hasDataModelChanged = true;
		}
		else { // if the character we are handling is invalid, change the color of the text to red for ERROR_TIME_OUT
			var navigatorInfo = navigator.userAgent.toLowerCase()
			var notifyError = true;

			if (navigatorInfo.indexOf("mac") != -1 && navigatorInfo.indexOf("msie") != -1) {
				notifyError = false;
			}

			if (notifyError) { // not supported for mac IE
				ctrl.style.color = ERROR_TEXT_COLOR;
				currentControl = ctrl;
				colorChangeTimeout = setTimeout("resetFieldColor()", ERROR_NOTIFICATION_TIME_OUT);
			}
		}
	}

	// reset the view only if the model has changed
	if (hasDataModelChanged)
	{
		ctrl.value = "";
		hiddenCtrl.value = "";
		var blankSpots = 0;
		var totalDataCharacters = 0;
		for (var i = 0; i < dataArray.length; i++) {
			ctrl.value += dataArray[i];

			if (isDataCharacter[i])
			{
				if (dataArray[i] == MASK_PROMPT_CHAR)
				{
					hiddenCtrl.value += " ";
					blankSpots++;
				}
				else
				{
					hiddenCtrl.value += dataArray[i];
				}

				totalDataCharacters++;
			}
		}

		// if no text is entered into the field, set the field to blank rather than a set of blank spaces
		if (blankSpots == totalDataCharacters)
		{
			hiddenCtrl.value = "";
		}
	}

	setCaretAtEnd(ctrl, cursorPosition);

	// return false because we have successfully handled the character that was entered
	return false;
}

function onKeyPressMask(ctrl, evt) {
	var keyCode = evt.which ? evt.which : evt.keyCode;
	return (keyCode == 13 || keyCode == 9);
}

function getKeyCode(key) {
	switch(key) {
		// from 96 - 105 are the keypad keycodes
		case 96: return "0"; break;
		case 97: return "1"; break;
		case 98: return "2"; break;
		case 99: return "3"; break;
		case 100: return "4"; break;
		case 101: return "5"; break;
		case 102: return "6"; break;
		case 103: return "7"; break;
		case 104: return "8"; break;
		case 105: return "9"; break;
		default: return String.fromCharCode(key); break;
	}
}

function resetFieldColor() {
	if (colorChangeTimeout != undefined) {
		clearTimeout(colorChangeTimeout);
	}

	if (currentControl != undefined) {
		currentControl.style.color = REGULAR_TEXT_COLOR;
	}

	currentControl = undefined;
}

String.prototype.MaskValue = function(mask) {
	var retVal = mask;
	var val = this;

	//loop thru mask and replace # with current value one at a time
	var pos = 0;
	for(var i = 0; i < val.length; i++)
	{
		pos = GetNextMaskPosition(mask, pos);

		if (mask.charAt(pos) == MASK_CHAR_NUMERIC)
		{
			retVal = retVal.replace(/#/i, val.charAt(i));
			pos++;
		}
		else if (mask.charAt(pos) == MASK_CHAR_ALPHA)
		{
			retVal = retVal.replace(/@/i, val.charAt(i));
			pos++;
		}
	}

	// replace the rest of # with _ as place holder
	retVal = retVal.replace(/#/gi, "_");
	retVal = retVal.replace(/@/gi, "_");

	return retVal;
}

// returns the next valid mask position
function GetNextMaskPosition(mask, startPos)
{
	if (mask.length < startPos)
		return -1;

	for (var i = startPos; i < mask.length; i++)
	{
		if (mask.charAt(i) == MASK_CHAR_ALPHA || mask.charAt(i) == MASK_CHAR_NUMERIC)
			return i;
	}

	return;
}

// displays the formatted date according to locale rules
function Mask_DisplayFomattedDate(ctrl)
{
	if (ctrl.value.length == 0)
	{
		ctrl.value = DATE_FORMAT;
	}
	else
	{
		var splits = ctrl.value.split(DATE_SPLITTER);

		if (splits.length == 3 && regexOneOrMoreDigits.test(splits[0]) && regexOneOrMoreDigits.test(splits[1]) && regexOneOrMoreDigits.test(splits[2]))
		{
			var date = Format_ParseDate(ctrl.value);

			if (date != undefined) {
			ctrl.value = Format_GetDay(date.getDate(), (date.getMonth() + 1), date.getFullYear());
		}
			else {
				ctrl.value = "";
			}
		}
	}
}

// updates an update to the field
function Mask_UpdateContent(ctrlName, value)
{
	// mask the value provided to the function and then set it as the value for the mask control
	if (document.forms[0].elements["__MASK__" + ctrlName] != undefined) {
		document.forms[0].elements["__MASK__" + ctrlName].value = value.MaskValue(document.forms[0].elements["__MASK_FORMAT__" + ctrlName].value);
	}

	// set the hidden field value
	document.forms[0].elements[ctrlName].value = value;
}

// disable/enable mask control
function Mask_EnableControl(ctrlName, isEnabled)
{
	if (document.forms[0].elements["__MASK__" + ctrlName] != undefined) {
		document.forms[0].elements["__MASK__" + ctrlName].disabled = !isEnabled;
	}
}

// set the focus for the mask control
function Mask_FocusControl(ctrlName)
{
	document.forms[0].elements["__MASK__" + ctrlName].focus();
}

// update the readonly property
function Mask_MakeReadOnly(ctrlName, isReadOnly)
{
	document.forms[0].elements["__MASK__" + ctrlName].readOnly = isReadOnly;
}

// returns the model value
function Mask_GetModelValue(ctrlName)
{
	return document.forms[0].elements[ctrlName].value;
}

// returns the view value
function Mask_GetViewValue(ctrlName)
{
	return document.forms[0].elements["__MASK__" + ctrlName].value;
}

// returns the mask
function Mask_GetMask(ctrlName)
{
	return document.forms[0].elements["__MASK_FORMAT__" + ctrlName].value;
}

// returns the mask
function Mask_GetMask(ctrlName)
{
	return document.forms[0].elements["__MASK_FORMAT__" + ctrlName].value;
}

//Updates the hidden fields for the postal mask by passing the form's html objects
function Mask_UpdatePostalHiddenFields(postalFieldName, fsaFieldName, lduFieldName){
	eval(fsaFieldName).value = eval(postalFieldName).value.substring(0,3);
	eval(lduFieldName).value = eval(postalFieldName).value.substring(3,6);
}
