// Set of JavaScript functions for validating input on
// an HTML form.  Functions are provided to validate:
//
//  - U.S. and international phone/fax numbers
//  - U.S. ZIP codes (5 or 9 digit postal codes)
//  - U.S. State Postal Codes (2 letter abbreviations for names of states)
//  - U.S. Social Security Numbers (abbreviated as SSNs)
//  - email addresses
//	- credit card numbers
//  - required string fields
//
// SUPPORTING UTILITY FUNCTIONS ARE PROVIDED IN:
//      FormCheckIncludes.js
//
// FORM FIELD VALIDATION FUNCTIONS
// isSSN (s [,eok])                    True if string s is a valid U.S. Social Security Number.
// isUSPhoneNumber (s [,eok])          True if string s is a valid U.S. Phone Number.
// isInternationalPhoneNumber (s [,eok]) True if string s is a valid international phone number.
// isZIPCode (s [,eok])                True if string s is a valid U.S. ZIP code.
// isStateCode (s [,eok])              True if string s is a valid U.S. Postal Code
// isEmail (s [,eok])                  True if string s is a valid email address.

// FUNCTIONS TO INTERACTIVELY CHECK FIELD CONTENTS:
// checkString (theField, s [,eok])    Check that theField.value is not empty or all whitespace.
// checkStateCode (theField)           Check that theField.value is a valid U.S. state code.
// checkZIPCode (theField [,eok])      Check that theField.value is a valid ZIP code.
// checkUSPhone (theField [,eok])      Check that theField.value is a valid US Phone.
// checkInternationalPhone (theField [,eok])  Check that theField.value is a valid International Phone.
// checkEmail (theField [,eok])        Check that theField.value is a valid Email.
// checkSSN (theField [,eok])          Check that theField.value is a valid SSN.
// getRadioButtonValue (radio)         Get checked value from radio button.
// checkCreditCard (radio, theField)   Validate credit card info.

// CREDIT CARD DATA VALIDATION FUNCTIONS
// isCreditCard (st)                   True if credit card number passes the Luhn Mod-10 test.
// isVisa (cc)                         True if string cc is a valid VISA number.
// isMasterCard (cc)                   True if string cc is a valid MasterCard number.
// isAmericanExpress (cc)              True if string cc is a valid American Express number.
// isDiscover (cc)                     True if string cc is a valid Discover card number.
// isAnyCard (cc)                      True if string cc is a valid card number for any of the accepted types.
// isCardMatch (Type, Number)          True if Number is valid for credic card of type Type.
//
// FUNCTIONS TO REFORMAT DATA:
// reformatZIPCode (ZIPString)         If 9 digits, inserts separator hyphen.
// reformatSSN (SSN)                   Reformats as 123-45-6789.
// reformatUSPhone (USPhone)           Reformats as (123) 456-7890.
//
// FUNCTIONS TO PROMPT USER:
// prompt (s)                          Display prompt string s in status bar.
// promptEntry (s)                     Display data entry prompt string s in status bar.
// warnInvalid (theField, s)           Notify user that contents of fields are invalid.
//
// ORIGINAL:
// 18 Feb 97 created Eric Krock
// (c) 1997 Netscape Communications Corporation
//
// MODIFIED FROM ORIGINAL
// 15 Jul 2001 Craig Meese
// visionbox
// =================================================================================================
// =================================================================================================

// CONSTANT STRING DECLARATIONS
// errFlag and msg for alert to user on err
var errFlag = 0;
var msg;
	msg  = "___________________________________________________________________________\n\n";
	msg += "Your information could not be submitted. Please check the message below.\n";
	msg += "___________________________________________________________________________\n\n";

// s is an abbreviation for "string"
var sUSLastName = "Last Name"
var sUSFirstName = "First Name"
var sWorldLastName = "Family Name"
var sWorldFirstName = "Given Name"
var sTitle = "Title"
var sCompanyName = "Company Name"
var sUSAddress = "Street Address"
var sWorldAddress = "Address"
var sCity = "City"
var sStateCode = "State"
var sWorldState = "State, Province, or Prefecture"
var sCountry = "Country"
var sZIPCode = "ZIP Code"
var sWorldPostalCode = "Postal Code"
var sPhone = "Phone Number"
var sFax = "Fax Number"
var sEmail = "Email"
var sSSN = "Social Security Number"
var sCreditCardNumber = "Credit Card Number"
var sOtherInfo = "Other Information"

// i is an abbreviation for "invalid"
var iStateCode = "State Code must be a valid two character U.S. state abbreviation (like: CA for California)."
var iZIPCode = "ZIP Code must be a 5 or 9 digit U.S. ZIP Code (like: 94043)."
var iUSPhone = "Phone/Fax must be a 10 digit U.S. phone number (like: 415 555 1212)."
var iWorldPhone = "Phone must be a valid international phone number."
var iSSN = "SSN must be a 9 digit U.S. social security number (like: 123 45 6789)."
var iEmail = "Email must be a valid email address (like: name@company.com)."
var iCreditCardPrefix = "Credit Card Number for -- "
var iCreditCardSuffix = " -- is not a valid number."

// p is an abbreviation for "prompt" (option for prompts in status bar)
var pEntryPrompt = "Enter a "
var pStateCode = "2 character U.S. state abbreviation (like: CA)."
var pZIPCode = "5 or 9 digit U.S. ZIP Code (like: 94043)."
var pUSPhone = "10 digit U.S. phone/fax number (like: 415 555 1212)."
var pWorldPhone = "international phone number."
var pSSN = "9 digit U.S. social security number (like: 123 45 6789)."
var pEmail = "valid email address (like: name@company.com)."
var pCreditCard = "valid credit card number."


// isSSN (STRING s [, BOOLEAN emptyOK])
// isSSN returns true if string s is a valid U.S. Social Security Number.  Must be 9 digits.
// NOTE: Strip out any delimiters (spaces, hyphens, etc.) from string s before calling this function.
function isSSN (s)
{   if (isEmpty(s))
       if (isSSN.arguments.length == 1) return defaultEmptyOK;
       else return (isSSN.arguments[1] == true);
    return (isInteger(s) && s.length == digitsInSocialSecurityNumber)
}

// isUSPhoneNumber (STRING s [, BOOLEAN emptyOK])
// isUSPhoneNumber returns true if string s is a valid U.S. Phone Number.  Must be 10 digits.
// NOTE: Strip out any delimiters (spaces, hyphens, parentheses, etc.) from string s before calling this function.
function isUSPhoneNumber (s)
{   if (isEmpty(s))
       if (isUSPhoneNumber.arguments.length == 1) return defaultEmptyOK;
       else return (isUSPhoneNumber.arguments[1] == true);
    return (isInteger(s) && s.length == digitsInUSPhoneNumber)
}

// isInternationalPhoneNumber (STRING s [, BOOLEAN emptyOK])
// isInternationalPhoneNumber returns true if string s is a valid
// international phone number.  Must be digits only; any length OK.
// May be prefixed by + character.
// NOTE: A phone number of all zeros would not be accepted.
// NOTE: Strip out any delimiters (spaces, hyphens, parentheses, etc.)
// from string s before calling this function.  You may leave in leading + character if you wish.
function isInternationalPhoneNumber (s)
{   if (isEmpty(s))
       if (isInternationalPhoneNumber.arguments.length == 1) return defaultEmptyOK;
       else return (isInternationalPhoneNumber.arguments[1] == true);
    return (isPositiveInteger(s))
}

// isZIPCode (STRING s [, BOOLEAN emptyOK])
// isZIPCode returns true if string s is a valid U.S. ZIP code.  Must be 5 or 9 digits only.
// NOTE: Strip out any delimiters (spaces, hyphens, etc.) from string s before calling this function.
function isZIPCode (s)
{  if (isEmpty(s))
       if (isZIPCode.arguments.length == 1) return defaultEmptyOK;
       else return (isZIPCode.arguments[1] == true);
   return (isInteger(s) &&
            ((s.length == digitsInZIPCode1) ||
             (s.length == digitsInZIPCode2)))
}

// isStateCode (STRING s [, BOOLEAN emptyOK])
// Return true if s is a valid U.S. Postal Code (abbreviation for state).
function isStateCode(s)
{   if (isEmpty(s))
       if (isStateCode.arguments.length == 1) return defaultEmptyOK;
       else return (isStateCode.arguments[1] == true);
    return ( (USStateCodes.indexOf(s) != -1) &&
             (s.indexOf(USStateCodeDelimiter) == -1) )
}

// isEmail (STRING s [, BOOLEAN emptyOK])
// Email address must be of form a@b.c -- in other words:
// * there must be at least one character before the @
// * there must be at least one character before and after the .
// * the characters @ and . are both required
function isEmail (s)
{   if (isEmpty(s))
       if (isEmail.arguments.length == 1) return defaultEmptyOK;
       else return (isEmail.arguments[1] == true);
    // is s whitespace?
    if (isWhitespace(s)) return false;
    // there must be >= 1 character before @, so we
    // start looking at character position 1
    // (i.e. second character)
    var i = 1;
    var sLength = s.length;
    // look for @
    while ((i < sLength) && (s.charAt(i) != "@"))
    { i++
    }
    if ((i >= sLength) || (s.charAt(i) != "@")) return false;
    else i += 2;
    // look for .
    while ((i < sLength) && (s.charAt(i) != "."))
    { i++
    }
    // there must be at least one character after the .
    if ((i >= sLength - 1) || (s.charAt(i) != ".")) return false;
    else return true;
}

//===========================================================
//FUNCTIONS TO NOTIFY USER OF INPUT REQUIREMENTS OR MISTAKES.
//===========================================================
// Display prompt string s in status bar.
function prompt (s)
{   window.status = s
}

// Display data entry prompt string s in status bar.
function promptEntry (s)
{   window.status = pEntryPrompt + s
}

// Notify user that contents of field theField are invalid.
// String s describes expected contents of theField.value.
// Put select theField, pu focus in it, and return false.
//function warnInvalid (theField, s)
function warnInvalid ()
{	if (errFlag == 1) {
        alert(msg);
		// reset variables for error message
        errFlag = 0;
		msg  = "___________________________________________________________________________\n\n";
		msg += "Your information could not be submitted. Please check the message below.\n";
		msg += "___________________________________________________________________________\n\n";
        return false;
        }
	else return true;
}

//===========================================================
//FUNCTIONS TO INTERACTIVELY CHECK VARIOUS FIELDS.
//===========================================================
// checkString (TEXTFIELD theField, STRING s, [, BOOLEAN emptyOK==false])
// Check that string theField.value is not all whitespace.
function checkString (theField, s, emptyOK)
{   // Next line is needed on NN3 to avoid "undefined is not a number" error
    // in equality comparison below.
    if (checkString.arguments.length == 2) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    if (isWhitespace(theField.value))
	{	errFlag = 1;
		msg += "Required Field:  " + s + "\n";
		return true; //returns true to continue processing all fields...will concantenate error msg
	}
    else return true;
}

// checkSelection(SELECT ELEMENT theField, STRING S)
// Check that a selection was made in a select element
function checkSelection(theField,s)
{
	if (theField.selectedIndex == -1 || theField.selectedIndex == 0)
	{
		errFlag = 1;
		msg += "Please make a valid selection for " + s + "\n";
		return true; //returns true to continue processing all fields...will concantenate error msg
	}
	else return true;
}

// checkNumbers(TEXTFIELD value, STRING s)
// Check that all characters are numeric
function checkNumbers(value,s)
{	
	if (value.length != 0)
	{
		for (var i = 0; i < value.length; i++)
		{
			if (!(isDigit(value.charAt(i))))
			{
				errFlag = 1;
				msg += "Please enter only digits for " + s + "\n";
				return true; //returns true to continue processing all fields...will concantenate error msg
				break;
			}
		}
	}
	else
	{
		errFlag = 1;
		msg += "Please enter only digits for " + s + "\n";
		return true; //returns true to continue processing all fields...will concantenate error msg
	}
	return true; //returns true to continue processing all fields...will concantenate error msg
}

// checkMaxLength(TEXTFIELD theField, INTEGER i, STRING s)
// Check to make sure theField does not have more than i characters
function checkMaxLength(theField,i,s)
{
	if (theField.value.length > i)
	{
		errFlag = 1;
		msg += "The maximum numbers of characters for " + s + " is " + i + "\n";
		return true;
	}
	else return true;
}

// checkMinLength(TEXTFIELD theField, INTEGER i, STRING s)
// Check to make sure theField has at least i characters
function checkMinLength(theField,i,s)
{
	if (theField.value.length < i)
	{
		errFlag = 1;
		msg += "You must enter at least " + i + " characters for " + s + "\n";
		return true;
	}
	else return true;
}

// checkStateCode (TEXTFIELD theField [, BOOLEAN emptyOK==false])
// Check that string theField.value is a valid U.S. state code.
// DO NOT CALL THIS FUNCTION UNLESS YOU ACTUALLY WANT TO FORCE A VALID 2 CHAR STATE CODE
// USStateCodes = "AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MP|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY|AE|AA|AE|AE|AP"
function checkStateCode (theField, emptyOK)
{   if (checkStateCode.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else
    {  theField.value = theField.value.toUpperCase();
       if (!isStateCode(theField.value, false))
		{	errFlag = 1;
			msg += iStateCode + "\n";
			return true; //returns true to continue processing all fields...will concantenate error msg
		}
       else return true;
    }
}

// takes ZIPString, a string of 5 or 9 digits;
// if 9 digits, inserts separator hyphen
function reformatZIPCode (ZIPString)
{   if (ZIPString.length == 5) return ZIPString;
    else return (reformat (ZIPString, "", 5, "-", 4));
}

// checkZIPCode (TEXTFIELD theField [, BOOLEAN emptyOK==false])
// Check that string theField.value is a valid ZIP code.
function checkZIPCode (theField, emptyOK)
{   if (checkZIPCode.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else
    { var normalizedZIP = stripCharsInBag(theField.value, ZIPCodeDelimiters)
      if (!isZIPCode(normalizedZIP, false))
		{	errFlag = 1;
			msg += iZIPCode + "\n";
			return true; //returns true to continue processing all fields...will concantenate error msg
		}
      else
      {  // if you don't want to insert a hyphen, comment next line out
         theField.value = reformatZIPCode(normalizedZIP)
         return true;
      }
    }
}

// takes USPhone, a string of 10 digits
// and reformats as (123) 456-7890
function reformatUSPhone (USPhone)
{   return (reformat (USPhone, "(", 3, ") ", 3, "-", 4))
}

// checkUSPhone (TEXTFIELD theField [, BOOLEAN emptyOK==false])
// Check that string theField.value is a valid US Phone.
function checkUSPhone (theField, emptyOK)
{   if (checkUSPhone.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else
    {  var normalizedPhone = stripCharsInBag(theField.value, phoneNumberDelimiters)
       if (!isUSPhoneNumber(normalizedPhone, false))
		{	errFlag = 1;
			msg += iUSPhone + "\n";
			return true; //returns true to continue processing all fields...will concantenate error msg
		}
       else
       {  // if you don't want to reformat as (123) 456-7890, comment next line out
          theField.value = reformatUSPhone(normalizedPhone)
          return true;
       }
    }
}

// checkInternationalPhone (TEXTFIELD theField [, BOOLEAN emptyOK==false])
// Check that string theField.value is a valid International Phone.
function checkInternationalPhone (theField, emptyOK)
{   if (checkInternationalPhone.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else
    {  if (!isInternationalPhoneNumber(theField.value, false))
		{	errFlag = 1;
			msg += iWorldPhone + "\n";
			return true; //returns true to continue processing all fields...will concantenate error msg
}
       else return true;
    }
}

// checkEmail (TEXTFIELD theField [, BOOLEAN emptyOK==false])
// Check that string theField.value is a valid Email.
function checkEmail (theField, emptyOK)
{   if (checkEmail.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else if (!isEmail(theField.value, false))
		{	errFlag = 1;
			msg += iEmail + "\n";
			return true; //returns true to continue processing all fields...will concantenate error msg
}
    else return true;
}

// takes SSN, a string of 9 digits and reformats as 123-45-6789
function reformatSSN (SSN)
{   return (reformat (SSN, "", 3, "-", 2, "-", 4))
}

// Check that string theField.value is a valid US SSN.
function checkSSN (theField, emptyOK)
{   if (checkSSN.arguments.length == 1) emptyOK = defaultEmptyOK;
    if ((emptyOK == true) && (isEmpty(theField.value))) return true;
    else
    {  var normalizedSSN = stripCharsInBag(theField.value, SSNDelimiters)
       if (!isSSN(normalizedSSN, false))
		{	errFlag = 1;
			msg += iSSN + "\n";
			return true;
		}
       else
       {  // if you don't want to reformats as 123-456-7890, comment next line out
          theField.value = reformatSSN(normalizedSSN)
          return true;
       }
    }
}

// Get checked value from radio button.
function getRadioButtonValue (radio)
{   for (var i = 0; i < radio.length; i++)
    {   if (radio[i].checked) { break }
    }
    return radio[i].value
}

// Validate credit card info.
function checkCreditCard (radio, theField)
{   var cardType = getRadioButtonValue (radio)
    var normalizedCCN = stripCharsInBag(theField.value, creditCardDelimiters)
    if (!isCardMatch(cardType, normalizedCCN))
		{	errFlag = 1;
		msg += iCreditCardPrefix + cardType + iCreditCardSuffix + "\n";
		return true;
		}
    else
    {  theField.value = normalizedCCN
       return true
    }
}

/*  ================================================================
    FUNCTION: isCreditCard(st)
    INPUT:    st - a string representing a credit card number
    RETURNS:  true, if the cc number passes the Luhn Mod-10 test.
              false, otherwise
    ================================================================
*/
function isCreditCard(st) {
  // Encoding only works on cards with less than 19 digits
  if (st.length > 19)
    return (false);
  sum = 0; mul = 1; l = st.length;
  for (i = 0; i < l; i++) {
    digit = st.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--;
  }
// TO
// 1. Create a dummy number with a 0 as the last digit
// 2. Examine the sum written out
// 3. Replace the last digit with the difference between the sum and
//    the next multiple of 10.
// Uncomment the following lines to help create credit card numbers
//  document.writeln("<BR>Sum      = ",sum,"<BR>");
//  alert("Sum      = " + sum);
  if ((sum % 10) == 0)
    return (true);
  else
    return (false);
} // END FUNCTION isCreditCard()


/*  ================================================================
    FUNCTION: isVisa()
    INPUT:    cc - a string representing a credit card number
    RETURNS:  true, if the credit card number is a valid VISA number.
              false, otherwise
    Sample number: 4111 1111 1111 1111 (16 digits)
    ================================================================*/
function isVisa(cc)
{
  if (((cc.length == 16) || (cc.length == 13)) &&
      (cc.substring(0,1) == 4))
    return isCreditCard(cc);
  return false;
}  // END FUNCTION isVisa()


/*  ================================================================
    FUNCTION: isMasterCard()
    INPUT:    cc - a string representing a credit card number
    RETURNS:  true, if the cc number is a valid MasterCard number.
              false, otherwise
    Sample number: 5500 0000 0000 0004 (16 digits)
    ================================================================*/
function isMasterCard(cc)
{
  firstdig = cc.substring(0,1);
  seconddig = cc.substring(1,2);
  if ((cc.length == 16) && (firstdig == 5) &&
      ((seconddig >= 1) && (seconddig <= 5)))
    return isCreditCard(cc);
  return false;
} // END FUNCTION isMasterCard()


/*  ================================================================
    FUNCTION: isAmericanExpress()
    INPUT:    cc - a string representing a credit card number
    RETURNS:  true, if the cc number is a valid AmEx number.
              false, otherwise
    Sample number: 340000000000009 (15 digits)
    ================================================================*/
function isAmericanExpress(cc)
{
  firstdig = cc.substring(0,1);
  seconddig = cc.substring(1,2);
  if ((cc.length == 15) && (firstdig == 3) &&
      ((seconddig == 4) || (seconddig == 7)))
    return isCreditCard(cc);
  return false;
} // END FUNCTION isAmericanExpress()


/*  ================================================================
    FUNCTION: isDiscover()
    INPUT:    cc - a string representing a credit card number
    RETURNS:  true, if the ccnumber is a valid Discover card number.
              false, otherwise
    Sample number: 6011000000000004 (16 digits)
    ================================================================*/
function isDiscover(cc)
{
  first4digs = cc.substring(0,4);
  if ((cc.length == 16) && (first4digs == "6011"))
    return isCreditCard(cc);
  return false;
} // END FUNCTION isDiscover()


/*  ================================================================
    FUNCTION: isAnyCard()
    INPUT:    cc - a string representing a credit card number
    RETURNS:  true, if the credit card number is any valid credit
              card number for any of the accepted card types.
              false, otherwise
    ================================================================*/
function isAnyCard(cc)
{
  if (!isCreditCard(cc))
    return false;
  if (!isMasterCard(cc) && !isVisa(cc) && !isAmericanExpress(cc) && !isDiscover(cc)) {
    return false;
  }
  return true;
} // END FUNCTION isAnyCard()

/*  ================================================================
    FUNCTION: isCardMatch()
    INPUT:    cardType - a string representing the credit card type
	          cardNumber - a string representing a credit card number
    RETURNS:  true, if the credit card number is valid for the particular
	          credit card type given in "cardType".
	          false, otherwise
    ================================================================*/
function isCardMatch (cardType, cardNumber)
{
	cardType = cardType.toUpperCase();
	var doesMatch = true;
	if ((cardType == "VISA") && (!isVisa(cardNumber)))
		doesMatch = false;
	if ((cardType == "MASTERCARD") && (!isMasterCard(cardNumber)))
		doesMatch = false;
	if ( ( (cardType == "AMERICANEXPRESS") || (cardType == "AMEX") )
                && (!isAmericanExpress(cardNumber))) doesMatch = false;
	if ((cardType == "DISCOVER") && (!isDiscover(cardNumber)))
		doesMatch = false;
	return doesMatch;
}  // END FUNCTION CardMatch()