/***********************************************************************************
 * 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.dob, "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;
	};

	// Join form elements values into 1 value e.g. date select boxes
	CValidate.itemJoin = function(form, name, parts) {
		var value = '';
		for(var i = 2; i < arguments.length; i += 2) {
			var suffix = arguments[i];
			var sep = (i + 1) < arguments.length ? arguments[i + 1] : '';
			value += CValidate.itemValue(form.elements[name + suffix]) + sep;
		}
		return function(){ this.value = value; return this; }();
	};
	
	CValidate.itemJoinDate = function(form, name) {
		return 	CValidate.itemJoin(form, name, '__day','/','__month','/','__year');
	};
	
 /**********************************************************
  * 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();
		return !CValidate.isDate(value) ? this.setError("format") : this.setError();
	};

 /**********************************************************
  * 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;
	};
	
 /**********************************************************
  * Date functions
  **********************************************************/
	
	// Return difference between two dates (l=milisecs, s=secs, n=mins, h=hours, d=days (default), m=months, y=years)
	CValidate.dateDiff = function(dateFrom, dateTo, range) {
		if(!dateFrom || !dateTo)
			return null;
	
		// Convert to date objects if strings
		if(typeof(dateFrom) != "object")
			dateFrom = new Date(dateFrom);
		if(typeof(dateTo) != "object")
			dateTo = new Date(dateTo);

		if(isNaN(dateFrom) || isNaN(dateTo))
			return null;
		
		// Get differences
		var diffYears = parseInt(dateTo.getFullYear() - dateFrom.getFullYear());
		var diffMonths = parseInt(dateTo.getMonth() - dateFrom.getMonth());
		var diffDays = parseInt(dateTo.getDate() - dateFrom.getDate());
		var diffMili = parseInt(dateTo.getTime() - dateFrom.getTime());
	
		if(range == null)
			range = "d";
		
		// Return difference
		var div = 1;
		switch(range) {
		case "d":		div = 24;
		case "h":		div *= 60;
		case "n":		div *= 60;
		case "s":		div *= 1000;
		case "l":
			result = parseFloat(diffMili / div);
			break;
		case "m":
			result = (diffYears * 12 + diffMonths);
			break;
		case "y":
			result = parseFloat((diffYears * 12 + diffMonths) / 12);
			break;
		default:
			return null;
		}
		
		result2 = ((diffYears * 12 + diffMonths) * 30 + diffDays) / 30;
		result3 = parseFloat((diffYears * 12 + diffMonths) / 12);
		
		/*
		alert(dateFrom.toGMTString() + "\n" + 
			dateTo.toGMTString() + "\nMili: "
			 + diffMili + "\nDays: "
			 + result + "\nMonths: "
			 + result2 + "\nYears: "
			 + result3 + "\n");
		*/
		
		return result;
	}
	
	//date_dateDiff("Dec 1, 2001", "Feb 12, 2002", "d");

	// Return number of days in a month
	CValidate.daysInMonth = function(month, year) {
		return new Date(year ? year : 1970, month, 0).getDate();
	}
	
	// Is year a leap year?
	CValidate.isLeapYear = function(year) {
		return (parseInt(year) % 4 == 0 && parseInt(year) % 400 != 0);
	}
	
	// Checks for a valid date
	CValidate.isDate = function(date) {
		var valid = true;
		
		var date = date.split('/');
		if(date.length != 3)
			valid = false;
		else {
			var day = date[0];		// day
			var month = date[1];	// month
			var year = date[2];		// year
			
			if(isNaN(day) || isNaN(month) || isNaN(year) ||
				day < 1 || day > 31 || day.length > 2 || day.length < 1 ||
				month < 1 || month > 12 || month.length > 2 || month.length < 1 ||
				year < 0 || year.length > 4 || year.length < 2)
				return false;
		
			var mon = parseInt(month, 10);
			
			// April, June, September, November only 30 days
			if(mon == 4 || mon == 6 || mon == 9 || mon == 11) {
				if(parseInt(day, 10) > 30)
					valid = false;
			}
			else if(mon == 2) {
				if(parseInt(day, 10) > (28 + (CValidate.isLeapYear(year) ? 1 : 0)) )
					valid = false;
			}
			else if(parseInt(day, 10) > 31)
				valid = false;
		}
		
		return valid;
	}

