/*********************************************************************************
 * The contents of this file are subject to the AMPT License Version 1.0 
 * ("License"); You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://policies.ampt.co.za
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * See full license for requirements.
 *
 * Copyright (C) 2006 AMPT cc.;
 * All Rights Reserved.
 * Contributor(s): Rohland Lablache de Charmoy.
 ********************************************************************************/
/*********************************************************************************
 * Description:  Generic class for Validation of HTML forms using various input, 
 * select & textarea elements. (submit, reset and hidden inputs are ignored).
 *
 * Create: 2006-01-26
 *
 * Modifications:	Added textarea support
 *					2006-01-28
 *					Rohland Lablache de Charmoy
 *			Add CheckBox 'isticked' Support
 *					2006-02-02
 *					Rohland Lablache de Charmoy	
 *			Add Dual Password Validation 'validatepassword' Support
 *					2006-10-02
 *					Rohland Lablache de Charmoy	
 *			Add select-one validation for valuenot
 *					2006-14-02
 *					Rohland Lablache de Charmoy		
 *
 * TODO:	1. Convert the function from scanning a document to scanning a form 
 * 			object (i.e. add functionality for more than one form on a page)
 * 		2. Insert summary functionality so you can present this to the user 
 * 			when form validation fails  
 ********************************************************************************/

//---------------------------------------------------------------------------------------
// The following Cases are Taken into Account:
//---------------------------------------------------------------------------------------
//	1.	notnull		[text area and input fields]
//	2.	isnumeric
//	3.	integer
//	4.	email
//	5.	maxlength	[text area and input fields]
//	6.	minlength	[text area and input fields]
//	7.	date
//	8.	strongpassword
//	9.	fieldnot
//	10.	minselected	[only applies to select field]
//	11.	maxselected	[only applies to select field]
//	12.	filename
//	13.	isticked	[only applies to checkbox input]
//	14.	validatepassword[validates two password fields - Makes sure they match]
//	15.	valuenot	[only applies to drop downs / select fields]
//---------------------------------------------------------------------------------------
  
// --------------------------------------------------------------------------------------	
// The ValidateForm function runs through all input/select/textarea elements on a page			
// and looks for the 'validate' attribute. Once found the attribute value is scanned
// for validation keys. If the key is recognised the appropriate validation is fired.
// The action of a failed / passed validation can be changed generically using the
// PassField, FailField, PassFieldbyMsg,FailFieldByMsg functions.
// --------------------------------------------------------------------------------------

// --------------------------------------------------------------------------------------
// Function Name: PassField								
// Arguments: Input Object								
// This function is used to swap the border colour of an input field back to normal.	
// The function is called when a field passes its validation criteria.			
//---------------------------------------------------------------------------------------
function PassField(obj)
{
	Element.hide(obj.invalidContainerId);
	if (obj.validContainerId != '')
		$(obj.validContainerId).style.visibility = 'visible';
		
	return true;
}

// --------------------------------------------------------------------------------------
// Function Name: PassField								
// Arguments: Input Object								
// This function is used to swap the border colour of an input field back to a custom	
// color (red). The function is called when a field fails its validation criteria.	
//---------------------------------------------------------------------------------------
function FailField(obj)
{
	if (obj.validContainerId != '')
		$(obj.validContainerId).style.visibility = 'hidden';

	Element.show(obj.invalidContainerId);
	return false;
}

ClyralValidator = function(){
	this.ValidateOnBlur = ClyralValidator.ValidateOnBlur;
	this.FailFieldHandler = FailField;
	this.PassFieldHandler = PassField;
	this.ValidateAll = ClyralValidator.ValidateAll;
	this.validateField = ClyralValidator.validateField;
	this.validateFieldAndUpdateUI = ClyralValidator.validateFieldAndUpdateUI;
}
 
// --------------------------------------------------------------------------------------	
// The ValidateOnBlur function runs through the relevant input/select/textarea element		
// and validates it based on the options criteria
// PassField, FailField, PassFieldbyMsg,FailFieldByMsg functions.			
// Argument: Event Object					
// --------------------------------------------------------------------------------------
ClyralValidator.ValidateOnBlur = function(eventObj)
{
	if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent))
	{ // IE
		elementobj = eventObj.srcElement;
	}
	else // MOZILLA ETC
	{
		elementobj = eventObj.target;
	}
	
	// Find out which Form the element belongs to
	var parentForm = elementobj.form.id;
	var fieldArray = eval(parentForm + '_validationFields');
	
	// Find the form object
	var currentField = null;
	for(i=0;i<fieldArray.length;i++)
	{
		if (fieldArray[i].id == elementobj.id)
		{
			currentField = fieldArray[i];
		}
	}
	
	if (currentField == null)
		return;
	
	var validated = this.validateField(elementobj,currentField);
	
	if (validated)
		this.PassFieldHandler(currentField);
	else
		this.FailFieldHandler(currentField);
	
	return validated;
}

ClyralValidator.validateFieldAndUpdateUI = function(elementobj,currentField)
{
		var validated = this.validateField(elementobj,currentField);
	
		if (validated)
			this.PassFieldHandler(currentField);
		else
			this.FailFieldHandler(currentField);
}

ClyralValidator.validateField = function(elementobj,currentField)
{
	// Initialise the validation flag to true. This value returns the result of the form validation function.
	var Validated = true;
	// Test for input elements which we do not care about
	if (elementobj.type == 'submit' || elementobj.type =='reset' || elementobj.type=='hidden')
	{
		/// These are the input buttons and we must ignore them
	}
	else if (elementobj.type == 'select-one')
	{
		// Fetch the validate attribute object for the relevant element
		var validateAttribute = currentField.options;
		
		// We dont want to continue with this element if the validate attribute was not specified
		if (validateAttribute == null)
			return;
			
		// Split all the validator keys up in order to process them
		var valfunc = validateAttribute.split(';');

		// parse the validate string to see what needs to be validated
		var localval = true;
		
		// Cylce through all the Validate keys
		for (index = 0; index < valfunc.length; index++)
		{
			// If this element already failed one of its validation keys then do not continue
			if (localval == true)
			{
				// The valuenot key ensures that the a drop down value is not -1
				if(valfunc[index].indexOf('valuenot') != -1)
				{
					// First find out what the value is not supposed to be
					var values = valfunc[index].split(':');
					
					// Find out what the actual value is
					if (elementobj.selectedIndex != -1)
					{
						var currentValue = elementobj.options[elementobj.selectedIndex].value;
						if (currentValue == values[1])
						{
							localval = false;
						}
					}
					else
					{
						localval = false;
					}		
				}
			}
		}
		// If the Validated value is false we want it to stay that way
		Validated = Validated && localval;
	}
	else if (elementobj.type == 'select-multiple')
	{
		// Fetch the validate attribute object for the relevant element
		var validateAttribute = currentField.options;
		
		// We dont want to continue with this element if the validate attribute was not specified
		if (validateAttribute == null)
			return;
			
		// Split all the validator keys up in order to process them
		var valfunc = validateAttribute.split(';');

		// parse the validate string to see what needs to be validated
		var localval = true;
		
		// Cylce through all the Validate keys
		for (index = 0; index < valfunc.length; index++)
		{
			// If this element already failed one of its validation keys then do not continue
			if (localval == true)
			{
				// The minselected key ensures that the number of items selected is greater than a user specified value
				if (valfunc[index].indexOf('minselected') != -1)
				{
					var mlength = valfunc[index].split(':');
					// There is something wrong if there is no ':' seperator
					if (mlength.length != 2)
					{
						alert('Error in Min Selected Parameter');
						return;
					}
					// Cycle Through Items and Count How Many items are Selected
					var count = 0;
					for (selindex = 0;selindex < elementobj.length; selindex++)
					{
						if ( elementobj[selindex].selected == true)
							count++;
					}
					// Test the selected item count agains the user specified value
					if (mlength[1] > count)
					{
						localval = false;
					}				
				}
				// The maxselected key ensures that the number of items selected is less than a user specified value
				else if(valfunc[index].indexOf('maxselected') != -1)
				{
					var mlength = valfunc[index].split(':');
					// There is something wrong if there is no ':' seperator
					if (mlength.length != 2)
					{
						alert('Error in Max Selected Parameter');
						return;
					}
					
					// Cycle Through Items and Count How Many items are Selected
					var count = 0;
					for (selindex = 0;selindex < elementobj.length; selindex++)
					{
						if ( elementobj[selindex].selected == true)
							count++;
					}
					// Test the selected item count agains the user specified value
					if (mlength[1] < count)
					{
						localval = false;
					}
				}
			}
		}
		// If the Validated value is false we want it to stay that way
		Validated = Validated && localval;
	}
	else if (elementobj.type == 'textarea')
	{
		// Fetch the validate attribute object for the relevant element
		var validateAttribute = currentField.options;
		
		// We dont want to continue with this element if the validate attribute was not specified
		if (validateAttribute == null)
			return;
			
		// Fetch the value within the input element in question
		var value = elementobj.value;
		
			
		// Split all the validator keys up in order to process them
		var valfunc = validateAttribute.split(';');

		// parse the validate string to see what needs to be validated
		var localval = true;
		
		// Cylce through all the Validate keys
		for (index = 0; index < valfunc.length; index++)
		{
			// If this element already failed one of its validation keys then do not continue
			if (localval == true)
			{
				// The minselected key ensures that the number of items selected is greater than a user specified value
				if (valfunc[index].indexOf('minlength') != -1)
				{
					var mlength = valfunc[index].split(':');
					// There is something wrong if there is no ':' seperator
					if (mlength.length != 2)
					{
						alert('Error in Min Length Parameter');
						return;
					}
					// Test the selected item count agains the user specified value
					if (mlength[1] > value.length)
					{
						localval = false;
					}					
				}
				// The maxselected key ensures that the number of items selected is less than a user specified value
				else if(valfunc[index].indexOf('maxlength') != -1)
				{
					var mlength = valfunc[index].split(':');
					// There is something wrong if there is no ':' seperator
					if (mlength.length != 2)
					{
						alert('Error in Max Length Parameter');
						return;
					}
					
					// Cycle Through Items and Count How Many items are Selected
					// Test the selected item count agains the user specified value
					if (mlength[1] < value.length)
					{
						localval = false;
					}
				}
				else if(valfunc[index].indexOf('notnull') != -1)
				{
					if (value.length == 0)
					{
						localval = false;
					}					
				}
			}
		}
		// If the Validated value is false we want it to stay that way
		Validated = Validated && localval;
			

	}
	else
	{
		// Fetch the 'validate' attribute. This is compatible with Mozilla and IE
		var validateAttribute = currentField.options;
		
		// If the object does not have a validator then continue with the next element
		if (validateAttribute == null)  
			return;
		
		// Fetch all the validation keys
		var valfunc = validateAttribute.split(';');
		
		// Fetch the value within the input element in question
		var value = elementobj.value;
		
		// the local validation variable is set to true, thereafter all specified validation keys are applied, if one fails then the global validation is set to false
		var localval = true;
		
		// Cycle through all the keys, if one of them fails then the field fails validation
		for (index = 0; index < valfunc.length; index++)
		{
			// We dont care about continuing if one of the keys has already failed
			if (localval == true)
			{
				// Find out which validation key we are looking at
				switch (valfunc[index])
				{
					// The isnumeric key ensures that the value is a number [decimal included]
					case 'isnumeric':
						var num = value.split('.');
						for (numindex = 0; numindex < num.length;numindex++)
						{
							if (!num[numindex].match(/^[\d]+$/) || num.length > 2)
							{
								localval = false;
								break;
							}
						}
						break;
					// The integer key passes validation if the value entered is numeric with no decimal places
					case 'integer':
						if (!value.match(/^[\d]+$/) && value.length > 0)
						{
							localval = false;
						}
						break;
					// The notnull key ensures the value entered has a length > 0
					case 'notnull':
						if (value.length == 0  && localval==true)
						{
							localval = false;
						}
						break;
					// the email key ensures the value entered represents an email address
					case 'email':
						if (!value.match(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/))
						{
							localval = false;
						}
						break;
					// The strongpassword key ensures that the password entered has a combination of Alphabetical and Non-Alphabetical characters
					case 'strongpassword':
						if (!value.match(/^[^a-zA-Z\s]+\S*$|^\S*[^a-zA-Z\s]+.*$/))
						{
							localval = false;
						}
						break;
					// The date key ensures that the value entered is in the format DD/MM/CCYY | DD\MM\CCYY | DD-MM-CCYY
					case 'date':
						//Matches: 2/29/2004|||04/01/2003 10:01:23 am|||03-20-1999 
						// Non-Matches: 2/29/2003|||13/30/2001 10:05:00 pm|||12/32/2003 

						if (!value.match(/^((((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|([1-2][0-9]))))[\-\/\s]?\d{2}(([02468][048])|([13579][26])))|(((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))[\-\/\s]?\d{2}(([02468][1235679])|([13579][01345789]))))(\s(((0?[1-9])|(1[0-2]))\:([0-5][0-9])((\s)|(\:([0-5][0-9])\s))([AM|PM|am|pm]{2,2})))?$/))
						{
							localval = false;
						}
						break;
					// The isticked key ensures that the checkbox is ticked
					case 'isticked':
						if (!elementobj.checked)
						{
							localval = false;
						}
					break;
					// The validatepassword key ensures that the two passwords are the same
					case 'validatepassword':
						var pass2 = value;
						var passObject = document.getElementById(elementobj.id.substring(0,elementobj.id.length-1));
						if (passObject == null) // Error
							return false;
						var pass1 = passObject.value;
						if (pass1 != pass2)
						{
							localval = false;							
						}
					break;
					// Here we parse for keys that require extra information 
					default:
						// If the string contains maxlength key then process it
						if (valfunc[index].indexOf('maxlength') != -1)		// Caught Maxlength
						{
							var mlength = valfunc[index].split(':');
							// If there is no ':' sign then something is wrong
							if (mlength.length != 2)
							{
								alert('Error in Max Length Parameter');
								return;
							}
							if (value.length > mlength[1])
							{
								localval = false;
							}
						}
						// If the string contains maxlength key then process it
						else if (valfunc[index].indexOf('minlength') != -1) // Caught minlength
						{
							var mlength = valfunc[index].split(':');
							// If there is no ':' sign then something is wrong
							if (mlength.length != 2)
							{
								alert('Error in Min Length Parameter');
								return;
							}
							if (value.length < mlength[1])
							{
								localval = false;
							}						
						}
						// If the string contains fieldnot key then process it
						else if(valfunc[index].indexOf('fieldnot') != -1) // Caught Password Exclusions
						{
							var mlength = valfunc[index].split(':');
							// If there is no ':' sign then something is wrong
							if (mlength.length != 2)
							{
								alert('Error in Password Exclusion Parameter');
								return;
							}
							if (value.toLowerCase() == mlength[1].toLowerCase())
							{
								localval = false;
							}
						}
						// If the string contains filename key then process it
						else if(valfunc[index].indexOf('filename') != -1)
						{
							var mlength = valfunc[index].split(':');
							// If there is no ':' sign then something is wrong
							if (mlength.length != 2)
							{
								alert('Error in Filename Exclusion Parameter');
								return;
							}
							// Leave null string validation to notnull key
							if (value.length == 0)
							{
								continue;
							}
							var filename = value.split('.');
							// If the file has no extension then there is something wrong
							if (filename.length <= 1)
							{
								localval = false;
							}
							else
							{
								// Parse through all the extension types the user wants to validate againt
								var ext = mlength[1].split('|');
								var countmismatch = 0;
								// Build up an error message as we parse through expected file types, this is only shown if none match the current values extension
								var errmsg = "Please enter a valid filename ";
								for (extindex = 0; extindex < ext.length; extindex++)
								{
									errmsg += (extindex == 0? "[*." + ext[extindex].toLowerCase() : "|*." + ext[extindex].toLowerCase());
									// Is there a match? If not we increment countmismatch
									if (ext[extindex].toLowerCase() != filename[filename.length-1].toLowerCase())
									{
										countmismatch++;
									}											
								}
								errmsg += "]";
								// If the mismatch count is the same as the number or validating extensions then none could have matched --> Error
								if (countmismatch == ext.length)
								{
									localval = false;
								}
							}
							
						}
						break;
						
				}
			}
		}
		// If the Validated value is false we want it to stay that way
		Validated = Validated && localval;
	}

	return Validated;
}

validationField = function(elementid,validationOptions,invalidContainer,validContainer){
	this.id =elementid;
	this.options = validationOptions;
	this.invalidContainerId = invalidContainer;
	this.validContainerId = validContainer;
};

// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ClyralValidator.ValidateAll = function(formid, onsubmit)
{

	// Find out which Form the element belongs to
	var fieldArray = eval(formid + '_validationFields');
	
	var validated = true;
	
	for(i=0;i<fieldArray.length;i++)
	{
		var controlValidated = true;
		controlValidated = this.validateField($(fieldArray[i].id),fieldArray[i]);
		
		if (onsubmit) // If we are submitting display error messages
		{	
			if (controlValidated)
				this.PassFieldHandler(fieldArray[i]);
			else
				this.FailFieldHandler(fieldArray[i]);
		}
		
		if (!controlValidated)
			validated = false;
	}
	
	return validated;
}