/*
 *	General form validation class 
 *  Note: Phone field is automatically concatenated from phone1, phone2, and phone3.
 *	
 *	Usage Example:
 *	var valConfig = [
 *		{ fld:'email', lbl:'Email address', req:[{cmd:'required'},{cmd:'email'}] },
 *		{ fld:'pass', lbl:'Password', req:[{cmd:'required'},{cmd:'minLength=5', err:'Pass must be at least 5 characters long.'}] },
 *		{ fld:'confPass', lbl:'Confirm password', req:[{cmd:'required', err:'Please confirm your password.'},{cmd:'sameAs=pass', err:'Confirm password and original password do not match.'}] },
 *		{ fld:'phone', lbl:'Phone number', mergeFromFlds:['phone1a','phone2','phone3'], req:[{cmd:'required'},{cmd:'phone', err:'Please enter a valid phone number.'}] },
 *		{ fld:'carrier', lbl:'Carrier', req:[{cmd:'dontSelect=0'}] }
 *	];
 *	var validation = new Validation('regForm', valConfig);
 *
 */

function Validation(frmName, configObj) {
	this.frmObj = document.forms[frmName];
	var f = this.frmObj; f.configObj = configObj; f.debug = false;	
	// Save form's onsubmit if it exists
	if (f.onsubmit) { f.userDefinedOnsubmit = f.onsubmit; f.onsubmit = null; } else f.userDefinedOnsubmit = null;	
	
	f.onsubmit = function() { // Define document.frmName.customValidator() to add custom validations
		if (this.userDefinedOnsubmit) this.userDefinedOnsubmit(); // Use if skipping rest of validation is needed
		if (valHelper.isVar('valConfig') && valConfig.skip) return true;
		if (this.customValidator) { var str = 'var result=' + this.customValidator + '()'; eval(str); if (result == false) return false; }
		for (var i=0; i<this.configObj.length; i++) { if (this.validate(i) == false) return false; }
		if (valHelper.isVar('valOnSuccess')) valOnSuccess();
		return true;
	}	
	
	// Valididate input element at ith index
	f.validate = function(i) {
		var v = valHelper;
		currObj = this.configObj[i]; if (typeof currObj.lbl == 'undefined') currObj.lbl = currObj.fld;
		fldObj = this[currObj.fld]; cmdArr = currObj.req; phone1 = '';
		if (currObj.lbl.toLowerCase().match("phone") != null) {
			phone1 = 'phone1'; phone2 = 'phone2'; phone3 = 'phone3'; 
			phoneArr = currObj.mergeFromFlds;
			if (typeof phoneArr != 'undefined') { phone1 = phoneArr[0]; phone2 = phoneArr[1]; phone3 = phoneArr[2]; }
			fldObj.value = this[phone1].value + this[phone2].value + this[phone3].value;
		}
		
		// Loop through each requirement command
		for (var j=0; j<cmdArr.length; j++) {
			var command = cmdArr[j].cmd, cmd = command, cmdValue = '', cnd = cmdArr[j].cnd, eqSignPos = cmd.search("=");
			if (cnd && !cnd()) continue;
			if (eqSignPos >= 0) { command = cmd.substring(0,eqSignPos); cmdValue = cmd.substr(eqSignPos+1); }
			var err = cmdArr[j].err, val = fldObj.value, ok = 1;
			
			switch (command) {
				case "required": { if (v.isEmpty(val)) { err = err || 'Please enter ' + currObj.lbl + '.'; ok = 0; } break; }
				case "email": { if (!v.isValidEmail(val)) { err = err || 'Please enter a valid email address.'; ok = 0; } break; }
				case "phone": { if (!v.isValidUSNum(val)) { err = err || 'Please enter a valid phone number.'; ok = 0; } break; }
				case "sameAs": { if (val != cmdValue) { err = err || currObj.lbl + ' and ' +  cmdValue + ' do not match.'; ok = 0; } break; }
				case "sameAsById": { if (val != v.val(cmdValue)) { err = err || currObj.lbl + ' and ' +  cmdValue + ' do not match.'; ok = 0; } break; }
				case "notSameAs": { if (val == this.getValByFieldName(cmdValue)) { err = err || currObj.lbl + ' and ' +  cmdValue + ' cannot be the same.'; ok = 0; } break; }
				case "equal": { if (val != cmdValue) { err = err || currObj.lbl + ' and ' +  cmdValue + ' must be equal.'; ok = 0; } break; }
				case "maxLength": { if (v.isOverMaxLength(val, cmdValue)) { err = err || currObj.lbl + ' is too long.'; ok = 0; } break; }
				case "minLength": { if (v.isUnderMinLength(val, cmdValue)) { err = err || currObj.lbl + ' is too short.'; ok = 0; } break; }
				case "exactLength": { if (!v.isExactLength(val, cmdValue)) { err = err || currObj.lbl + ' must be ' + cmdValue + ' characters long.'; ok = 0; } break; }
				case "alpha": { if (!v.isAlpha(val)) { err = err || currObj.lbl + ' can only contain letters.'; ok = 0; } break; }
				case "alphaNumeric": { if (!v.isAlphaNumeric(val)) { err = err || currObj.lbl + ' can only contain alpha-numeric characters.'; ok = 0; } break; }
				case "aphaNumericHyphen": { if (!v.isAlphaNumericHyphen(val)) { err = err || currObj.lbl + ' can only contain A-Z,a-z,0-9,- and _ characters.'; ok = 0; } break; }
				case "numeric": { if (!v.isNumeric(val)) { err = err || currObj.lbl + ' can only contain numbers.'; ok = 0; } break; }
				case "price": { if (!v.isPrice(val)) { err = err || currObj.lbl + ' can only contain numbers'; ok = 0; } break; }
				case "date": { if (!v.isDate(val)) { err = err || currObj.name + ' must be a valid date in mm/dd/yyyy format.'; ok = 0; } break; }
				case "earlierThan": { if (!v.isEarlierThan(val,this.getValByFieldName(cmdValue))) { err = err || currObj.name + ' must be earlier than end date.'; ok = 0; } break; }
				case "earlierThanEqual": { if (!v.isEarlierThanEqual(val,this.getValByFieldName(cmdValue))) { err = err || currObj.name + ' must be earlier than end date.'; ok = 0; } break; }
				case "laterThan": { if (!v.isLaterThan(val,this.getValByFieldName(cmdValue))) { err = err || currObj.name + ' must be later than start date.'; ok = 0; } break; }
				case "laterThanEqual": { if (!v.isLaterThanEqual(val,this.getValByFieldName(cmdValue))) { err = err || currObj.name + ' must be later than start date.'; ok = 0; } break; }
				case "lessThan": { if (!v.isLessThan(val, cmdValue)) { err = err || currObj.lbl + ' should be less than ' + cmdValue; ok = 0; } break; }
				case "lessThanEqual": { if (!v.isLessThanEqual(val, cmdValue)) { err = err || currObj.lbl + ' should be less than or equal to ' + cmdValue; ok = 0; } break; }
				case "greaterThan": { if (!v.isGreaterThan(val, cmdValue)) { err = err || currObj.lbl + ' should be greater than ' + cmdValue; ok = 0; } break; }
				case "greaterThanEqual": { if (!v.isGreaterThanEqual(val, cmdValue)) { err = err || currObj.lbl + ' should be greater than or equal to ' + cmdValue; ok = 0; } break; }
				case "regExp": { if (!v.regExp(val, cmdValue)) { err = err || 'Invalid characters found.'; ok = 0; } break; }
				case "dontSelect": { if (!v.dontSelect(fldObj.selectedIndex, cmdValue)) { err = err || 'Please choose ' + currObj.lbl + '.'; ok = 0; } break; }
				case "minChecked": { if (!v.minChecked(fldObj,cmdValue)) { err = err || 'At least ' + cmdValue +' item' + (cmdValue>1?'s':'') + ' must be checked in ' + currObj.lbl + '.'; ok = 0; } break; }
				case "mustCheck": { if (!fldObj.checked) { err = err || currObj.lbl + ' must be checked.'; ok = 0; } break; }
			}

			if (!ok) {
				alert(err);
				if (phone1 != '') this[phone1].focus();
				else if (!fldObj.length) fldObj.focus();
				return false;
			}
		}
		return true;
	}
	f.getValByFieldName = function(fldName) { return this[fldName].value; }
	this.setCustomValidator = function(functionName) { f.customValidator = functionName; }	
	this.removeCustomValidator = function() { f.customValidator = null; }
}

var valHelper = {
	isValidUSNum : function(str) { var regex = /^[2-9][0-8][0-9][2-9][0-9]{6}$/; return regex.test(str); },
	isValidEmail : function(str) { var regex = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; return regex.test(str); },
	isDate : function(str) { var regex = /^(?=\d)(?:(?:(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})|(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2}))($|\ (?=\d)))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\ [AP]M))|([01]\d|2[0-3])(:[0-5]\d){1,2})?$/; return regex.test(str); }, // mm/dd/yyyy
	isEmpty : function(str) { return str.length == 0; },
	isOverMaxLength : function(str, maxLength) { return str.length > maxLength; },
	isUnderMinLength : function(str, minLength) { return str.length < minLength; },
	isExactLength : function(str, length) { return str.length == length; },
	isAlphaNumeric : function(str) { return (str.length > 0 && str.search("[^A-Za-z0-9]") < 0); },
	isAlpha : function(str) { return (str.length > 0 && str.search("[^A-Za-z]") < 0); },
	isAlphaNumericHyphen : function(str) { return (str.length > 0 && str.search("[^A-Za-z0-9\-_]") < 0); },
	isNumeric : function(str) { return (str.length > 0 && str.search("[^0-9]") < 0); },
	isPrice : function(str) { return (str.length > 0 && str.search("[^0-9\.]") < 0); },
	isLessThan : function(str, cmdValue) { return !isNaN(str) && eval(str) < eval(cmdValue); },
	isLessThanEqual : function(str, cmdValue) { return !isNaN(str) && eval(str) <= eval(cmdValue); },
	isGreaterThan : function(str, cmdValue) { return !isNaN(str) && eval(str) > eval(cmdValue); },
	isGreaterThanEqual : function(str, cmdValue) { return !isNaN(str) && eval(str) >= eval(cmdValue); },
	isEarlierThan : function(d1, d2) { return this.parseDate(d1) < this.parseDate(d2); },
	isEarlierThanEqual : function(d1, d2) { return this.parseDate(d1) <= this.parseDate(d2); },
	isLaterThan : function(d1, d2) { return this.parseDate(d1) > this.parseDate(d2); },
	isLaterThanEqual : function(d1, d2) { return this.parseDate(d1) >= this.parseDate(d2); },
	isVar : function(str) { return (eval('typeof '+str) != 'undefined'); },
	val : function(id) { return document.getElementById(id).value; },
	regExp : function(str, cmdValue) { return str.length > 0 && str.match(cmdValue); },
	dontSelect : function(str, cmdValue) { return str != null && str != eval(cmdValue);	},
	minChecked : function(arrChk, minCheckCnt) {
		if (!arrChk.length) return true; minCheckCnt = minCheckCnt || 1; var chkCnt = 0;
		for(var i=0, j=arrChk.length, chkCtn=0; i<j && chkCtn<minCheckCnt; i++) { if(arrChk[i].checked) chkCtn++; }
		return chkCtn >= minCheckCnt;
	},
	parseDate : function(str) { // date must be in mm/dd/yyyy format
		var arr = str.split('/'); if (!arr || arr.length!=3) return null;
		var m = arr[0]-1, d = arr[1], y = arr[2]; if (y.length==2) y = '20'+y;
		if (isNaN(m) || isNaN(d) || isNaN(y)) return null;
		return (new Date(y, m, d));
	}
}