// ****************************************************
// PLEASE READ THIS BEFORE EDITING ANY OF THE MASK CODE
// ****************************************************
//
// The code below to handle edit masks is complicated. 
// When editing this code, please be careful, read the comments and if you
// do not understand the code, please ask Andrew.
// 
// There are certain behaviours that do not work:
//  - Pasting will break the textbox
//  - Selecting and overtyping characters will break the textbox
//  
// There are also limitations with mask strings that contain ambiguous 
// wildcards adjacent to one another. For example, the mask string:
// 		___AAA### 
// will not allow you to type the string:
// 		aA1
// Even though this string is valid, the code is matching the upper-case A to 
// the second "_" character not to the first "A". Similar cases occur wherever
// consecutive wildcards' content overlap and there is a character following, e.g.:
// 
//	Mask String		Valid Input 	Works?
//  =======================================
//	__**##			aa1				No
//  AA**-			BB+				No
//  ##**__			21b				No
//
// This fails because when validating as you type, the code will always match to 
// the current wildcard before trying the next wildcard. Additionally it will not 
// backtrack.
//
// The above applies only when dealing with per keystroke validation. Any valid 
// string will be accepted when tabbing away because the regular expression is 
// always correct.
function EditMaskHandler(element, editMask)
{
	// Javascript keycodes
	var KEY_BACKSPACE = 8;
	var KEY_TAB = 9;
	var KEY_DELETE = 46;
	var KEY_LEFT_ARROW = 37;
	var KEY_RIGHT_ARROW = 39;
	
	this.Element = element;	// Reference to the text box the mask applies to
	
	this.ValidateOnKeyPress = true;	// Indicates whether the string should be validated on keypress
	
	// Attach to the element
	element.InfraEditMaskHandler = this;
	
	this.Mask = new EditMask(editMask);
	this.Loaded = (element.value.length == 0); // Consider loaded if the input is empty
	
	// Handle non delete/backspace keypresses
	// Use this instead of keydown as keydown doesn't return 
	// correct character codes.
	this.OnKeyPress = function (event)
	{
		// If we aren't validating on keypresses, return immediately and allow the keypress
		if (!this.EnableValidateOnKeyPress()) return true;
		
		if (!event) event = window.event;
		if (!event) return 0;

		var keycode = (event.keyCode ? event.keyCode : event.which);

	
		// Firefox stuff:
		// Keypress fires for these keys too
		if (keycode == KEY_BACKSPACE || keycode == KEY_DELETE || keycode == KEY_LEFT_ARROW || keycode == KEY_RIGHT_ARROW) return true;
		// KeyPress fires for tabs before onblur
		if (keycode == KEY_TAB) return this.OnBlur(null);
		
		var c = String.fromCharCode(keycode);
		var cursorPos = GetSelection(this.Element);
		var ret = false;
		
		// currentIndex never points at a literal. It is assumed all literals are automatically added
		var currentIndex = this.Mask.GetElementIndexByCursorPosition(cursorPos.start);
		var currentElement = this.Mask.Bits[currentIndex];
		
		if (!currentElement) return ret;
		
		// Case where the string starts with a literal (i.e. the first wildcard is at position 1)
		if (this.Element.value.length == 0 && currentIndex == 1)
		{	// First character entered
			var firstLiteral = this.Mask.Bits[0];
			
			// Put the literal in if the character entered was either the literal or 
			// something matching the first wildcard. 
			if ((firstLiteral.CanAccept(c) || currentElement.CanAccept(c)) && !firstLiteral.InString)
			{
				this.Element.value += firstLiteral.Consume(c);
			}
		}
		
		if (currentElement.IsComplete())
		{
			var nextElement = this.Mask.Bits[currentIndex + 1];
			if (nextElement && nextElement.Literal)
			{
				var nextWildcard = this.Mask.Bits[currentIndex + 2];
				if (nextWildcard)
				{
					if (nextWildcard.CanAccept(c))
					{
						nextWildcard.Consume(c);
						ret = true;
						if (!nextElement.InString) 
						{
							this.Element.value += nextElement.Consume(c);
						}
						else 
						{
							var newpos = cursorPos.start + nextElement.Literal.length;
							SetPosition(this.Element, newpos, newpos);
						}
					}
				}
				else
				{
					if (!nextElement.InString) this.Element.value += nextElement.Consume(c);
				}
			}
			else if (nextElement && !nextElement.Literal)
			{
				if (nextElement.CanAccept(c))
				{
					nextElement.Consume(c);
					ret = true;
				}
			}
		}
		else if (currentElement.CanAccept(c)) 
		{
			currentElement.Consume(c);
			ret = true;
			
			if (currentElement.IsComplete())
			{
				var nextElement = this.Mask.Bits[currentIndex + 1];
				if (nextElement && nextElement.Literal && !nextElement.InString)
				{
					this.Element.value += c + nextElement.Consume(c);
					ret = false;	// Manually append the entered character otherwise the key press will appear after the literal
				}
			}
		}
		else if (this.Mask.FixedLength)
		{
			// If it's fixed length and the current element isn't complete, and the 
			// current element can't accept this character then the input is invalid
			ret = false;
		}
		else if (!currentElement.IsAtStart())
		{
			// If we get here then the current element is not complete, can't accept c and is not  
			// at the start then need to check if something following it can accept the input
			
			var nextElement = this.Mask.Bits[currentIndex + 1];
			if (nextElement && nextElement.CanAccept(c))
			{
				if (nextElement.Literal)
				{
					if (!nextElement.InString) this.Element.value += nextElement.Consume(c);
					ret = false;
				}
				else
				{
					nextElement.Consume(c);
					ret = true;
				}	
			}
			else
			{
				ret = false;
			}
		}
				
		return ret;
	}
	
	// Handle delete and backspace. These do not cause keypress to fire
	this.OnKeyDown = function (event)
	{
		// If we aren't validating on keypresses, return immediately and allow the keydown
		if (!this.EnableValidateOnKeyPress()) return true;
		
		if (!event) event = window.event;
			if (!event) return 0;

		var keycode = (event.keyCode ? event.keyCode : event.which);

		// Firefox fires onkeydown before onblur
		if (keycode == KEY_TAB) return this.OnBlur(null);
		
		var cursorPos = GetSelection(this.Element);
		
		// Return quickly if the key wasn't something we handle
		if (keycode != KEY_BACKSPACE && keycode != KEY_DELETE) return true;
	
		var c = String.fromCharCode(keycode);

		// By default return true so that the keypress event is fired.
		var ret = true;
		
		if (keycode == KEY_BACKSPACE)
		{
			if (cursorPos.start > 0)
			{
				var currentIndex = this.Mask.GetAnyIndexByCursorPosition(cursorPos.start);
				var currentElement = this.Mask.Bits[currentIndex];
				
				if (cursorPos.start == this.Element.value.length)
				{
					// At end of string
					if (currentElement.Literal)
					{
						this.Element.value = this.Element.value.slice(0, -(currentElement.UnConsume(c)));
						ret = false;
					}
					else
					{
						currentElement.UnConsume(c);
					}
				}
				else 
				{	// Somewhere inside the string
					if (currentElement.Literal)
					{
						if ((this.Mask.GetStringLengthToCurrent(cursorPos.start) + currentElement.Literal.length) < this.Element.value.length)
						{
							// Inside a literal, but there are things after it. Do not allow backspace
							ret = false;
						}
						else
						{
							// Inside a literal and the literal is the last thing in the input string.
							// Backspace OK.
							this.Element.value = this.Element.value.slice(0, -(currentElement.UnConsume(c)));
							ret = false;
						}
					}
					else
					{
						currentElement.UnConsume(c);
					}
				}
			}
		}
		else if (keycode == KEY_DELETE)
		{
			// Only care if the cursor is somewhere inside the string
			if (cursorPos.start < this.Element.value.length)
			{
				var currentIndex = this.Mask.GetIndexAfterCursor(cursorPos.start);
				var currentElement = this.Mask.Bits[currentIndex];
				
				if (!currentElement.Literal)
				{
					currentElement.UnConsume(c);
				}
				else
				{
					if ((this.Mask.GetStringLengthToCurrent(cursorPos.start) + currentElement.Literal.length) < this.Element.value.length)
					{
						// Inside a literal, but there are things after it. Do not allow delete
						ret = false;
					}
					else
					{
						// Inside a literal and the literal is the last thing in the input string.
						// Delete OK.
						this.Element.value = this.Element.value.slice(0, -(currentElement.UnConsume(c)));
						ret = false;	// Also supress delete as the code will remove the literal
					}
						
				}
				
			}
		}
		
		return ret;
	}
	
	// Validate the complete string. Does not allow the string
	// to be partially complete.
	this.OnBlur = function (event)
	{
		var valid = true;
		// Allow people to tab through
		if (this.Element.value.length > 0)
		{
			var valid = this.Mask.ToRegex().test(this.Element.value);
			if (!valid)	this.Element.focus();
		}
		return valid;
	}
	
	
	// Tries to the input string into the mask when receiving focus.
	//
	// If the input string is an empty string, the input is considered 
	// loaded and keypress validation is enabled.
	//
	// Note that the default state of this.Loaded is true when the element's
	// value is an empty string, hence it is not set by this method.
	//
	// If the input string is valid (tested using the Regex) then:
	//  - The input string is loaded into the mask objects
	//  - Keypress validation is enabled
	//
	// If the input string is not valid then:
	//  - The input string is not loaded into the mask objects
	//  - Keypress validation is disabled
	//
	// This will check if the input has been loaded every time the control
	// gains focus. If it is already loaded, nothing happens.
	//
	// Note that this only applies to loading the input stringinto the mask 
	// objects. The mask string itself is loaded when this object is constructed.
	this.OnFocus = function (event)
	{
		if (!this.Loaded)
		{
			var valid = true;
			var inputstring = this.Element.value;
		
			if (inputstring.length > 0) valid = this.Mask.ToRegex().test(inputstring);
		
			if (valid)
			{
				if (this.Mask.Load(inputstring))
				{
					this.ValidateOnKeyPress = true;
					this.Loaded = true;
				}
			}
			else
			{
				this.ValidateOnKeyPress = false;
			}
		}
		
		return true;
	}
			
	// Check if keypress validation should be enabled (i.e. the length of the input is 0).
	// Returns true if keypress validation is on. False otherwise. 
	//
	// The return value does not indicate whether the state of keypress validation has changed.
	this.EnableValidateOnKeyPress = function()
	{
		if (this.Element.value.length == 0) 
		{
			this.ValidateOnKeyPress = true;
			this.Mask.Reset();
		}
		return this.ValidateOnKeyPress;
	}
	
	// Remove the try/catch when debugging. 
	element.onkeypress 	= function (event) { try { return this.InfraEditMaskHandler.OnKeyPress(event); } catch(ex) {} };
	element.onkeydown 	= function (event) { try { return this.InfraEditMaskHandler.OnKeyDown(event); } catch(ex) {} };
	element.onblur 		= function (event) { try { return this.InfraEditMaskHandler.OnBlur(event); } catch(ex) {} };
	element.onfocus 	= function (event) { try { return this.InfraEditMaskHandler.OnFocus(event); } catch(ex) {} };
	
	element.onpaste		= function () 		{ try { event.returnValue = false; } catch(ex) {} }

}

// A basic string reader. Initially positioned one character before
// the first character of the string. 
function MaskStringReader(maskString)
{
	var position = -1;
	var length = maskString.length;
	var lastIndex = length - 1;
	
	this.MaskString = maskString;
	
	// Return the next character and advance the reader.
	// Returns null when at the end of the string
	this.Next = function()
	{
		if (position == lastIndex) return null;
		position++;
		return maskString.charAt(position);
	}
	
	// Return the next character without advancing the reader
	// Returns null when at the end of the string
	this.PeekNext = function()
	{
		if (this.EOF()) return null;
		return maskString.charAt(position + 1);
	}
	
	this.EOF = function ()
	{
		return position == lastIndex;
	}
	
	this.GetPosition = function()
	{
		return position;
	}	
}


function EditMask(maskString)
{
	// Mask string wildcards
	var NUMBER = "#";
	var ALPHA = "_";
	var UPPER = "A";
	var ANY = "*";
	var FIXED = "^";
	
	// Regexp equivalents for mask string wildcards
	var REXP_LETTERS = "[A-Za-z\\xC0-\\xFF]?";
	var REXP_NUMBERS = "[0-9]?";
	var REXP_ANY = ".?";
	var REXP_UPPER_LETTER = "[A-Z]?";

	// Regular expression form of the mask
	this.Regex= null;
	
	// Index of the current Mask Element that the string is in
	var currentIndex = 0;
	
	this.MaskString = maskString;
	this.Bits;
	
	var reader = new MaskStringReader(maskString);
	this.FixedLength = false;	// Indicates that the input should match the mask length (i.e. ^)
	this.Bits = new Array();
	
	while (!reader.EOF())
	{
		var nextChar = reader.Next();
		if (nextChar == FIXED)
		{
			this.FixedLength = true;
		}
		else if (nextChar == NUMBER)
		{
			this.Bits[this.Bits.length] = ReadNumber(reader);
		}
		else if (nextChar == ALPHA)
		{
			this.Bits[this.Bits.length] = ReadAlpha(reader);
		}
		else if (nextChar == UPPER)
		{
			this.Bits[this.Bits.length] = ReadUpper(reader);
		}
		else if (nextChar == ANY)
		{
			this.Bits[this.Bits.length] = ReadAny(reader);
		}
		else
		{
			this.Bits[this.Bits.length] = ReadLiteral(reader);
		}
	}
	
	reader = null;
		
	function ReadNumber(reader)
	{
		var count = 1;	// At least one otherwise we wouldn't have got here
		
		while (reader.PeekNext() == NUMBER)
		{
			reader.Next();
			count++;
		}
		
		return new EditMaskNumber(count);
	}
	
	function ReadAlpha(reader)
	{
		var count = 1;
		
		while (reader.PeekNext() == ALPHA)
		{
			reader.Next();
			count++;
		}
		
		return new EditMaskAlpha(count, false);
	}
	
	function ReadUpper(reader)
	{
		var count = 1;
		
		while (reader.PeekNext() == UPPER)
		{
			reader.Next();
			count++;
		}
		
		return new EditMaskAlpha(count, true);
	}
	
	function ReadAny(reader)
	{
		var count = 1;
		
		while (reader.PeekNext() == ANY)
		{
			reader.Next();
			count++;
		}
		
		return new EditMaskAny(count);
	}

	function ReadLiteral(reader)
	{
		var startPos = reader.GetPosition();
		var nextChar = reader.PeekNext();
		
		while (nextChar && nextChar != ANY && nextChar != NUMBER && nextChar != UPPER && nextChar != ALPHA)
		{
			reader.Next();
			nextChar = reader.PeekNext();
		}
		
		var length = (reader.GetPosition() - startPos) + 1;
		
		return new EditMaskLiteral(reader.MaskString.substr(startPos, length));
	}
	
	// Return a mask wildcard element index based on the position of the cursor
	this.GetElementIndexByCursorPosition = function (position)
	{
		var cpos = 0;	// Cummulative position of the cursor given the elements in the mask string
		
		for (var i = 0; i < this.Bits.length; i++)
		{
			var e = this.Bits[i];
			cpos += e.Entered;
			if (!e.Literal && position <= cpos) return i;
		}
		
		return null;
	}
	
	// Return a mask element index based on the position of the cursor
	this.GetAnyIndexByCursorPosition = function (position)
	{
		var cpos = 0;	// Cummulative position of the cursor given the elements in the mask string
		
		for (var i = 0; i < this.Bits.length; i++)
		{
			var e = this.Bits[i];
			cpos += e.Entered;
			if (position <= cpos) return i;
		}
		
		return null;
	}
	
	// Get the element that appears after the cursor
	this.GetIndexAfterCursor = function (position)
	{
		var cpos = 0;	
		
		for (var i = 0; i < this.Bits.length; i++)
		{
			cpos += this.Bits[i].Entered;
			if (cpos > position) return i;
		}
		
		return null;
	}
	
	// Return the length of the input up to but not including the element that the cursor is currently in
	this.GetStringLengthToCurrent = function (position)
	{
		var cpos = 0;	// Cummulative position of the cursor given the elements in the mask string
		
		for (var i = 0; i < this.Bits.length; i++)
		{
			var e = this.Bits[i];
			if (position >= (cpos + e.Entered))
			{
				cpos += e.Entered;
			}
			else
			{
				return cpos;
			}
		}
		
		return null;
	}
	
	// Load a valid input string into the Mask.
	//
	// Use ToRegex to validate the string
	this.Load = function (input)
	{
		currentIndex = 0;
		var stringpos = 0;
		var consumed = null;
		
		while ( (stringpos < input.length) && (consumed = this.LoadCharacter(input.charAt(stringpos)) ) )
		{
			stringpos += consumed.length;
		}
		
		return !(stringpos < input.length);
	}
	
	// Returns the character loaded. May be more than one if it resulted in loading a literal
	this.LoadCharacter = function (c)
	{
		var result = null;
		var currentElement = this.Bits[currentIndex];
	
		// Get the next element if one exists
		var nextElement = null;
		
		if (currentIndex + 1 < this.Bits.length)
		{
			nextElement = this.Bits[currentIndex + 1];
		}
		
		if (!currentElement.IsAtStart())
		{
			if (nextElement && nextElement.Literal && nextElement.CanAccept(c))
			{
				// If the character matches the next literal, favour that
				result = nextElement.Consume(c);
				currentIndex += 2;	// Skip over the literal
			}
			else if (currentElement.CanAccept(c))
			{
				// Otherwise does the character match the current wildcard?
				result = currentElement.Consume(c);
				
				if (currentElement.IsComplete())
				{
					currentIndex++; 	// Move to next element.
					
					// If the current wildcard is now complete, dump out the next literal
					if (nextElement && nextElement.Literal)
					{
						result += nextElement.Consume(null);
						currentIndex++;	// Skip over the literal
					}
				}
			}
			else if (nextElement && nextElement.CanAccept(c))
			{
				// Matches next wildcard
				result = nextElement.Consume(c);
				currentIndex++;	// Move position onto nextElement
				
				if (nextElement.IsComplete())
				{
					if (currentIndex + 1 < this.Bits.length)
					{
						currentIndex++; 	// Move to element after next element.
					
						// Get element after nextElement and see if it is a literal
						var nextNextElement = this.Bits[currentIndex];
						
						if (nextElement && nextElement.Literal)
						{
							result += nextElement.Consume(null);
							currentIndex++;	// Skip over the literal
						}
					}
				}
			}
		}
		else if (currentElement.CanAccept(c))
		{
			// At start of current element
			result = currentElement.Consume(c);
				
			// May be a single wildcard
			if (currentElement.IsComplete())
			{
				currentIndex++; 	// Move to next element.
				
				// If the current wildcard is now complete, dump out the next literal
				if (nextElement && nextElement.Literal)
				{
					result += nextElement.Consume(null);
					currentIndex++;	// Skip over the literal
				}
			}
		}
		
		return result;
	}
	
	// Resets the mask. Sets each mask element's Entered back to 0.
	this.Reset = function()
	{
		for (var i = 0; i < this.Bits.length; i++)
		{
			this.Bits[i].Reset();
		}
	}
	
	// Convert the mask to a regular expression to validate the 
	// contents. Can only be used to validate a complete string.
	this.ToRegex = function ()
	{
		var regexStr = "^";
		
		for (var i = 0; i < this.Bits.length; i++)
		{
			regexStr += this.Bits[i].ToRegex(this.FixedLength);
		}
		
		regexStr += "$";
		
		return new RegExp(regexStr);
	}
}

function GetSelection(field){
	var pos = {start:0, end:0};
	if(field.selectionStart && field.selectionEnd){
		pos.start = field.selectionStart;
		pos.end = field.selectionEnd;
	}
	else if(document.selection){
		var range = field.createTextRange();
		range.setEndPoint("EndToStart", document.selection.createRange());
		pos.start = range.text.length;
		pos.end = document.selection.createRange().text.length;
	}
	return pos;
}

function SetPosition(field, startIdx, endIdx)
{
	if(field.createTextRange)
	{
		var fld= field.createTextRange();
		fld.moveStart("character", startIdx);
		fld.moveEnd("character", field.value.length - startIdx - (endIdx - startIdx));
		fld.select();
	}
	else if (field.setSelectionRange)
	{
		field.setSelectionRange(startIdx, endIdx);
	}
}	

// Represents a number wildcard in a mask 
function EditMaskNumber(count)
{
	// Count of numbers to allow
	this.Count = count;
	
	// Count of numbers currently entered
	this.Entered = 0;
	
	// Returns a value indicating whether the character c can be 
	// accepted by this edit mask element
	this.CanAccept = function (c)
	{
		return this.Entered < this.Count && /[0-9]/.test(c);
	}
	
	// Consume the character c. Assumes that CanAccept has been
	// called and returned true.
	//
	// Returns the character entered
	this.Consume = function (c)
	{
		this.Entered++;
		return c;
	}
	
	// UnConsume a character. Assumes that you are
	// not at the start of a the segment
	//
	// Returns the number of characters removed.
	this.UnConsume = function (c)
	{
		this.Entered--;
		return 1;
	}
		
	this.IsAtStart = function ()
	{
		return this.Entered == 0;
	}
	
	this.HasEnough = function ()
	{
		return this.Entered > 0;
	}
	
	this.IsComplete = function ()
	{
		return this.Entered == this.Count;
	}
	
	this.ToRegex = function(fixedLength)
	{
		return "[0-9]{" + (fixedLength ? "" : "1,") + count.toString() + "}";
	}
	
	this.Reset = function ()
	{
		this.Entered = 0;
	}
}

// Represents an alpha numeric character in a mask.
// Can optionally be limited to upper case characters only.
function EditMaskAlpha(count, upperOnly)
{
	this.Count = count;
	this.Entered = 0;
	this.UpperOnly = upperOnly;
	
	var REXP_LETTERS = /[A-Za-z\xC0-\xFF]/i;
	var REXP_UPPER = /[A-Z]/;
	
	var REXP_STR_LETTERS = "[A-Za-z\\xC0-\\xFF]";
	var REXP_STR_UPPER = "[A-Z]";

	
	// Returns a value indicating whether the character c can be 
	// accepted by this edit mask element
	this.CanAccept = function (c)
	{
		return this.Entered < this.Count && (upperOnly ? REXP_UPPER.test(c) : REXP_LETTERS.test(c));
	}
	
	// Consume the character c. Assumes that CanAccept has been
	// called and returned true.
	//
	// Returns the character entered
	this.Consume = function (c)
	{
		this.Entered++;
		return c;
	}
	
	// UnConsume a character. Assumes that you are
	// not at the start of a the segment
	//
	// Returns the number of characters removed.
	this.UnConsume = function (c)
	{
		this.Entered--;
		return 1;
	}
		
	this.IsAtStart = function ()
	{
		return this.Entered == 0;
	}
	
	this.HasEnough = function ()
	{
		return this.Entered > 0;
	}
	
	this.IsComplete = function ()
	{
		return this.Entered == this.Count;
	}
	
	this.ToRegex = function(fixedLength)
	{
		return (upperOnly ? REXP_STR_UPPER : REXP_STR_LETTERS) + "{" + (fixedLength ? "" : "1,") + count.toString() + "}";
	}
	
	this.Reset = function ()
	{
		this.Entered = 0;
	}
}

// Represents the any character wildcard in a mask
function EditMaskAny(count)
{
	this.Count = count;
	this.Entered = 0;
	
	// Returns a value indicating whether the character c can be 
	// accepted by this edit mask element
	this.CanAccept = function (c)
	{
		return this.Entered < this.Count && /./.test(c);
	}
	
	// Consume the character c. Assumes that CanAccept has been
	// called and returned true.
	//
	// Returns the character entered
	this.Consume = function (c)
	{
		this.Entered++;
		return c;
	}
	
	// UnConsume a character. Assumes that you are
	// not at the start of a the segment
	//
	// Returns the number of characters removed.
	this.UnConsume = function (c)
	{
		this.Entered--;
		return 1;
	}
		
	this.IsAtStart = function ()
	{
		return this.Entered == 0;
	}
	
	this.HasEnough = function ()
	{
		return this.Entered > 0;
	}
	
	this.IsComplete = function ()
	{
		return this.Entered == this.Count;
	}
	
	this.ToRegex = function(fixedLength)
	{
		return ".{" + (fixedLength ? "" : "1,") + count.toString() + "}";
	}
	
	this.Reset = function ()
	{
		this.Entered = 0;
	}
}

// Represents a literal in an edit mask
function EditMaskLiteral(literal)
{
	this.Literal = literal;
	this.Entered = literal.length;
	this.InString = false;	// Indicate whether the literal has been added to the input string.
	
	this.CanAccept = function (c)
	{
		return !this.InString && (this.Literal.charAt(0) == c);
	}
	
	// Consume the literal. Assumes that CanAccept was called and returned true.
	//
	// Returns the literal string
	this.Consume = function (c)
	{
		this.InString = true;
		return this.Literal;
	}
	
	// Unconsume this element. For a literal this means that the
	// entire literal string should be removed.
	//
	// Returns the number of characters removed (i.e. the length of the literal)
	this.UnConsume = function (c)
	{
		this.InString = false;
		return this.Literal.length;
	}
	
	this.ToRegex = function()
	{
		var rule = this.Literal;
		rule = rule.replace(/\[/g, "\\[");
		rule = rule.replace(/\]/g, "\\]");
		rule = rule.replace(/\./g, "\\.");
		rule = rule.replace(/\+/g, "\\+");
		rule = rule.replace(/\$/g, "\\$");
		rule = rule.replace(/\^/g, "\\^");
		rule = rule.replace(/\?/g, "\\?");
		rule = rule.replace(/\(/g, "\\(");
		rule = rule.replace(/\)/g, "\\)");
		rule = rule.replace(/\{/g, "\\{");
		rule = rule.replace(/\}/g, "\\}");
		return rule;
	}	
	
	this.Reset = function ()
	{
		this.InString = false;
	}
}



function CustomHTMLForm( form )
{
   form.Validate = true;

   form.LoadConstraints = function( psFile )
    {
       try
       {
          var XMLDoc = formHelper.LoadXML( psFile );
          var fields = XMLDoc.getElementsByTagName("validation");

           for(var z=0; z < fields[0].childNodes.length; z++ )
           {
              if( fields[0].childNodes[z].nodeType != Node.ELEMENT_NODE ) continue;

              var _Id = fields[0].childNodes[z].getAttribute("id");
              var _Name = fields[0].childNodes[z].getAttribute("name");

              var _Blocks = fields[0].childNodes[z].getAttribute("block");

              var _Personality = fields[0].childNodes[z].getAttribute("personality");
              var _Precision = fields[0].childNodes[z].getAttribute("precision");
              var _MaxLength = fields[0].childNodes[z].getAttribute("maxlength");
              var _MinLength = fields[0].childNodes[z].getAttribute("minlength");

              var _MaxValue = fields[0].childNodes[z].getAttribute("maxvalue");
              var _MinValue = fields[0].childNodes[z].getAttribute("minvalue");

              var _Required = fields[0].childNodes[z].getAttribute("required");

              var _MarkUp = fields[0].childNodes[z].getAttribute("markup_applies_to");

              //fields[0].childNodes[z].getAttribute("rules");

                if( _Id )
                {
                    if( formHelper.GetElementById(_Id) == null ) continue;

                    var ElementBlock = $(_Id).getAttribute("ieblock");

                    if( _Blocks.indexOf( ElementBlock ) > -1 )
                    {
                      if( _Personality ) $(_Id).setAttribute("ismPersonality", _Personality);
                      if( _Precision ) $(_Id).setAttribute("ieprecision", _Precision);
                      if( _MaxLength ) $(_Id).maxLength = _MaxLength;
                      if( _MinLength ) $(_Id).setAttribute("ieminlength", _MinLength);
                      if( _MaxValue ) $(_Id).setAttribute("iemaxvalue", _MaxValue);
                      if( _MinValue ) $(_Id).setAttribute("ieminvalue", _MinValue);
                      if( _Required ) $(_Id).setAttribute("ismMandatory", _Required);
                      if( _MarkUp ) $(_Id).setAttribute("ai_markup_applies_to", _MarkUp);
                  }
                }

                if( _Name )
                {
                    if( document.getElementsByName(_Name) == null ) continue;

                    var Elements = formHelper.GetElementsByAttribute("ai_name", _Name);

                    for(var y=0; y<Elements.length; y++)
                    {
                        //TODO: name cannot be in different BLOCKS
                        var ElementBlock = Elements[y].getAttribute("ieblock");
                        if( _Blocks.indexOf( ElementBlock ) > -1 )
                        {
                          if( _Personality ) Elements[y].setAttribute("ismPersonality", _Personality);
                          if( _Precision ) Elements[y].setAttribute("ieprecision", _Precision);
                          if( _MaxLength ) Elements[y].maxLength = _MaxLength;
                          if( _MinLength ) Elements[y].setAttribute("ieminlength", _MinLength);
                         if( _MaxValue ) Elements[y].setAttribute("iemaxvalue", _MaxValue);
                          if( _MinValue ) Elements[y].setAttribute("ieminvalue", _MinValue);
                           if( _Required ) Elements[y].setAttribute("ismMandatory", _Required);
                         if( _MarkUp ) Elements[y].setAttribute("ai_markup_applies_to", _MarkUp);
                      }
                    }
                }
            }
       }
       catch(ex)
       {
           debugHelper.Exception("CustomHTMLForm.LoadConstraints " + ex.message, document.URL, document.lastModified, document.referrer);
       }
   }

	form.Setup = function()
	{
		try
		{
			for (i = 0; i < this.elements.length; i++)
			{
				var oSrcElement = this.elements[i];
				var sEditMask = oSrcElement.getAttribute('ismEditMask');

				if(oSrcElement.nodeName.toLowerCase() == "fieldset")
				{
                    var strElementType = "fieldset";
                }
                else
                {
             	    var strElementType = oSrcElement.type.toLowerCase();
                }
				if (sEditMask != null && sEditMask.length != "")
				{
					new EditMaskHandler(oSrcElement, sEditMask);
				}

				if (oSrcElement.onkeydown == null && strElementType != "submit" && strElementType != "reset" && strElementType != "textarea")
				{
					oSrcElement.onkeydown = function(event){return CustomFormIgnoreEnter(event)};
				}

				var sPersonality = oSrcElement.getAttribute('ismPersonality');
				if( sPersonality != null )
				{

					switch( sPersonality.toLowerCase() )
					{
					   case 'datetimepicker' :
						  SetupDateTimeDicker(oSrcElement);
						  break ;

					   case 'multitierprofile':
							SetUpDynamicMultiTier(oSrcElement);
							break;
					   case 'singletierprofile':
							SetUpDynamicSingleTier(oSrcElement);
							break;
					}
				}
			}
		}
		catch(ex)
		{
			debugHelper.Exception("CustomHTMLForm.Setup " + ex.message, document.URL, document.lastModified, document.referrer);
		}
		return form;
	}



	return form;

}

function CustomFormIgnoreEnter(event)
{
   if(!event) event = window.event;
   var iKeyCode = String(event.keyCode ? event.keyCode : event.which);
   if (iKeyCode == 13)
   {
		//w3c stop default behavior
		if( event.cancelable ) event.preventDefault();
		//ie stop default behavior
		event.returnValue = false;
		//DOM level 0 stop default behavior
		return false;
	}
}

var MASK_NUMERIC_CHAR = "#";
var MASK_CHARACTER = "_";
var MASK_UPPER_CHARACTER = "A";
var MASK_ANY = "*";
var MASK_ENFORCE_LENGTH = "^"; //at the beginning says that the input value must be the same length as the mask

// This function is still used to validate masks in the designer 
// mask editor window.
function MaskField(oSrcElement, sEditMask)
{
	if (oSrcElement.value.length == 0) return true;  //dont enforce if just tabbing through...

	var IsValid = true;
	var inputValue = oSrcElement.value;
	var EnforceLength = false;

	if (sEditMask.charAt(0) == MASK_ENFORCE_LENGTH)
	{
		if (inputValue.length != sEditMask.length - 1)
		{
			oSrcElement.focus();
			return false;
		}
		else
		{
			sEditMask = sEditMask.substr(1);
			EnforceLength = true;
		}
	}
	else if (inputValue.length > sEditMask.length)
	{
		oSrcElement.focus();
		return false;
	}

	var rule = ParseRule(sEditMask);
	IsValid = rule.test(inputValue);

	if (!IsValid)
	{
		oSrcElement.focus();
		return false;
	}
	return true;
}

var REXP_LETTERS = "[A-Za-z\\xC0-\\xFF]?";
var REXP_NUMBERS = "[0-9]?";
var REXP_ANY = ".?";
var REXP_UPPER_LETTER = "[A-Z]?";

var MaskPatterns = {"1": /[A-Z]/i, "2": /[0-9]/, "4": /[\xC0-\xFF]/i, "8": /./ , "16": /[A-Z]/};
var MaskRules = { "#": 2, "_":5, "*": 8, "A": 18};

function ParseRule(rule)
{
	rule = rule.replace(/\[/g, "\\[");
	rule = rule.replace(/\]/g, "\\]");
	rule = rule.replace(/\./g, "\\.");
	rule = rule.replace(/\+/g, "\\+");
	rule = rule.replace(/\$/g, "\\$");
	rule = rule.replace(/\^/g, "\\^");
	rule = rule.replace(/\?/g, "\\?");
	rule = rule.replace(/\(/g, "\\(");
	rule = rule.replace(/\)/g, "\\)");
	rule = rule.replace(/\{/g, "\\{");
	rule = rule.replace(/\}/g, "\\}");
	
	rule = rule.replace(/A/g, REXP_UPPER_LETTER);
	rule = rule.replace(/#/g, REXP_NUMBERS);
	rule = rule.replace(/_/g, REXP_LETTERS);
	rule = rule.replace(/\*/g, REXP_ANY);
	
	return new RegExp("^" + rule + "$");
}

