/***********************************************************************************
 * Class: CValidate
 * Description:
 *   Validation class for forms
 *   Example of usage:
 *
 *   function validate(form) {
 *     var val = new CValidate(form);
 *     // Field name, Caption
 *     val.formString(form.firstName, "First Name");
 *     // Field name, Caption, Required (true | false)
 *     val.formEmail('email', "Email", true);
 *     // Field name, Caption, Required (true | false)
 *     val.formDate(form.address3, "Date Of Birth", true);
 *
 *     // Displays error and returns false if an error occurred
 *     return val.checkError();
 *   }
 *
 *   ...
 *
 *   <form .... onsubmit="return validate(this);">
 ***********************************************************************************/

	// Contructor
	function CValidate() {
		this.errClear(); 
		return this; 
	};

 /**********************************************************
  * Message contants
  **********************************************************/
 	CValidate.MSG_CHECK_ERROR 		= "Some fields were not entered correctly:\n\n";
 	CValidate.MSG_ITEM 				= "Script Error: Undefined field '$1'";
	CValidate.MSG_COMMON_REQUIRED 	= "The field '$1' is a required field";
 	CValidate.MSG_STRING_MIN 		= "The field '$1' requires at least $2 chars";
 	CValidate.MSG_STRING_MAX 		= "The field '$1' may not be longer than $2 char(s)";
	CValidate.MSG_NUMBER_NUMBER 	= "The field '$1' requires a valid number";
	CValidate.MSG_NUMBER_DECIMAL 	= "The field '$1' may not contain decimal places";
 	CValidate.MSG_NUMBER_MIN 		= "The field '$1' may not be less than $2";
 	CValidate.MSG_NUMBER_MAX 		= "The field '$1' may not be larger than $2";
	CValidate.MSG_PHONE_FORMAT 		= "The field '$1' requires a valid phone number";
	CValidate.MSG_EMAIL_FORMAT 		= "The field '$1' requires a valid email address";
	CValidate.MSG_DATE_FORMAT 		= "The field '$1' requires a valid date";
	CValidate.MSG_LIST_MIN 			= "The field '$1' requires at least $2 items selected";
	CValidate.MSG_LIST_MAX 			= "The field '$1' may not have more than $2 item(s) selected";

 /**********************************************************
  * String methods
  **********************************************************/
	// Trim string (left: "l|left", right: "r|right", both: "b|both" [default])
  	CValidate.strTrim = function(str, sides) {
		var i = 0, j = str.length - 1;
		
		// Trim left side
		if(!sides || (sides.charAt(0) != "r")) {
			while(i < j && str.charAt(i) == ' ')
				i++;
		}
		// Trim right side
		if(!sides || (sides.charAt(0) != "l")) {
			while(j > i && str.charAt(j) == ' ')
				j--;
		}
		// Return trimmed string
		return str.substring(i, j + 1);
	};

	// Change first letter of each word to upper case
	CValidate.strProperCase = function(str) {
		var prevIsSpace = true;
		var chr;
		for(var i = 0; i < str.length; i++) {
			chr = str.charAt(i);
			if(prevIsSpace && chr != ' ')
				str = str.substring(0, i) + chr.toUpperCase() + str.substring(i + 1);
			prevIsSpace = (chr == ' ');
		}
		return str;
	};
	
 /**********************************************************
  * Object methods
  **********************************************************/
	CValidate.itemValue = function(item, value) {
		if(item.options)
			return (item.selectedIndex >= 0) ? item.options[item.selectedIndex].value : "";
		else if(item.length) {
			i = 0;
			while(i < item.length && !item[i].checked)
				i++;
			return (i < item.length) ? item[i].value : "";
		}
		else
			return item.value;
	};
	
 /**********************************************************
  * Boolean validation functions
  **********************************************************/
	// Check string
	CValidate.prototype.checkString = function(value, min, max) {
		var length = CValidate.strTrim(value).length;
		if(min == null) min = 1;
		
		if(length < min)
			return (min == 1) ? this.setError("required") : this.setError("min");
		if(max && length > max)
			return this.setError("max");
		return this.setError();
	};
	
	// Check number
	CValidate.prototype.checkNumber = function(value, required, decimal, min, max) {
		if(value == "")
			return required ? this.setError("required") : this.setError();
		
		if(isNaN(value))
			return this.setError("number");
		if(!decimal && value.indexOf(".") >= 0)
			return this.setError("decimal");
		if(min != null && parseFloat(value) < min)
			return this.setError("min");
		if(max != null && parseFloat(value) > max)
			return this.setError("max");
		return this.setError();
	};	
	
	// Check phone number
	CValidate.prototype.checkPhone = function(value, required, extraChars) {
		if(value == "")
			return required ? this.setError("required") : this.setError();

		if(!extraChars)
			extraChars = "-+/ ";
		// Pattern: +353/087-434 43 43
		var pattern = new RegExp("^[" + extraChars.replace("(\-|\+|\/)", "\\\\$0") + "0-9]{5,25}$");
		return !pattern.test(value) ? this.setError("format") : this.setError();
	};

	// Checks email address
	CValidate.prototype.checkEmail = function(value, required) {
		if(value == "")
			return required ? this.setError("required") : this.setError();

		// Pattern: x@y.z
		var pattern = new RegExp("^[a-z0-9-_+\\.]+@[a-z0-9-_+\\.]+\\.[a-z0-9]+$", "i");
		return !pattern.test(value) ? this.setError("format") : this.setError();
	};
		
	// Check date (format: dd/mm/yyyy)
	CValidate.prototype.checkDate = function(value, required) {
		if(value == "")
			return required ? this.setError("required") : this.setError();

		var date_array = split(value, "/");
		return (value == "" && !required) || 
				(date_array.length == 3 && date_array[2].length == 4 && 
				isDate(date_array[1], date_array[0], date_array[2]));
	};

 /**********************************************************
  * Form validation functions
  **********************************************************/
  	// Check if form item exists
	CValidate.prototype.formItem = function(item, caption) {
		if(!item)
			return this.errAdd(CValidate.msgGet(CValidate.MSG_ITEM, caption));
		return true;
	};
  
  	// Validate string
	CValidate.prototype.formString = function(item, caption, min, max) {
		if(!this.formItem(item, caption))
			return false;
			
		if(!this.checkString(CValidate.itemValue(item), min, max)) {
			switch(this.lastError) {
			case "required":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			case "min":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_STRING_MIN, caption, min), item);	
			case "max":			
				return this.errAdd(CValidate.msgGet(CValidate.MSG_STRING_MAX, caption, max), item);
			}
		}
		return true;
	};
	
 	// Validate dropdown (min = least valid index or invalid item value)
	CValidate.prototype.formDropdown = function(item, caption, min) {
		if(!this.formItem(item, caption))
			return false;
		
		if(min == null)
			min = 0;
		else if(min == "" || isNaN(min) || min < 0)
			// Check that selected item is not equal to min
			return (CValidate.itemValue(item) == min) ? this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item) : true;
	
		// Check if min item has been selected or if min is not a number, check that the selected value is not equal to min
		if(item.selectedIndex < min || (isNaN(min) && CValidate.itemValue(item) == min))
			return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
		return true;
	};
	
	// Validate list
	CValidate.prototype.formList = function(item, caption, min, max) {
		if(!this.formItem(item, caption))
			return false;

		// Get selected count
		var selected = 0;
		var list = item.options ? item.options : item;
		for(var i = 0; i < list.length; i++) {
			if(list[i].selected || list[i].checked)
				selected++;
		}
		
		if(!min) min = 1;
		
		if(selected < min) {
			if(min == 1)
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			else
				return this.errAdd(CValidate.msgGet(CValidate.MSG_LIST_MIN, caption), item);
		}
		if(max && selected > max)
			return this.errAdd(CValidate.msgGet(CValidate.MSG_LIST_MAX, caption), item);
		return true;
	};

  	// Validate number 
	CValidate.prototype.formNumber = function(item, caption, required, decimal, min, max) {
		if(!this.formItem(item, caption))
			return false;
			
		if(!this.checkNumber(CValidate.itemValue(item), required, decimal, min, max)) {
			switch(this.lastError) {
			case "required":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			case "number":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_NUMBER_NUMBER, caption), item);
			case "decimal":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_NUMBER_DECIMAL, caption), item);
			case "min":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_NUMBER_MIN, caption, min), item);	
			case "max":			
				return this.errAdd(CValidate.msgGet(CValidate.MSG_NUMBER_MAX, caption, max), item);
			}
		}
		return true;
	};

  	// Validate phone number 
	CValidate.prototype.formPhone = function(item, caption, required, extraChars) {
		if(!this.formItem(item, caption))
			return false;
			
		if(!this.checkPhone(CValidate.itemValue(item), required, extraChars)) {
			switch(this.lastError) {
			case "required":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			case "format":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_PHONE_FORMAT, caption), item);
			}
		}
		return true;
	};
	
  	// Validate email 
	CValidate.prototype.formEmail = function(item, caption, required) {
		if(!this.formItem(item, caption))
			return false;
			
		if(!this.checkEmail(CValidate.itemValue(item), required)) {
			switch(this.lastError) {
			case "required":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			case "format":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_EMAIL_FORMAT, caption), item);
			}
		}
		return true;
	};
	
  	// Validate date 
	CValidate.prototype.formDate = function(item, caption, required) {
		if(!this.formItem(item, caption))
			return false;
			
		if(!this.checkDate(CValidate.itemValue(item), required)) {
			switch(this.lastError) {
			case "required":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_COMMON_REQUIRED, caption), item);
			case "format":
				return this.errAdd(CValidate.msgGet(CValidate.MSG_DATE_FORMAT, caption), item);
			}
		}
		return true;
	};
			
 /**********************************************************
  * Message functions
  **********************************************************/
	// Add attributes to message and return the result
	CValidate.msgGet = function(message) {
		for(var i = 1; i < arguments.length; i++)
			message = message.replace(new RegExp("\\$" + i), arguments[i]);
		return message;	
	};
	
 /**********************************************************
  * Error handling functions
  **********************************************************/
	// Sets last error for use by calling functions
	CValidate.prototype.setError = function(errId) {
		if(!errId) {
			this.lastError = null;
			return true;
		}
		// Set error
		this.lastError = errId;
		return false;
	};
	
	// Add error message to array
	CValidate.prototype.errAdd = function(message, item) {
		if(!this.errorArray)
			this.errorArray = new Array();
			
		var i = this.errorArray.length;
		this.errorArray.length += 2;
		this.errorArray[i] = message;
		this.errorArray[i + 1] = item;
		return false;
	};

	// Clear errors
	CValidate.prototype.errClear = function() {
		this.errorArray = null;
		this.lastError = null;
	};
	
	// Get error count
	CValidate.prototype.errCount = function() {
		return this.errorArray ? this.errorArray.length : 0;
	};
	
	// Displays errors, if any
	CValidate.prototype.checkError = function() {
		if(this.errCount() > 0) {
			var i, focusItem, msg = CValidate.MSG_CHECK_ERROR;
		
			// Add errors to message
			for(i = 0; i < this.errorArray.length; i += 2) {
				msg += this.errorArray[i] + "\n";
			
				if(!focusItem && this.errorArray[i + 1] != null)
					focusItem = this.errorArray[i + 1];
			}	
			// Clear errors
			this.errClear();
			// Display message
			alert(msg);
			// Set focus to first item with an error
			if(focusItem && !focusItem.disabled && focusItem.type != "hidden") {
				try { focusItem.focus(); } catch(e) { ; }
			}
			return false;
		}
		return true;
	};
