// checks if a string contains only whitespace characters
function isBlank(s) {
  for (var i = 0; i < s.length; i++) {
    var c = s.charAt(i);
    if (( c != ' ') && (c != '\n') && (c != '\r') && (c != '\t')) {
      return false;
    }
  }
  return true;
}

// these might come in handy some day...
function trimEnd (s) {
  var end = s.length-1;
  while( end > 0 && isBlank(s.charAt(end)) ) { 
    end--;
  }

  return s.substring(0,end+1);
}

function trimFront (s) {
  var start=0;
  while( start < s.length && isBlank(s.charAt(start)) ) { 
    start++; 
  }

  return s.substring(start,s.length);
}

function trim (s) {
  return trimFront (trimEnd (s));
}

// checks if a string contains a "valid" email address - a@b.c
// invalid if...
//     contains any whitespace
//     no '@'
//     more than one '@'
//     no '.' after '@'
//     email domain after '@' does not start and end with an alphanumeric char
// note that empty strings are "valid".
// 
// if the "silent" param is false or not present, alert the user
// if the "silent" param is true, just return true for a good email address,
//    false for bad email
var emailOkChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

function isEmailAddress (s, silent) {
  var valid = true;
  if ( ! isBlank (''+s) ) {
    var at1 = s.indexOf ("@");
    var at2 = s.lastIndexOf ("@");
    var dot = s.lastIndexOf (".");
    var space = s.indexOf(" ");
    var tab = s.indexOf("\t");
    // handle case when "silent" parameter is not present
    if (!silent) {
        silent = false;
    }
    // invalid if...
    //     no '@'
    //     no '.'
    //     more than one '@'
    //     last '@' comes before last '.'
    //     embedded whitespace
    if ( (at1 < 0) || (dot < 0) || (at1 != at2) || (at2 > dot) || (space >= 0) || (tab >= 0) ) {
      valid = false;
    }
    else {
      // now check that the domain part after the '@' 
      // starts and ends with an alphanumeric
      var domainStart = s.charAt(at1+1);
      var domainEnd = s.charAt(s.length - 1);
      if ((emailOkChars.indexOf(domainStart) < 0)
              || (emailOkChars.indexOf(domainEnd) < 0)) {
        valid = false;
      }
    }
  }
  if (!valid && !silent) {
    alert ('Invalid email address: ' + s);
  }
  return valid;
}


function validate(f, reqField, maxLengths, minLengths) {

    if( reqField && 
        reqField.length > 0 &&
        reqField[0].length != 2) 
    {
        // no field names, must be old version of validate
        for( var i=0; i<reqField.length; i++ ) {
            reqField[i] = new Array( reqField[i], reqField[i] );
        }
    }

    if( maxLengths && 
        maxLengths.length > 0 &&
        maxLengths[0].length != 3)
    {
        // no field names, must be old version of validate
        for( var i=0; i<maxLengths.length; i++ ) {
            maxLengths[i] = new Array( maxLengths[i][0], 
                                       maxLengths[i][1], 
                                       maxLengths[i][0] );
        }
    }        

    if( minLengths && 
        minLengths.length > 0 &&
        minLengths[0].length != 3)
    {
        // no field names, must be old version of validate
        for( var i=0; i<minLengths.length; i++ ) {
            minLengths[i] = new Array( minLengths[i][0], 
                                       minLengths[i][1], 
                                       minLengths[i][0] );
        }
    }     

    return validateWithFieldNames(f, reqField, maxLengths, minLengths);
}

function getFormElement(f, elemName) {
  var elem;
  if (f) {
    for (var elemNum=0; (elemNum < f.elements.length) && !elem; elemNum++) {
      if (f.elements[elemNum].name == elemName) {
        elem = f.elements[elemNum];
      }
    }
  }
  return elem;
}

// validates the form data
function validateWithFieldNames(f, reqField, maxLengths, minLengths) {
  var validateMsg = "ERROR: The following fields are required: \n\n"; 
  var hasError = false;
  var field, i, len, max, diff;

  // first make sure all required fields are present, selected, 
  // and/or not black
  for ( i=0; i < reqField.length; i++) {
    field = getFormElement(f, reqField[i][0]);
    // add checking for select boxes
    // make sure there is at least one selected item in the box
    if (field && field.type && field.type.indexOf("select") == 0) {
      // log an error if at least one item is not selected
      if (field.selectedIndex == -1) {
        validateMsg += reqField[i][1];
        validateMsg += "\n";
        hasError = true;
      }
    } 
    else {
      if (field && field[0]) {
         // just test the first field if there are 
         // multiple with the same name
         field = field[0];
      }

      // log an error if the field is blank or not found
      if( !field || isBlank(field.value) ) {
        validateMsg += reqField[i][1];
        validateMsg += "\n";
        hasError = true;
      }
    }
  }

  if (!hasError && maxLengths) {
    validateMsg = "ERROR: The following fields have exceeded maximum length: \n\n";
    for( i=0; i < maxLengths.length; i++ ) {
      field = eval("f." + maxLengths[i][0] );
      max = maxLengths[i][1];
      if( field ) {
        if (field[0]) {
          for (var p = 0; p < field.length; p++) {
            len = field[p].value.length;
            diff = len-max;
            if( diff > 0 ) {
              validateMsg += maxLengths[i][2] + "(" + (p+1) + ") has a maximum length of " + max + ".";
              validateMsg += "\n    You have exceeded the maximum by " + diff + " characters.";
              validateMsg += "\n\n";
              hasError = true;
            }
          }
        }
        else {
          len = field.value.length;
          diff = len-max;
          if( diff > 0 ) {
            validateMsg += maxLengths[i][2] + " has a maximum length of " + max + ".";
            validateMsg += "\n    You have exceeded the maximum by " + diff + " characters.";
            validateMsg += "\n\n";
            hasError = true;
          }
        }
      }
    }
  }


  if (!hasError && minLengths) {
    validateMsg = "ERROR: The following fields do not satisfy minimum length requirements: \n\n";
    for( i=0; i < minLengths.length; i++ ) {
      diff = -1;
      field = eval("f." + minLengths[i][0] );
      min = minLengths[i][1];
      if( field ) {
        len = field.value.length;
        diff = len-min;
      } else {
        // field can't be blank if it has a minimum requirement
        diff = -1;
      }
      if( diff < 0 ) {
        validateMsg += minLengths[i][2] + " requires at least " + min + " characters.";
        validateMsg += "\n";
        hasError = true;
      }
    }
  }

  if( hasError ) {
    alert(validateMsg);
    return false;
  }

  return true;     
}


/*  ================================================================
    FUNCTION:  isCreditCard(st)
 
    INPUT:     st - a string representing a credit card number

    RETURNS:  true, if the credit card 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--;
  }
// Uncomment the following line to help create credit card numbers
// 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.

//  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 credit card 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 credit card number is a valid American
		    Express 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:  isDinersClub()
 
    INPUT:     cc - a string representing a credit card number

    RETURNS:  true, if the credit card number is a valid Diners
		    Club number.
		    
	      false, otherwise

    Sample number: 30000000000004 (14 digits)
    ================================================================ */

function isDinersClub(cc)
{
  firstdig = cc.substring(0,1);
  seconddig = cc.substring(1,2);
  if ((cc.length == 14) && (firstdig == 3) &&
      ((seconddig == 0) || (seconddig == 6) || (seconddig == 8)))
    return isCreditCard(cc);
  return false;
}



/*  ================================================================
    FUNCTION:  isCarteBlanche()
 
    INPUT:     cc - a string representing a credit card number

    RETURNS:  true, if the credit card number is a valid Carte
		    Blanche number.
		    
	      false, otherwise
    ================================================================ */

function isCarteBlanche(cc)
{
  return isDinersClub(cc);
}




/*  ================================================================
    FUNCTION:  isDiscover()
 
    INPUT:     cc - a string representing a credit card number

    RETURNS:  true, if the credit card number 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:  isEnRoute()
 
    INPUT:     cc - a string representing a credit card number

    RETURNS:  true, if the credit card number is a valid enRoute
		    card number.
		    
	      false, otherwise

    Sample number: 201400000000009 (15 digits)
    ================================================================ */

function isEnRoute(cc)
{
  first4digs = cc.substring(0,4);
  if ((cc.length == 15) &&
      ((first4digs == "2014") ||
       (first4digs == "2149")))
    return isCreditCard(cc);
  return false;
}



/*  ================================================================
    FUNCTION:  isJCB()
 
    INPUT:     cc - a string representing a credit card number

    RETURNS:  true, if the credit card number is a valid JCB
		    card number.
		    
	      false, otherwise
    ================================================================ */

function isJCB(cc)
{
  first4digs = cc.substring(0,4);
  if ((cc.length == 16) &&
      ((first4digs == "3088") ||
       (first4digs == "3096") ||
       (first4digs == "3112") ||
       (first4digs == "3158") ||
       (first4digs == "3337") ||
       (first4digs == "3528")))
    return isCreditCard(cc);
  return false;

} // END FUNCTION isJCB()



/*  ================================================================
    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) && !isDinersClub(cc) &&
      !isDiscover(cc) && !isEnRoute(cc) && !isJCB(cc)) {
    return false;
  }
  return true;

} // END FUNCTION isAnyCard()



