/*******************************************************************************
FILE: reValidate.js

DESCRIPTION: This file contains a library of validation functions
  using javascript regular expressions.  Some string reformatting functions are also provided
  
	validateForm(oForm, bSilently) - loop through all validation functions for child elements
	and make sure things are legit before submitting
		oForm - the form element being validated
		bSilently - boolean indicating whether the function should use 'alert' to report errors
		            or simply return false
		SAMPLES: <FORM onSubmit="return validateForm(this)" ...> ... </FORM>"
		
  validate(oElement, sType, bSilently, sOptionalArg1, sOptionalArg2, bSilently) - check
	that the specified element successfully matches the datatype specifed
		oElement - the element whose value is being validated
		sType - the type of validation to perform:
							'numeric','integer','notempty','time','date'
							'state','ssn','email','phone','zip','unitnumber',
							'regexp',
							'integerrange', 'numericrange', 'daterange'
		sOptionalArgn = optional arguments for types that require further details
										(numericrange, daterange, regexp)
		bSilently - boolean indicating whether the function should use 'alert' to report errors
		            or simply return false
		NOTE: validate returns the value that has been validated when successful and the boolean value
		      false when not.  This is useful when you want the value to undergo some minor
					reformatting as a side-effect of validation.  See the daterange section for an example.
		SAMPLES: <INPUT type="text" name="unitnumber" onchange="return validate(this, 'unitnumber')"/>
						<INPUT type="text" name="birth_date" onchange="return validate(this, 'date')"/>
						<INPUT type="text" name="peak_flow" onchange="return validate(this, 'numericrange', 0, 800)" />

	trim(oElement, sType, sOptionalArg1)
		oElement - the element whose value is being trimmed
		sType - the type trim to perform:
							'all','left','right'
		sOptionalArg1 = optional arguments for types that require further details
										(e.g. a set of characters(suitable to construct an RegExp object) 
										to trim (whitespace (\s) by default)

*******************************************************************************/
function validateForm(oForm, bSilently)
{
	var len = oForm.elements.length;
	var bReturnValue = true;
	var sMessage = "";
	var bQuite = bSilently || false;
	
	// determine the index of the current form
	for(var j=0; j<document.forms.length; j++)
		if (document.forms[j] == oForm) break;

	// loop through each element's onchange handler and invoke the validation function
	// return false if any of them have failed
	for(var i=0; i<len; i++)
		if (oForm.elements[i].onchange)
		{
			var sFn = oForm.elements[i].onchange.toString();
			var sFnBody = sFn.match(/function \w*[^\{]*\{([^$]*)\}\s*$/)[1];
			// replace any occurences of the this pointer with actual references
			var sOnChangeBody = sFnBody.replace(/this/g, "document.forms[" + j + "].elements[" +  i + "]");
			var aValidateCalls = sOnChangeBody.match(/(validate\([^\)]*\))/g);
			for (var k=0; (aValidateCalls && k<aValidateCalls.length); k++)
			{
				// turn off the individual reporting for each element's validation
				if (aValidateCalls[k].match(/validate\([^,]+,[^,]+\)/g))
					aValidateCalls[k] = aValidateCalls[k].replace(/\)/,",null,null,true)");
				if (aValidateCalls[k].match(/validate\([^,]+,[^,]+,[^,]+\)/g))
					aValidateCalls[k] = aValidateCalls[k].replace(/\)/,",null,true)");
				if (aValidateCalls[k].match(/validate\([^,]+,[^,]+,[^,]+,[^,]+\)/g))
					aValidateCalls[k] = aValidateCalls[k].replace(/\)/,",true)");
				if (aValidateCalls[k].match(/validate\([^,]+,[^,]+,[^,]+,[^,]+,false\)/g))
					aValidateCalls[k] = aValidateCalls[k].replace(/,false\)/,",true)");
			}
			var fOnChange = new Function("", sOnChangeBody);
			if (!fOnChange())
			{
				sMessage += oForm.elements[i].name + " is not correctly filled in.\n";
				bReturnValue = false;
			}
		}
	if (!bReturnValue)
	{
		if (!bQuite)
			alert("Form " + j + " could not be submitted because of the following problems:\n\n" + sMessage);
		return false;
	}
	else
		return true;
}

function validateMandatory(oForm)
{
	var aMandatoryFields = oForm.elements.__mandatory__.value.split(",");
	var sMessage = "Mandatory fields for this form include:\n";
	var bSuccess = true;
	for (var i=0; i<aMandatoryFields.length; i++)
		sMessage += aMandatoryFields[i] + "\n";
	alert(sMessage);
	return true;
}

function validate(oElement, sType, sOptionalArg1, sOptionalArg2, bSilently)
{
	var sValue;
	var rePattern;
	var bQuite = bSilently || false;
	var bInput = false;
	var sMessage = "";
	switch (typeof(oElement))
	{
		case 'string':
		case 'number':
		case 'boolean':
			sValue = oElement.toString();
			break;
		case 'object':
			if (oElement.constructor == Date)
				sValue = (oElement.getMonth()+1) + "/" + oElement.getDate() + "/" + oElement.getFullYear();
			else
			{
				sValue = oElement.value.toString();
				bInput = true;
			}
			break;
	}
		
	switch (sType.toLowerCase())
	{
		case 'name': 
			rePattern = /^[A-Z]\'?[a-zA-Z]+\,\s[A-Z][a-zA-Z]*(\s[a-zA-Z]\.?)?$|[A-Z][a-zA-Z]*\s([a-zA-Z]\.?\s)?[A-Z]\'?[a-zA-Z]+$/;
			sMessage = "Invalid name: '" + sValue + "'\n\nValid forms are:" +
								 "\nLast, First M. or First M. Last" +
								 "\nLast, First or First Last";
			break;
			
		case 'unitnumber': 
			rePattern = /^\d{3}([\s\-]?)\d{2}\1\d{2}$/;
			sMessage = "Invalid unit number: '" + sValue + "'\n\nValid forms are:" +
								 "\n123-45-67 or 1234567";
			break;
			
		case 'time':
			rePattern = /^([1-9]|1[0-2]):[0-5]\d(:[0-5]\d)?$/;
			sMessage = "Invalid 12 hour time: '" + sValue + "'\n\nValid forms are:" +
								 "\nhh:mm:ss or hh:mm";
			break;
			
		case 'state':
			rePattern = /^AK$|^AL$|^AR$|^AZ$|^CA$|^CO$|^CT$|^DC$|^DE$|^FL$|^GA$|^HI$|^IA$|^ID$|^IL$|^IN$|^KS$|^KY$|^LA$|^MA$|^MD$|^ME$|^MI$|^MN$|^MO$|^MS$|^MT$|^NB$|^NC$|^ND$|^NH$|^NJ$|^NM$|^NV$|^NY$|^OH$|^OK$|^OR$|^PA$|^RI$|^SC$|^SD$|^TN$|^TX$|^UT$|^VA$|^VT$|^WA$|^WI$|^WV$|^WY$/i; 
			sMessage = "Invalid state abbreviation: '" + sValue + "'\n\nValid forms are:" +
								 "\n" + rePattern.toString().replace(/[\^\$]/g,"").replace(/\|/g,"\n");
			break;
			
		case 'ssn':
			rePattern = /^\d{3}(\-?)\d{2}\1\d{4}$/;
			sMessage = "Invalid social security number: '" + sValue + "'\n\nValid forms are:" +
								 "\n123-45-6789 or 123456789";
			break;
			
		case 'email':
			rePattern = /^[\w][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/i;
			sMessage = "Invalid email: '" + sValue;
			break;
			
		case 'phone':
			rePattern = /^\([1-9]\d{2}\)\s?\d{3}\-\d{4}$/;
			sMessage = "Invalid phone number: '" + sValue + "'\n\nValid forms are:" +
								 "\n(999) 999-9999 or (999)999-9999)";
			break;
			
		case 'integer':
			rePattern = /(^-?\d+$)/;
   	   if (rePattern.test(sValue))
				return new Number(sValue);
			sMessage = "Invalid integer: '" + sValue + "'\n\nValid forms are:" +
								 "\n10 or -10";
			break;
			
		case 'zip':
			rePattern = /^\d{5}(-?\d{4})?$/;
			sMessage = "Invalid zipcode; '" + sValue + "'\n\nValid forms are:" +
								 "\n12345 or 12345-9876 or 123459876";
			break;
			
		case 'numeric':
			rePattern = /^-?\d*\.?\d*$/; 
		  if (rePattern.test(sValue))
				return new Number(sValue);
			else 
			sMessage = "Invalid number: '" + sValue + "'\n\nValid forms are:" +
								 "\n-.123 or .123 or 1.234 or -1.234";
			break;				

		case 'notempty':
			var sTemp = trim(oElement, 'all');
   		if(sTemp.length > 0) return sTemp;
			if (!bQuite) 
				alert("Value should not be empty.")
			if (bInput) oElement.select();
			return false;

		case 'date':
		  rePattern = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;

 		  //check to see if in correct format
  		if(!rePattern.test(sValue))
			{
	  		if (!bQuite)
					alert("Invalid date: '" + sValue + "'" +
								"\n\nValid forms are:" +
								"\nmm/dd/yyyy or mm-dd-yyyy");
		    if (bInput) oElement.select();
		    return false; //doesn't match pattern, bad date
			}
  		else
			{
    		var sSeparator = sValue.match(/\D/g)[1]; //find date separator
    		var aDate = sValue.split(sSeparator); //split date into month, day, year
    		//create a lookup for months not equal to Feb.
				var nNumDaysInMonth;
				switch (parseInt(aDate[0]))
				{
					case 1: case 3: case 5: case 7: case 8: case 10: case 12:
						nNumDaysInMonth = 31;
						break;
					case 4: case 6: case 9: case 11:
						nNumDaysInMonth = 30;
						break;
					default:
						nNumDaysInMonth = null;
				}
					
    		var nDay = parseInt(aDate[1]); 
		    //check if month value and day value agree
   		  if(nNumDaysInMonth != null) 
      		if(nDay <= nNumDaysInMonth && nDay != 0)
        		return new Date(sValue); //found in lookup table, good date
    
		    //check for February
   		  var nYear = parseInt(aDate[2]);
        var nMonth = parseInt(aDate[0]);
    		if( ((nYear % 4 == 0 && nDay <= 29) || (nYear % 4 != 0 && nDay <=28)) && nDay !=0)
		      return new Date(sValue); //Feb. had valid number of days
  		}
  		if (!bQuite)
				alert("Invalid date: '" + sValue + "'" +
							"\n\nValid forms are:" +
							"\nmm/dd/yyyy or mm-dd-yyyy");
		      if (bInput) oElement.select();
			return false; //any other values, bad date

		case 'regexp':
			rePattern = new RegExp(sOptionalArg1);
			sMessage = "Value passed('" + sValue + "') does not match type specified(" + rePattern.toString() + ")";
			break;

		case 'numericrange':
			value = validate(oElement, 'numeric');
			min = (sOptionalArg1 != null) ? validate(sOptionalArg1, 'numeric') : Number.NEGATIVE_INFINITY;
			max = (sOptionalArg2 != null) ? validate(sOptionalArg2, 'numeric') : Number.POSITIVE_INFINITY;
			if (!value || !min || !max)
			{
				if (!bQuite)
					alert ("One of {" + value + "," +  min + "," + max + "} does not convert to a numeric value."); 
			       if(bInput) oElement.select();
				return false;
			}
			if (min && (value < min))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at least " + min);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
			if (max && (value > max))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at most " + max);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
		 	return value;

		case 'integerrange':
			value = validate(oElement, 'integer');
			min = (sOptionalArg1 != null) ? validate(sOptionalArg1, 'integer') : Number.NEGATIVE_INFINITY;
			max = (sOptionalArg2 != null) ? validate(sOptionalArg2, 'integer') : Number.POSITIVE_INFINITY;
			if (!value || !min || !max)
			{
				if (!bQuite)
					alert ("One of {" + value + "," +  min + "," + max + "} does not convert to an integer value."); 
			       if(bInput) oElement.select();
				return false;
			}
			if (min && (value < min))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at least " + min);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
			if (max && (value > max))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at most " + max);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
		 	return value;

		case 'daterange':
			value = validate(oElement, 'date', null, null, true);
			min = (sOptionalArg1 != null) ? validate(sOptionalArg1, 'date', null, null, true) : new Date("1/1/1900");
			max = (sOptionalArg2 != null) ? validate(sOptionalArg2, 'date', null, null, true) : new Date("1/1/2099");
			if (!value || !min || !max)
			{
				if (!bQuite) 
					alert ("One of {" + oElement.value + 
															(sOptionalArg1 ? ("," + sOptionalArg1) : "") + 
															(sOptionalArg2 ? ("," + sOptionalArg2) : "") + 
															"} does not convert to a date value.");
			       if(bInput) oElement.select();
				return false;
			}
			if (min && (value < min))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at least " + min);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
			if (max && (value > max))
		 	{
 		  	if (!bQuite) 
					alert("Value(" + value + ") should be at most " + max);
		       if(bInput) oElement.select();
 		  	return false;
		 	}
		 	return value;
			
		default:
			if (!bQuite) 
				alert("Don't know how to validate " + sType);
		       if(bInput) oElement.select();
			return false;
	}
	
  if (rePattern.test(sValue))
		return sValue;
	else
		if (!bQuite) alert(sMessage); 
	       if(bInput) oElement.select();
			return false;
}

function trim(oElement, sType, sOptionalArg1, sOptionalArg2, sOptionalArg3)
{
	var sCharacters = sOptionalArg1 || "\\s";
	var sValue;
	var rePattern;
	switch (typeof(oElement))
	{
		case 'string':
		case 'number':
		case 'boolean':
			sValue = oElement.toString();
			break;
		case 'object':
			if (oElement.constructor == Date)
				sValue = (oElement.getMonth()+1) + "/" + oElement.getDate() + "/" + oElement.getFullYear();
			else
				sValue = oElement.value.toString();
			break;
	}

	switch(sType.toLowerCase())
	{
		case 'all':
			rePattern = new RegExp("(" + sCharacters + "*)","g");
			return sValue.replace(rePattern, '');
		case 'left':
			rePattern = new RegExp("^(" + sCharacters + "*)(\\b[\\w\\W]*)$");
     	return sValue.replace(rePattern, '$2');
		case 'right':
			rePattern = new RegExp("([\\w\\W]*)(\\b" + sCharacters + "*)$");
			return sValue.replace(rePattern, '$1');
	}
}

