/*
TO DO's: 
			- Select-Multi
			- Testing - Mac, Win2k, XP
			
	FORM VALIDATOR: Version 1.1 // last fixes for DOM problems between ID and Mozilla
	
	The validator is a generic form validation script which requires 
	moderate but straight ahead HTML coding standards to be applied to the
	HTML form. 
	
	HTML FORM STANDARDS:
	The form standars are 1) the form must use the <label></label>
	elements for all field names. The code will highlite these in red when 
	errors are found. 2) All field names must end with a two digit number.
	This number is referred to as a 'check code' and is used to specifiy
	whether or not the filed is mandatory and or other levels of required
	validation (i.e. mandatory integer vs. string in text fields).
	
	VALIDATION FUNCTIONS:
	There are two types of validation functions provided refered to as
	'Level 1' and 'Level 2'.
	
	Level 1 Validation Functions -
	Level 1 validation functions provide basic checking for mandatory 
	fields. The <input type="text"> element also has options (specified
	using the Check Code) for ensuring that the data entered is of certain
	data types (see the function for a complete list of check codes).
	
	Level 2 Validation functions -
	Leve 2 validation functions are designed for checking the accuracy of
	specific data entities such as credit cards, phone numbers and postal 
	codes.
	
	:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	::::::::::::::::::: CHECK CODES (by Input type) :::::::::::::::::::
	:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	
	Note: Form input elements that do NOT have check codes will not be
		  validated by the script.
	Note: Check Codes must have a leading zero (0) for all codes less 
		  than 10.
		  
	TEXT -
		Check codes for text input:
		00: not manditory
		01: manditory string
		02: manditory integer
		03: manditory float
		04: manditory postal code
		05: manditory credit card (level 2 validation)
		06: manditory area code
		07: manditory phone number
		08: manditory email address
	
	RADIO -
		00: not manditory
		01: manditory 
	
	SELECT-ONE
		00: not manditory
		01: manditory 
		02: credit card type 
*/




// general validation globals
var gFormObj;
var gFormLength;
var gFormName;
var gFormNum;

// used to store class attribute names that differ in the DOM between IE and Mozilla
var gClass;  // represents the style 'class' attribute
var gFor;    // represents the <label> 'for' attribute

var gCCType;	//only used if ccard info is being collected. <str> indicates card type

//////////////////////////////////////////////////////
//////////////// HIGH LEVEL FUNCTIONS ////////////////
//////////////////////////////////////////////////////


function Init(pFormName)
{
	// some DOM oddness between browsers means we need to do this...
	var jBrowserName = navigator.appName;
	if(jBrowserName.indexOf("Microsoft",0) >= 0)
	{
		gClass = 'className';
		gFor   = 'htmlFor';
	}else{
		gClass = 'class';
		gFor   = 'for';
	}
	
	gFormObj    = document.forms[pFormName];      // form object itself
	gFormLength = gFormObj.length;                // number of form elements
	gFormName   = gFormObj.name;
	
	// find the form number
	var gFormNum = Get_FormNum(pFormName);
	
	Reset_Flags();
}


function Validate_Form()
{	
	// content globals to reset before validation
	gCCType = '';
	
	Reset_Flags(gFormName);
	var jErrorMessage = 'At least one required question has been left unanswered on the form. Please scroll to the top of the page and double check any questions that have been marked in red.';
	// validate all field elements except buttons
	var jCurrElement;
	var jAllErrors = [];
	for(var i=0; i<gFormLength; i++)
	{
		jCurrElement = document.forms[gFormName][i];
		switch(jCurrElement.type)
		{
			case 'text':
				var jError = Validate_Text(jCurrElement);
				if(jError != 'ok') 
				{
					Flag_Label(jError, false)
					jAllErrors[jAllErrors.length] = jError;
				}
				break;
			case 'password':
				var jError = Validate_Text(jCurrElement);
				if(jError != 'ok') 
				{
					Flag_Label(jError, false)
					jAllErrors[jAllErrors.length] = jError;
				}
				break;
			case 'textarea':
				var jError = Validate_Text(jCurrElement);
				if(jError != 'ok') 
				{
					Flag_Label(jError, false)
					jAllErrors[jAllErrors.length] = jError;					
				}
				break;
			case 'select-one':
				var jError = Validate_Select(jCurrElement);
				if(jError != 'ok') 
				{
					Flag_Label(jError, false)
					jAllErrors[jAllErrors.length] = jError;
				}
				break;			
			case 'radio':
				
				//alert(jCurrElement.name);
				
				var jValidated = [];
				if(Find_In(jValidated, jCurrElement.name) == -1)
				{
					
					jValidated[jValidated.length] = jCurrElement.name;
					jRadios = document.forms[gFormName][jCurrElement.name];
					var jError = Validate_Radio(jRadios);
					if(jError != 'ok') 
					{
						Flag_Label(jError, false)
						jAllErrors[jAllErrors.length] = jError;
					}
				}
				break;			
		}
	}
	// if no errors, submit the form
	
	if(jAllErrors.length == 0)
	{
		gFormObj.submit();
	}
	// end testing code

}


///////////////////////////////////////////////////////////////
///////////////// LEVEL 1 VALIDATION FUNCTIONS ////////////////
///////////////////////////////////////////////////////////////


function Validate_Text(pElement)
{
	/*
		Check codes for text input:
			0: not manditory
			1: manditory string
			2: manditory integer
			3: manditory float
			4: manditory postal code
			5: manditory credit card (level 2 validation)
			6: manditory area code
			7: manditory phone number
			8: manditory email address
			NOTE: No check code means NOT manditory
	*/
	var jStr = pElement.value;
	var jCC = Get_Check_Code(pElement);	
	if(!Is_Integer(jCC)) {return 'ok';}	// not manditory so drop out
	
	switch(jCC)
	{
		case 0:
			break;
		case 1:	//string
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
		case 2:	//integer
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if(!Is_Integer(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
		case 3:	//float
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if(!Is_Float(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
		case 4:	//postal code
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if (Validate_PostalCode(pElement.value) == 'error') {return Get_Label_Element(gFormName, pElement.id);}
			break;
	
		case 5:	//credit card
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if (Validate_CreditCard_LUHN(pElement, pElement.value) == 'error') {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
		case 6:	//area code
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if (Validate_AreaCode(pElement.value) == 'error') {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
		case 7:	//phone number
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if (Validate_PhoneNumber(pElement.value) == 'error') {return Get_Label_Element(gFormName, pElement.id);}
			break;
			
/*		case 8:	//email address
			if(Is_Empty_String(jStr)) {return Get_Label_Element(gFormName, pElement.id);}
			if (Validate_Email(pElement.value) == 'error') {return Get_Label_Element(gFormName, pElement.id);}
			break;
*/
}
	return 'ok';
}


function Validate_Radio(pElement)
{
	/*
		Check codes for radio input:
			0: not manditory
			1: manditory 
			NOTE: No check code means NOT manditory
	*/
	
	var jCC = Get_Check_Code(pElement[0]);
	
	if(!Is_Integer(jCC)) {return 'ok';} // not manditory so drop out
	
	if(jCC == 1)
	{
		var jLength = pElement.length;
		var jBool = false;
		for(var i=0; i<jLength; i++)
		{
			if(pElement[i].checked) 
			{
				jBool=true;
				break;
			}
		}
	}
	if(!jBool) {return Get_Label_Element(gFormName, pElement[0].id);}
	return 'ok';
}


function Validate_Select(pElement)
{
	/*
		Check codes for select input:
			0: not manditory
			1: manditory 
			2: credit card type (NOTE: sets global variable)(level 2 validation)
			NOTE: No check code means NOT manditory
	*/
	var jCC = Get_Check_Code(pElement);
	if(!Is_Integer(jCC)){return 'ok';} // not manditory so drop out
	
	if(jCC == 1)
	{
		if(pElement.selectedIndex == 0) {{return Get_Label_Element(gFormName, pElement.id);}}
	} 
	else if (jCC == 2) 
	{
		if(pElement.selectedIndex == 0) {{return Get_Label_Element(gFormName, pElement.id);}}
		gCCType = pElement.value;
	}
	return 'ok';
}


///////////////////////////////////////////////////////////////
///////////////// LEVEL 2 VALIDATION FUNCTIONS ////////////////
///////////////////////////////////////////////////////////////


/*
	Credit Card Identification Standards:
	
	CARD TYPE				PREFIX(es)				LENGTH(s)		TESTING NUMBERS
	-------------------------------------------------------------------------------------
	MC					|	51-55				|	16			|	5500000000000004
	Visa				|	4					|	13 or 16	|	4111111111111111 OR 4111000000004
	American Express	|	34 or 37			|	15			|	340000000000000
	Diners Club			|	300-305, 36 or 38	|	14			|	30000000000004 - 30100000000002 - 30200000000000 - 30304000000000 - 30400000000006 - 30500000000003 - 36400000000000 - 38300000000000
	Discover			|	6011				|	16			|	6011000000000004
	
	Check_CreditCard_Luhn(2323200577663554) << useful for testing LUNH algorithm 
*/

function Validate_CreditCard_LUHN(pElement, pNum)
{
	// clean up input string for card number before we do anything
	pNum      = Num_To_String(pNum);      // number in string format
	pNum      = Strip_Alpha(pNum); // remove any characters that are not numbers
	var jLen  = pNum.length;              // number length
	var jCsum = 0;                        // must be zero by end of validation process
	var jNum;

	// check card type based on length & prefix
	switch(gCCType)
	{
		case 'mc':
			if(jLen != 16) {return 'error';}
			jPrefix = parseInt(pNum.substring(0,2));
			if(jPrefix < 51 || jPrefix > 55) {alert('error');}
			if(jPrefix < 51 || jPrefix > 55) {return 'error';}
			break;
			
		case 'visa':
			if(jLen != 13 && jLen != 16) {return 'error';}
			jPrefix = parseInt(pNum[0]);
			if(jPrefix != 4) {return 'error';}
			break;
			
		case 'amex':
			if(jLen != 15) {return 'error';}
			jPrefix = parseInt(pNum.substring(0,2));
			if(jPrefix != 34 && jPrefix != 37) {return 'error';}
			break;
			
		case 'discover':
			if(jLen != 16) {return 'error';}
			jPrefix = parseInt(pNum.substring(0,4));
			if(jPrefix != 6011) {return 'error';}
			break;
			
		case 'dinersclub':
			if(jLen != 14) {return 'error';}
			jPrefix = parseInt(pNum.substring(0,2));
			if(jPrefix != 36 && jPrefix != 38)
			{
				jPrefix = parseInt(pNum.substring(0,3));	// must be the 3 digit form
				if(jPrefix < 300 || jPrefix > 305) {return 'error';}
			}
			break;
			
		default:
		return 'error'
			break;
	}
	
	if(Luhn_10(pNum) != 0) {return 'error';} // LUHN-10 check
	return 'ok';
}


//Validate_PostalCode(document.theForm.postalcode_04, 'm4b1a2');
function Validate_PostalCode(pString)
{
	if(isPostalCode(pString) || isZip(pString)) {return 'ok';}
	return 'error';
}

function isPostalCode(pString)
{
	pString = pString.toLowerCase();
	pString = Remove_Char(pString, " ");
	var jLetters    = "abcdefghijklmnopqrstuvwxyz ";
	var jNumbers    = "0123456789";
	var jLength     = pString.length;
	
	if(jLength > 6) {return false;}
	var x;
	for(var i=0; i<jLength; i++)
	{
  		if(i%2)
  		{
	  		if(jNumbers.indexOf(pString.charAt(i)) == -1) {return false;}
	  	} else {
	  		if(jLetters.indexOf(pString.charAt(i)) == -1) {return false;}
	  	}
	}
	return true;	
}


function isZip(pString) 
{
	//99999 or 99999-9999
	var jLength = pString.length;
	if(jLength != 5 && jLength != 10) {return false;}
	
	if(jLength == 5)
	{
		return Is_Integer(pString);
	} else if(jLength=10) {
		var jSeg1 = pString.substr(0,5);
		var jSeg2 = pString.charAt(5);
		var jSeg3 = pString.substr(6,4);
		if(Is_Integer(jSeg1) && jSeg2=='-' && Is_Integer(jSeg3)) {return true;}
		return false;
	}//endif
}


function Validate_AreaCode(pNum)
{
	// do some cleaning
	pNum = Strip_Alpha(pNum);
	if(pNum.length != 3 || !Is_Integer(pNum)) {return 'error';}
	return 'ok';
}

function Validate_PhoneNumber(pNum)
{
	// do some cleaning
	pNum = Strip_Alpha(pNum);
	if(pNum.length != 7 || !Is_Integer(pNum)) {return 'error';}
	return 'ok';
}


function Validate_Email(pEmail)
{
	// are regular expressions jSupported?
	var jSupported = 0;
	if (window.RegExp) 
	{
		var jTempStr = "a";
		var jTempReg = new RegExp(jTempStr);
		if (jTempReg.test(jTempStr)) {jSupported = 1;}
	}
	
	if (!jSupported) 
	{
		if((pEmail.indexOf(".") > 2) && (pEmail.indexOf("@") > 0)) {return 'ok';}
		return 'error';
	}
  	
	var r1 = new RegExp("(@.*@)|(\\.\\.)|(@\\.)|(^\\.)");
	var r2 = new RegExp("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$");
	if(!r1.test(pEmail) && r2.test(pEmail)) {return 'ok';}
	return 'error';
}


////////////////////////////////////////////////////////////
////////////////////// CORE FUNCTIONS //////////////////////
////////////////////////////////////////////////////////////


function Luhn_10(pNum)
{
	pNum      = Num_To_String(pNum);      // number in string format
	pNum      = Strip_Alpha(pNum); // remove any characters that are not numbers
	var jLen  = pNum.length;              // number length
	var jCsum = 0;                        // will contain checksum value by end of algorithm
	var jNum;
	
	for(var i=0; i<jLen; i++)	// step 1, double alternating digits (except the last)
	{
		i%2 ? jNum=parseInt(pNum.charAt(i)) : jNum=(parseInt(pNum.charAt(i))*2);
		if(jNum>9) {jNum = parseInt(1+(jNum%10));}	// step 2, reduce double digit numbers by adding together
		//if(i < jLen) {jCsum+=jNum};
		jCsum+=jNum;
	}
	return jCsum%10;	//step 3, mod 10 and return
}


function Get_Label_Element(pForm, pID)
{
	//find right form object first
	var jLabels = document.body.getElementsByTagName("label");
	//find specified label by pID (really the 'for' attribute)
	var jLength = jLabels.length;
	for(var i=0; i<jLength; i++)
	{
		var jCurrLabel = jLabels[i];
		if(jCurrLabel.getAttribute(gFor) == pID) {return jCurrLabel;}
	}
	
	return jCurrLabel;
}


function Flag_Label(pElement, pStatus)
{
	if (pStatus == null) {pStatus = false;}

	if(pStatus)
	{
		pElement.setAttribute(gClass,'formnoerror');
	} else {
		pElement.setAttribute(gClass,'formerror');
	}
}


function Reset_Flags()
{
	//var jForm  = document.getElementsByTagName("form")[gFormName];
	//var jLabels = jForm.getElementsByTagName("label");
	var jLabels = gFormObj.getElementsByTagName("label");
	var jLength = jLabels.length;
	
	for(var i=0; i<jLength; i++)
	{
		Flag_Label(jLabels[i], true);
	}
}


function Is_Float(pNum)
{
	pNum = parseFloat(pNum);
	if(isNaN(pNum)) {return false;}
	if(pNum%1 == 0)
	{
		return false;
	} else {
		return true;
	}
}

function Is_Integer(pNum)
{
	pNum = parseFloat(pNum);   // we must use parseFloat() or risk false/positives by converting floats to integers.
	if(isNaN(pNum)) {return false;}
	if(pNum%1 != 0)
	{
		return false;
	} else {
		return true;
	}
}



function Get_FormNum(pFormName)
{
	var jBody   = document.getElementsByTagName('body').item(0);
	var jForms  = jBody.getElementsByTagName('form');
	var jLength = jForms.length;
	
	for(var i=0; i<jLength; i++)
	{
		if(jForms[i].name == pFormName) {return i;}
	}	
}


function Reset_Form()
{
	Reset_Flags(gFormName);
	document.forms[gFormName].reset();
}


function Find_In(pArray, pItem)
{
	
	var jLength = pArray.length;
	for(var i=0; i<jLength; i++)
	{
		if(pArray[i] == pItem) {return i;}
	}	
	return -1;
}


function Num_To_String(pNum)
{
	return '' + pNum;
}


function Remove_Char(pString, pChar)
{
   var jClean = '';
   for(var i=0; i<pString.length; i++)
   {
      if(pString.charAt(i) != pChar) {jClean += pString.charAt(i);}
   }
   return jClean;
}


function Is_Empty_String(pString)
{
	// returns 0 if empty string, otherwise returns true
	//if(pString.length == 0) {return 0;}
	
	var jLength = pString.length;
	for(var i=0; i<jLength; i++)
	{
		if(pString[i] != " ") {return false;}
	}
	return true;
}


function Strip_Alpha(pString)
{
	pString = '' + pString;
	var jLegal = "0123456789";
	var jClean = "";
	for(var i=0; i<pString.length; i++)
	{
		if(jLegal.indexOf(pString.charAt(i)) != -1) {jClean += pString.charAt(i);}
	}
	return jClean;
}


function Get_Check_Code(pElement)
{
	var jName = pElement.name;
	return parseInt(jName.substr(jName.length-2, 2), 10); // must past 2nd parameter (10) to parseInt because of leading 0's on most Check Codes.
}
