//*************************************************************************************************************
// Javascript Form Validation Functions
// vs 1.0
// Author: Randy Moller
// Email: rmoller@hyperxmedia.com
// Date: 01-08-2003
// Last Revised: 01-09-2004
//
// Integration Notes:
//
// 1. Make sure this file is a js include on the document which contains the form(s) to be tested!
//
// 2. Change the error message background color variables to suit style.
//     2a. bgColorErr will set the error background color for the form elements if there's an error.
//     2b. bgColorNorm sets the normal background color for the form elements when no error exists.
//
// 3. The aryNames variable is basically a name/value array which aids in returning a friendly name in
//    the error message alert. So, on the left of the colon (:) would be the input name (w/o the 
//		validation flag "|validator"), and on the right is the friendly name you want returned in the 
//		error message. These name value pairs are delimited by a comma (,).
//		(i.e. "input_name" : "friendly_name", etc.)
//
// 4. In your web document, use the following naming convention for your input names in the form:
//    4a. Required values - name|required - Checks for empty strings
//    4b. *Phone Numbers - name|phone - validates u.s. phone #'s and returns formatted.
//    4c. Email Addresses - name|email - checks for valid email address
//    4d. Select box (selected) - name|selected - validates that user selected (not index[0])
//    4e. Minimum Chars - name|minimum,num - validates input to be >= num chars
//    4f. Maximum Chars - name|maximum,num - validates input to be <= num chars (NOT YET IMPLEMENTED...COMING!)
//    4g. **SSAN - name|ssan (*note - currently only validates single box input, not split) - valid ssn
//    4h. Dates - name|date (most any u.s. format acceptable) - validates any date (UNTESTED)
//    4i. MasterCard - name|mc - validates that entry is a valid mastercard number
//    4j. Visa - name|visa - validates that entry is a valid visa number
//		4k. Amex - name|amex - validates that entry is a valid amex number
//    4l. Zip Codes - name|zip (currently only supports u.s. or canadian)
//    4m. Dates of Birth - name|dob (incl leap years. Pass in as concatenated if split)
//		4n. alpha - name|alpha - field contains only letters
//		4o. alphaspace - name|alphaspace - field contains either letters or spaces
//		4p. alphanumeric - name|alphanumeric - field contains either numbers or letters
//		4q. alphanumericspace - name|alphanumericspace - field contains letters, numbers or spaces
//		4r. numeric - name|numeric - field contains all numbers
//		4s. digit - name|digit - for single digit validation
//
//		example: <input type='text' name='fname|required' value=''>
//		example: <select name='country|selected'>
//
//		* Phone number return format is configurable by uncommenting/commenting the particular format you want
//			(see function formatPhone())
//
//		**SSAN return format is configurable by uncommenting/commenting the particular format you want
//			(see function formatSSAN())
//
//		5. In the onSubmit of your <form> to be tested, call onSubmit='return validate(this);' and it should work!
//
//*************************************************************************************************************

//variables that hold normal and error background colors - config as necessary.
var bgColorErr = "#FC8C8C";
var bgColorNorm = "#FFFFFF";

//set up associative array of input/friendly err message names - add additional as necessary.
var aryNames = {
							 "fname" : "First Name",
							 "lname" : "Last Name",
							 "phone" : "Phone Number",
							 "city" : "City",
							 "practice_name" : "Name of Practice",
							 "address1" : "Street Address 1",
							 "address2" : "Street Address 2",
							 "zip" : "Zip Code",
							 "country" : "Country",
							 "state" : "State",
							 "title" : "Requestor Title",
							 "bus_email" : "Email Address",
							 "test" : "TestBox"
							 };

function validate (oForm)	{
	var elem;
	var val;
	var style;
	var firstError = -1;
	var errName;
	var errMsg = "";
	var validator;

	for (var i=0; i<oForm.length; i++)	{
		elem = oForm.elements[i];
		name = oForm.elements[i].name;
		errName = lookUp(name);
		val = oForm.elements[i].value;
		style = oForm.elements[i].style;
		
		//parse out the validator from elem name
		var aTmp = name.split("|");
		if (aTmp[1])	{
			strInput = aTmp[1];
			switch (strInput)	{
				case "required":
					if (!required(val))	{
						errMsg += errName + " is missing\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "email":
					if (!validEmail(val))	{
						errMsg += errName + " is missing or incorrectly formatted\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "phone":
					if (!validPhone(val))	{
						errMsg += errName + " missing or not valid\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						elem.value = formatPhone(val);
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "minimum_chars":
					tmp = name.split(",");
					chars = tmp[1];
					if (!minimumChars(val, chars))	{
						errMsg += errName + " must contain at least " + chars + "characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "maximum_chars":
					tmp = name.split(",");
					chars = tmp[1];
					if (!maximumChars(val, chars))	{
						errMsg += errName + " must contain at least " + chars + "characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "zip":
					if (!validZip(val))	{
						errMsg += errName + " missing or incorrectly formatted\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "ssan":
					if (!validSSAN(val))	{
						errMsg += errName + " is not a valid Social Security Number\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						elem.value = formatSSAN(val);
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "selected":
					if (!validSelected(elem))	{
						errMsg += errName + " has not been selected\n";
						firstError = ((firstError > -1)	? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);				
					}
					break;
				case "mc":
					if (!validMasterCard(val))	{
						errMsg += errName + " is not a valid MasterCard number\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "visa":
					if (!validVisa(val))	{
						errMsg += errName + " is not a valid Visa number\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "amex":
					if (!validAmex(val))	{
						errMsg += errName + " is not a valid American Express number\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "alpha":
					if (!validAlpha(val))	{
						errMsg += errName + " must contain only letter characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "alphaspace":
					if (!validAlphaSpace(val))	{
						errMsg += errName + " must contain only letter or space characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "alphanumeric":
					if (!validAlphaNumeric(val))	{
						errMsg += errName + " must contain only letter or number characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "alphanumericspace":
					if (!validAlphaNumericSpace(val))	{
						errMsg += errName + " must contain only letters, numbers, or the space character\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "numeric":
					if (!validNumeric(val))	{
						errMsg += errName + " must contain number characters\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				case "digit":
					if (!validDigit(val))	{
						errMsg += errName + " must be a single digit character\n";
						firstError = ((firstError > -1) ? firstError : i);
						setBackgroundStyle(elem, bgColorErr);
					} else {
						setBackgroundStyle(elem, bgColorNorm);
					}
					break;
				default:
					break;
			}
		}
	}
	if (errMsg.length == 0)	{
		return true;
	} else {
		alert(errMsg);
		oForm.elements[firstError].focus();
		return false;
	}
}

//returns friendly name for input to display error msg
function lookUp (iput)	{
	for (i in aryNames)	{
		if (iput.indexOf("|") > -1)	{
			tmp = iput.split("|");
			if (tmp[0] == i)	{
				return aryNames[i];
			}
		}
	}
}

//returns false if country selectedIndex is 0
function validSelected (e)	{
	if (!e.selectedIndex > 0)	{
		return false;
	}
	return true;
}


function setBackgroundStyle	(elem, color)	{
	elem.style.backgroundColor = color;
	return;
}

function required (sVal)	{	
	if (sVal.length == 0)	{
		return false;
	}
	return true;
}

//allows minimum of (num) number of chars
function minimumChars (v, num)	{
	if (v.length < num)	{
		return false;
	}
	return true;
}

// allows maximum of (num) number of chars
function maximumChars (v, num)	{
	if (v.length > num)	{
		return false;
	}
	return true;
}


//email
function validEmail (strEmail)	{
	var regex = /^[a-z0-9]([a-z0-9_\-\.]*)@([a-z0-9_\-\.]*)(\.[a-z]{2,3}(\.[a-z]{2}){0,2})$/i;
	return regex.test(strEmail);
}

//ssn (freeform)
function validSSAN (num)	{
	var regex = /\D+/gi;
	var tmp = num.replace(regex, "");
	if ((tmp.length == 9) && (validNumeric(tmp))) {
		return true;
	}
	return false;
}

//ssn - 3 part
function validSplitSSN (strPart1, strPart2, strPart3) 	{
	var regex1 = /^\d{3}/gi;
	var regex2 = /^\d{2}/gi;
	var regex3 = /^\d{4}/gi;
	if (!regex1.test(strPart1))	{
		return false;
	}
	if (!regex2.test(strPart2))	{
		return false;
	}
	if (!regex3.test(strPart3))	{
		return false;
	} else {
		return strPart1 + "-" + strPart2 + "-" + strPart3;
	}
}

//formats valid SSAN for return
function formatSSAN (num)	{
	var regex = /\D+/gi;
	if (tmp = num.replace(regex, ""))	{
	
		//create the substrings
		var ssn1 = tmp.substring(0,3);
		var ssn2 = tmp.substring(4,5);
		var ssn3 = tmp.substring(6,9);
		
		//format substrings (uncomment the format you want to return
		var fssan = ssn1 + "-" + ssn2 + "-" + ssn3;
		//var fssan = ssn1 + " " + ssn2 + "-" + ssn3;
		//var fssan = ssn1 + "." + ssn2 + "-" + ssn3;
		
		return fssan;
	}
	return false;
}

//u.s. phone number incl area code (freeform)
function validPhone (strNum)	{
	var regex = /\D+/gi;
	var tmp = strNum.replace(regex, "");
	if (tmp.length == 10)	{
		return true; 
	} else {
		return false;
	}
}

//u.s. phone number incl area code (split entry)
function validSplitPhone (strNum1, strNum2, strNum3)	{
	var regex = /^[\d{3}]/i;
	var regex2 = /^[\d{4}$]/i;
	if (!regex.test(strNum1))	{
		return false;
	}
	if (!regex.test(strNum2))	{
		return false;
	}
	if (!regex2.test(strNum3))	{
		return false;
	} else {
		return strNum1 + "-" + strNum2 + "-" + strNum3;
	}
}

//strips non digit characters and returns formatted phone number
function formatPhone (num)	{
	var regex = /\D+/gi;
	var tmp = num.replace(regex, "");
	if (tmp.length == 10)	{
	
		//format for return
		var tmp_ac = tmp.substring(0,3);
		var tmp_pre = tmp.substring(3,6);
		var tmp_num = tmp.substring(6,10);
		
		//uncomment the format you want returned
		var fphone = tmp_ac + "-" + tmp_pre + "-" + tmp_num; 								//returns xxx-xxx-xxxx
		//var fphone = "(" + tmp_ac + ") " + tmp_pre + "-" + tmp_num;				//returns (xxx) xxx-xxxx
		//var fphone = tmp_ac + "." + tmp_pre + "." + tmp_num;							//returns xxx.xxx.xxxx
		//var fphone = tmp_ac + tmp_pre + tmp_num;													//returns xxxxxxxxxx
		return fphone; 
	}
	return;
}

//validates dates in u.s. format (i.e. mm/dd/yyyy or mm.dd.yyyy) (freeform)
function validDate (strDate) {
	
	var formatted;
	var regex = /^\d{1,2}([\D|\s])\d{1,2}[\D|\s]\d{4}$/gi;
 
  if(!regex.test(strDate))	{
    return false;
  } else {
    var arrayDate = strDate.split(RegExp.$1);
    var intMonth = parseInt(arrayDate[0]);
		var intDay = parseInt(arrayDate[1]);
		var intYear = parseInt(arrayDate[2]);

		if(intMonth > 12 || intMonth < 1) {
			return false;
		}
	
		//create a lookup for months not equal to Feb.
	  var arrayLookup = { '01' : 31,'03' : 31, '04' : 30,'05' : 31,'06' : 30,'07' : 31,
                        '08' : 31,'09' : 30,'10' : 31,'11' : 30,'12' : 31};
  
	  //check if month value and day value agree
  	if(arrayLookup[arrayDate[0]] != null) {
    	if(intDay <= arrayLookup[arrayDate[0]] && intDay != 0) {
      	return formatted = formatDate(arrayDate);
			}
  	}
		
  	//check for February
		var bLeapYear = (intYear % 4 == 0 && (intYear % 100 != 0 || intYear % 400 == 0));
  	if( ((bLeapYear && intDay <= 29) || (!bLeapYear && intDay <=28)) && intDay !=0)	{
    	return true;
  	}
  	return false;
	}
}

//validates dates in u.s. format (i.e. mm/dd/yyyy or mm.dd.yyyy) (split)
function validSplitDate (strMonth, strDay, strYear) {

	var formatted;
  var regexMo = /^[\d{1,2}]/i;
	var regexDay = /[\d{1,2,3}\1]/i;
	var regexYear = /[\d{4}$]/i;
 
  if (!regexMo.test(strMonth))	{
    return false;
  } else if (!regexDay.test(strDay))	{
		return false;
	} else if (!regexYear.test(strYear))	{
		return false;
	} else {
	
	if(strMonth > 12 || strMonth < 1) {
		return false;
	} else if (strDay > 31 || strDay < 1)	{
		return false;
	}
	
	//create a lookup for months not equal to Feb.
  var arrayLookup = { '01' : 31,'03' : 31, '04' : 30,'05' : 31,'06' : 30,'07' : 31,
                        '08' : 31,'09' : 30,'10' : 31,'11' : 30,'12' : 31}
  
  //check if month value and day value agree
  if(arrayLookup[strMonth] != null) {
    if(strDay <= arrayLookup[strMonth] && strDay != 0)	{
      return formatted = strMonth + "-" + strDay + "-" + strYear;
		}
  }
		
  //check for February
	var bLeapYear = (strYear % 4 == 0 && (strYear % 100 != 0 || strYear % 400 == 0));
  if( ((bLeapYear && strDay <= 29) || (!bLeapYear && strDay <=28)) && strDay !=0)
    return formatted = strMonth + "-" + strDay + "-" + strYear;
  }
  return false;
}

//validates a u.s. zip code (5 or 5+4) returns a formatted zip if 9char using hyphen as seperator (or false)
function validZip (strZip)	{
	var formatted;
	var regex = /(^\d{5}$|^\d{5}[\s-\.]?\d{4}$)/gi;
	if (!regex.test(strZip))	{
		return false;
	} else {
		if (strZip.length == 9) {
			return formatted = strZip.substring(0,5) + "-" + strZip.substring(5);
		} else {
			return formatted = strZip.replace(/[.\s]/, "-");
		}
	}
}

//validates a u.s. zip code (5+4 split) returns a formatted zip if 9char using hyphen as seperator (or false)
function validSplitZip (strZip1, strZip2)	{
	var formatted;
	var regex1 = /^\d{5}$/gi;
	var regex2 = /^\d{4}$/gi;
	if ((regex1.test(strZip1)) && (regex2.test(strZip2)))	{
		return formatted = strZip1 + "-" + strZip2;
	} else {
		return false;
	}
}

//validates Date of Birth dates in u.s. format (i.e. mm/dd/yyyy or mm.dd.yyyy) (freeform)
function validDOB (strDate) {
	
	var formatted;
	var regex = /^\d{1,2}([\D|\s])\d{1,2}[\D|\s]\d{4}$/gi;
 
  if(!regex.test(strDate))	{
    return false;
  } else {
    var arrayDate = strDate.split(RegExp.$1);
    var intMonth = parseInt(arrayDate[0]);
		var intDay = parseInt(arrayDate[1]);
		var intYear = parseInt(arrayDate[2]);
		var now = new Date();
		var intTodayDay = now.getDay();
		var intTodayMonth = now.getMonth();
		var intTodayYear = now.getYear();

		if(intMonth > 12 || intMonth < 1) {
			return false;
		} else if (intYear < 1800)	{
			return false;
		} else if ((intDay > (intTodayDay + 1)) && (intMonth > intTodayMonth) && (intYear >= intTodayYear))	{
			return false;
		}
	
		//create a lookup for months not equal to Feb.
	  var arrayLookup = { '01' : 31,'03' : 31, '04' : 30,'05' : 31,'06' : 30,'07' : 31,
                        '08' : 31,'09' : 30,'10' : 31,'11' : 30,'12' : 31};
  
	  //check if month value and day value agree
  	if(arrayLookup[arrayDate[0]] != null) {
    	if(intDay <= arrayLookup[arrayDate[0]] && intDay != 0) {
      	return formatted = formatDate(arrayDate);
			}
  	}
		
  	//check for February
		var bLeapYear = (intYear % 4 == 0 && (intYear % 100 != 0 || intYear % 400 == 0));
  	if( ((bLeapYear && intDay <= 29) || (!bLeapYear && intDay <=28)) && intDay !=0)	{
    	return formatted = formatDate(arrayDate);
  	}
  	return false;
	}
}

//validates dates in u.s. format (i.e. mm/dd/yyyy or mm.dd.yyyy) (split)
function validSplitDOB (strMonth, strDay, strYear) {

	var formatted;
  var regexMo = /^[\d{1,2}]/i;
	var regexDay = /[\d{1,2,3}\1]/i;
	var regexYear = /[\d{4}$]/i;
	
  if (!regexMo.test(strMonth))	{
    return false;
  } else if (!regexDay.test(strDay))	{
		return false;
	} else if (!regexYear.test(strYear))	{		
		return false;
	} 

 	var now = new Date();
	var intTodayMonth = now.getMonth();
	var intTodayDay = now.getDay();
	var intTodayYear = now.getYear();
	var intMonth = parseInt(strMonth, 10);
	var intDay = parseInt(strDay, 10);
	var intYear = parseInt(strYear, 10);
	
	if (strMonth > 12 || strMonth < 1) {
		return false;
	} else if (strDay > 31 || strDay < 1)	{
		return false;
	} else if ((intDay > (intTodayDay + 1)) && (intMonth > intTodayMonth) && (intYear >= intTodayYear))	{
		return false;
	}	else if (intYear < 1800)	{
		return false;
	}
	
	//create a lookup for months not equal to Feb.
  var arrayLookup = { '01' : 31,'03' : 31, '04' : 30,'05' : 31,'06' : 30,'07' : 31,
                        '08' : 31,'09' : 30,'10' : 31,'11' : 30,'12' : 31}
  
  //check if month value and day value agree
  if(arrayLookup[strMonth] != null) {
    if(strDay <= arrayLookup[strMonth] && strDay != 0)	{
      return formatted = intMonth + "-" + intDay + "-" + intYear;
		}
  }
		
  //check for February
	var bLeapYear = (strYear % 4 == 0 && (strYear % 100 != 0 || strYear % 400 == 0));
  if( ((bLeapYear && strDay <= 29) || (!bLeapYear && strDay <=28)) && strDay !=0)	{
    return formatted = intMonth + "-" + intDay + "-" + intYear;
  }
  return false;
}

//credit card functions
//visa
function validVisa (num)	{
	var regex = /\D+/gi;
	var tmp = num.replace(regex, "");
  if ((tmp.length == 16) && (tmp.substring(0,1) == 4)) {
    return validCreditCard(tmp);
	}
  return false;
}

//mastercard
function validMasterCard (num)	{
	var regex = /\D+/gi;
	var tmp = num.replace(regex, "");
  firstdig = tmp.substring(0,1);
  seconddig = tmp.substring(1,2);
  if ((tmp.length == 16) && (firstdig == 5) && ((seconddig >= 1) && (seconddig <= 5))) {
    return validCreditCard(tmp);
	}
  return false;
}

//american express
function validAmex (num)	{
	var regex = /\D+/gi;
	var tmp = num.replace(regex, "");
	fdigit = tmp.substring(0,1);
	if ((tmp.length == 15) && (fdigit == 3)) {
		return validCreditCard(tmp);
	}
	return false;
}

//general credit card
function validCreditCard(cnum) {
  // Encoding only works on cards with less than 19 digits
  if (cnum.length > 19)
    return (false);

  sum = 0; mul = 1; l = cnum.length;
  for (i = 0; i < l; i++) {
    digit = cnum.substring(l-i-1,l-i);
    tproduct = parseInt(digit ,10)*mul;
    if (tproduct >= 10)
      sum += (tproduct % 10) + 1;
    else
      sum += tproduct;
    if (mul == 1)
      mul++;
    else
      mul--;
  }
  if ((sum % 10) == 0)
    return (true);
  else
    return (false);
}

//matches alpha characters only
function validAlpha (v)	{
	var regex = /^[a-zA-Z]+$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//matches alphabetic or space characters only
function validAlphaSpace (v)	{
	var regex = /[a-zA-Z\s]+$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//matches alpha numeric characters only
function validAlphaNumeric (v)	{
	var regex = /[a-zA-Z0-9]+$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//matches either alphabetic, numeric, or space characters only
function validAlphaNumericSpace (v)	{
	var regex = /[a-zA-Z0-9\s]+$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//matches numbers only
function validNumeric (v)	{
	var regex = /[0-9]+$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//matches any single digit only
function validDigit (v)	{
	var regex = /^[0-9]$/;
	if (!regex.test(v))	{
		return false;
	}
	return true;
}

//end validation functions

