// +---------------------------------------------------------------------+
// |          Copyright (c) 2000-2001 Cipient Networks Inc.              |
// |                         All Rights Reserved.                        |
// |      CONFIDENTIAL MATERIAL - UNAUTHORIZED ACCESS PROHIBITED.        |
// |---------------------------------------------------------------------|
// |    module:  /lib/validation.js                	        	         |
// |  platform:  Independent                                             |
// |    author:  Christopher Go                                          |
// +---------------------------------------------------------------------+

var FIELD_TYPE_TEXT         = 0;
var FIELD_TYPE_NUMBER       = 1;
var FIELD_TYPE_INTEGER      = 2;
var FIELD_TYPE_DATE         = 3;
var FIELD_TYPE_EMAIL        = 4;
var FIELD_TYPE_URL          = 5;
var FIELD_TYPE_PHONE        = 6;
var FIELD_TYPE_FLOAT        = 7;
var FIELD_TYPE_DATETIME     = 8;
var FIELD_TYPE_CCNUM        = 9;
var FIELD_TYPE_CCEXP        = 10;
var FIELD_TYPE_ZIP          = 11;
var FIELD_TYPE_ROUTING      = 12;
var FIELD_TYPE_AMOUNT       = 13;
var FIELD_TYPE_SSN          = 14;
var FIELD_TYPE_TIME         = 15;
var FIELD_TYPE_TOTAL_AMOUNT = 16;

var defaultEmptyOK = true;

// ================================================== FORM CHECKER OBJECT

function FormChecker(aFormName, aStrict, aAutoFix) {
    this.objname    = "object " + aFormName + " FormChecker";
    this.errorArr   = new Array();
    this.fieldArr   = new Array();
    this.strict     = true;
    this.autoFix    = true;
    this.checkFormField         = checkFormField;           // checkFormField method
    this.displayFormErrors      = displayFormErrors;        // displayFormErrors method
    this.getFormErrorsAsString  = getFormErrorsAsString;    // getFormErrorsAsString method
    this.process                = process;                  // process method
    this.addFormErrorMessage    = addFormErrorMessage;      // addFormErrorMessage method
    this.getFieldValue          = getFieldValue;            // get the value of text/pass/hidden form field
    if( aStrict!=null ) this.strict = aStrict;
    if( aAutoFix!=null ) this.autoFix = aAutoFix;
    // Immediately check to see if the form exists
    this.formObj = document.forms[aFormName];
    if( !this.formObj || !this.formObj.name ) {
        for( ii=0; ii<document.forms.length; ii++ ) {
            if( document.forms[ii].name==aFormName ) {
                this.formObj = document.forms[ii];
                break;
            }
        }
        //alert("bad");
    }
    if( !this.formObj || !this.formObj.name ) {
        alert("Form with name '" + aFormName + "' does not exist on this page.");
        return null;
    }
    return this;
}

/**
 * Checks a single form field and start populating error array if an
 * error is found.
 */
function checkFormField(aFieldName, aLabel, aRequired, aStringType, aRegExp, aMaxlength) {
    // (1) Check to see if field EXISTS in form.  Only display error if STRICT is on
    if( !this.formObj.elements[aFieldName] && this.strict ) {
        this.errorArr.push('Form element name="' + aFieldName + '" [' + aLabel + '] does not exist!');
        this.valid = false;
    }
    var fieldObj = this.formObj.elements[aFieldName];
    // (2) Check REQUIRED field
    if( aRequired && trimstr(fieldObj.value)=='' ) {
        this.errorArr.push(aLabel + ' is a required field');
        this.valid = false;
    }
    // (3) Check TYPE (based on provided types)
    if( trimstr(fieldObj.value)!='' ) {
        var value = trimstr(fieldObj.value);
        switch( aStringType ) {
            case FIELD_TYPE_TEXT:
                break;
            case FIELD_TYPE_NUMBER:  // OK
                valueCleaned = value;
                valueForCheck = value.replace(/-/,"");
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isFloat(valueForCheck) ) {
                    this.errorArr.push(aLabel + ' needs to be a number');
                }
                break;
            case FIELD_TYPE_INTEGER: // OK
                valueCleaned = value.replace(/\D/gi,"");
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isInteger(valueCleaned) ) {
                    this.errorArr.push(aLabel + ' needs to be a number');
                }
                break;
            case FIELD_TYPE_DATE: // OK
                valueCleaned = value.replace(/\-/gi,"/")
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isDateString(valueCleaned) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid date (MM/DD/YYYY format)');
                }
                break;
            case FIELD_TYPE_EMAIL:
                if( !isEmail(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid email address');
                }
                break;
            case FIELD_TYPE_URL:
                if( !isURL(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid URL (starts with http://)');
                }
                break;
            case FIELD_TYPE_PHONE: // OK
                valueCleaned = value.replace(/\./gi,"-")
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isPhoneNumber(valueCleaned) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid US phone number');
                }
                break;
            case FIELD_TYPE_FLOAT: // OK
                valueCleaned = value;
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isFloat(valueCleaned) ) {
                    this.errorArr.push(aLabel + ' needs to be a number (decimals ok)');
                }
                break;
            case FIELD_TYPE_DATETIME:
                if( !isDateTimeString(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid date/time (MM/DD/YYYY HH:MM AM format)');
                }
                break;
            case FIELD_TYPE_CCNUM: // OK
                valueCleaned = value.replace(/\-/gi,"")
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if ( valueCleaned.length<15 || valueCleaned.length>16 || !isInteger(valueCleaned) ) {
                    this.errorArr.push(aLabel + ' needs to be a 15 (Amex) or 16 digit number');
                }
                break;
            case FIELD_TYPE_CCEXP: // OK
                valueCleaned = value.replace(/\//gi,"")
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if( !isInteger(valueCleaned) || valueCleaned.length!=4 ) {
                    this.errorArr.push(aLabel + ' needs to be a 4 numbers (MMYY format)');
                }
                break;
            case FIELD_TYPE_ZIP:
                if ( !isInteger(value) || value.length!=5 ) {
                    this.errorArr.push(aLabel + ' needs to be a 5 digit number');
                }
                break;
            case FIELD_TYPE_ROUTING:
                valueCleaned = value.replace(/\D/gi,"");
                if( this.autoFix ) fieldObj.value = valueCleaned;
                if ( !isInteger(valueCleaned) || valueCleaned.length!=9 ) {
                    this.errorArr.push(aLabel + ' needs to be a 9 digit number');
                }
                break;
            case FIELD_TYPE_AMOUNT:
                if ( !isAmount(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid amount in US currency');
                }
                break;
            case FIELD_TYPE_TIME: // time field 
                if ( !isTimeStr(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid time (HH:MM AM)');
                }
                break;
            case FIELD_TYPE_SSN: // ssn field 
                if ( !isSSN(value) ) {
                    this.errorArr.push(aLabel + ' needs to be a valid Social Security Number');
                }
                break;
            case FIELD_TYPE_TOTAL_AMOUNT: // Total amount for purchase new licenses
                if (value > 0) {
                    if (!isAmount(value)) {
                        this.errorArr.push(aLabel + ' needs to be a valid amount in US currency');
                    }
                }else{
                    this.errorArr.push(aLabel + " can't be zero. ");
                }
                break;
        }
    }
    // (4) Regular Expression TODO:
    // (5) Check MAXLENGTH
    if( aMaxlength!=null && aMaxlength>0 ) {
        if( fieldObj.value.length > aMaxlength ) {
            this.errorArr.push(aLabel + " can only have " + aMaxlength + " characters.");
        }
    }
}

function process() {
    if( this.errorArr.length>0 ) return false
    else return true;
}

function displayFormErrors() {
    if ( this.errorArr.length>0 ) {
        var errorStr = "";
        for( ii=0; ii<this.errorArr.length; ii++ ) {
            errorStr += this.errorArr[ii] + "\n";
        }
        alert('Please correct the following errors to continue.\n\n' + errorStr);
        return false;
    }
    return true;
}

function getFormErrorsAsString() {
    if ( this.errorArr.length>0 ) {
        var errorStr = "";
        for( ii=0; ii<this.errorArr.length; ii++ ) {
            errorStr += this.errorArr[ii] + "\n";
        }
        return errorStr;
    } else {
        return "";
    }
}

/**
 * Adds an error message 
 */
function addFormErrorMessage(aErrorMessage) {
    if( aErrorMessage!="" ) this.errorArr.push(aErrorMessage);
}

function getFieldValue(aFieldName) {
    if( this.formObj.elements[aFieldName] ) return this.formObj.elements[aFieldName].value;
    else return "";
}


// ================================================== FORM CHECKER OBJECT


// Expression constants
var DIGITS              = "0123456789";
var LOWER_CASE_LETTERS  = "abcdefghijklmnopqrstuvwxyz"
var UPPER_CASE_LETTERS  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var DEFAULT_EMPTY_OK    = false;
var DAYS_IN_MONTH       = new Array(0,31,29,31,30,31,30,31,31,30,31,30,31);
var WHITESPACE          = " \t\n\r";
var DECIMAL_DELIMITER   = ".";
var DATE_DELIMITER      = "-/.";
var SPECIAL_CHARS       = new Array( "<", "\"", "'", ">", ",", "&", ";", ":", ".", "%", "^", "$", "#", "@", "(", ")", "/", "?", "!", "-", "[", "]", "+", "=", "_", '*', "|");

// Form field type constants
var TEXT                = 0;
var PASSWORD            = 1;
var SELECT              = 2;
var RADIO               = 3;
var CHECKBOX            = 4;
var BUTTON              = 5;
var SUBMIT              = 6;
var RESET               = 7;
var HIDDEN              = 8;
var TEXTAREA            = 9;
var FIELD_TYPE          = new Array("text","password","select-one","radio","checkbox","button","submit","reset","hidden","textarea");

// Returns true if s is null or empty
function isEmpty(s) {
  return ((s == null) || (s.length == 0));
}

// Returns true if c is a letter
function isLetter (c) {   
  return ( ((c >= "a") && (c <= "z")) || ((c >= "A") && (c <= "Z")) );
}

// Returns true if c is a digit
function isDigit (c) {
  return ((c >= "0") && (c <= "9"));
}

// Returns true if c is a letter OR digit
function isLetterOrDigit (c) {   
  return (isLetter(c) || isDigit(c));
}

// Returns true if c is a special character
function isSpecialCharacter(c) {
  for (var i=0; i<SPECIAL_CHARS.length; i++) {
    if (c==SPECIAL_CHARS[i]) return true;
  }
  return false;
}

// Returns true if s is a whitespace characters
function isWhitespace (s) {
  var i;
  if (isEmpty(s)) return true;
  // Search through string's characters one by one
  // until we find a non-whitespace character.
  // When we do, return false; if we don't, return true.
  for (i = 0; i < s.length; i++) {   
      // Check that current character isn't whitespace.
      var c = s.charAt(i);

      if (WHITESPACE.indexOf(c) == -1) return false;
  }
  // All characters are whitespace.
  return true;
}

// Returns true if s is an integer within the range of integer arguments a and b, inclusive.
function isIntegerInRange (s, a, b) {
  if (isEmpty(s)) 
    if (isIntegerInRange.arguments.length == 1) return DEFAULT_EMPTY_OK;
    else return (isIntegerInRange.arguments[1] == true);
  // Catch non-integer strings to avoid creating a NaN below,
  // which isn't available on JavaScript 1.0 for Windows.
  if (!isInteger(s, false)) return false;
  // Now, explicitly change the type to integer via parseInt
  // so that the comparison code below will work both on 
  // JavaScript 1.2 (which typechecks in equality comparisons)
  // and JavaScript 1.1 and before (which doesn't).
  var num = parseInt (s);
  return ((num >= a) && (num <= b));
}

// Returns true if s is an integer
function isInteger (s) {
  var i;
  if (isEmpty(s)) 
     if (isInteger.arguments.length == 1) return defaultEmptyOK;
     else return (isInteger.arguments[1] == true);
  for (i = 0; i < s.length; i++) {   
      var c = s.charAt(i);
      if (!isDigit(c)) return false;
  }
  return true;
}

// Returns true if s is a float
function isFloat (s) {
  var i;
  var seenDecimalPoint = false;
  if (isEmpty(s)) 
     if (isFloat.arguments.length == 1) return DEFAULT_EMPTY_OK;
     else return (isFloat.arguments[1] == true);
  if (s == DECIMAL_DELIMITER) return false;
  for (i = 0; i < s.length; i++) {   
      var c = s.charAt(i);
      if ((c == DECIMAL_DELIMITER) && !seenDecimalPoint) seenDecimalPoint = true;
      else if (!isDigit(c)) return false;
  }
  return true;
}

// Returns true if s only contains both numbers and letters
function isAlphanumeric (s) {   
  var i;
  if (isEmpty(s)) 
     if (isAlphanumeric.arguments.length == 1) return DEFAULT_EMPTY_OK;
     else return (isAlphanumeric.arguments[1] == true);
  for (i = 0; i < s.length; i++){   
      var c = s.charAt(i);
      if (! (isLetter(c) || isDigit(c) || isWhitespace(c) || isSpecialCharacter(c) ) )
      return false;
  }
  return true;
}

// Returns true if s is a valid email 
function isEmail (s) {
  if (isEmpty(s)) 
     if (isEmail.arguments.length == 1) return DEFAULT_EMPTY_OK;
     else return (isEmail.arguments[1] == true);
  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;
}

// Checks to sees if the string is a valid URL
function isURL(s) {
    if (isEmpty(s)) 
        if (isURL.arguments.length == 1) return DEFAULT_EMPTY_OK;
        else return (isURL.arguments[1] == true);
    if (isWhitespace(s)) return false;
    return true;
}

// Returns true is s is a valid month
function isMonth (s) {  
  if (isEmpty(s)) 
    if (isMonth.arguments.length == 1) return DEFAULT_EMPTY_OK;
    else return (isMonth.arguments[1] == true);
  return isIntegerInRange (s, 1, 12);
}

// Returns true is s is a valid day
function isDay (s) {
  if (isEmpty(s)) 
    if (isDay.arguments.length == 1) return DEFAULT_EMPTY_OK;
    else return (isDay.arguments[1] == true);   
  return isIntegerInRange (s, 1, 31);
}

// Returns true is s is a valid year
function isYear (s) {   
    if (isEmpty(s)) 
    if (isYear.arguments.length == 1) return DEFAULT_EMPTY_OK;
    else return (isYear.arguments[1] == true);
    return ((s.length == 2) || (s.length == 4));
}

// Returns the number of days in February given the year
function daysInFebruary (year) {
  // February has 29 days in any year evenly divisible by four,
  // EXCEPT for centurial years which are not also divisible by 400.
  return (  ((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0) ) ) ? 29 : 28 );
}

// Returns true if year, month and day returns a valid date
function isDate (year, month, day) {
    // catch invalid years (not 2- or 4-digit) and invalid months and days.
    //if (! (isYear(year, false) && isMonth(month, false) && isDay(day, false))) return false;
    // Explicitly change type to integer to make code work in both
    // JavaScript 1.1 and JavaScript 1.2.
    var intYear     = parseInt(year);
    var intMonth    = parseInt(month);
    var intDay      = parseInt(day);
    // catch invalid days, except for February
    if ( intDay > DAYS_IN_MONTH[intMonth] ) return false; 
    if ( (intMonth == 2) && (intDay > daysInFebruary(intYear)) ) return false;
    if ( !is4DigitYear(intYear) ) return false;
    return true;
}

/**
 * Checks to see if date is a valid date string
 * Dates can be in the following format:
 *   MM/DD/YYYY or MM-DD-YYYY
 */
function isDateString (aDateStr) {
    // (1) Validate the regular expression
    // var regexp = /^\d{2}[-/]\d{2}[-/]\d{4}?/;
    // var regexp = new RegExp("\d{2}[-/]\d{2}[-/]\d{4}?","i");
    // var regexp = new RegExp("\d{2}[-/]\d{2}[-/]\d{2,4}","i");
    var regexp = new RegExp("[0-1][0-9][-/][0-3][0-9][-/]\\d{2,4}","i");
    //alert(regexp.toSource());
    //alert(regexp.test(aDateStr));
    if( !regexp.test(aDateStr) ) return false;
    //alert('got thru');
    // (2) Grab the month, day and year (Bug in parseInt so using parseFloat)
    var month   = parseFloat(aDateStr.substring(0,2));
    var day 	= parseFloat(aDateStr.substring(3,5));
    var yearStr = aDateStr.substring(6,10);
    // (3) Make sure day given is allowed for that month
    if ( day > DAYS_IN_MONTH[month] ) return false; 
    // (4) Catch wrong dates in Feb (leap year)
    if ( (month == 2) && (day > daysInFebruary(year)) ) return false;
    // (4.5) Make the year string be 4 digits if necessary
    if( yearStr.length==2 ) {
        yearStr = "20"+yearStr;
    }
    // (5) Make sure the year is a valid 4 digit number
    var year = parseInt(yearStr);
    if ( !is4DigitYear(year) ) return false;
    return true;
}

/**
 * Format is MM/DD/YYYY HH:MM AM
 */
function isDateTimeString(aDateTimeStr) {
    // (1) Parse the date
    if( !isDateString(aDateTimeStr.substring(0,10)) ) return false;
    // (2) Do the TIME PART of the string 
    //     (use the function below but you need to parse it before sending!!)
    
    return true;
}

/** 
 * Valid format for tie string is HH:MM am or HH:MM pm
 */
function isTimeString(aTimeStr) {
    // (1) Make sure there are two digits in front of the first colon
    if(aTimeStr.substring(2,3)!=":") aTimeStr = "0"+aTimeStr;
    // (2) Validate against a regular express first 
    var regexp = new RegExp(/^\d{2}\:\d{2}\ (am|pm|AM|PM|Am|Pm)$/);
    if( !regexp.test(aTimeStr) ) return false;
    // (3) First two numbers needs to be between 1 and 12
    if( parseInt(aTimeStr.substring(0,2))<0 || parseInt(aTimeStr.substring(0,2))>12 ) return false;
    // (4) Second set of numbers need to be between 0 and 59
    if( parseInt(aTimeStr.substring(3,5))<0 || parseInt(aTimeStr.substring(3,5))>59 ) return false;
    // (5) Last letters needs to be am or pm (reg exp already handled this)
    return true;
}

/**
 * Checks to see if the year passed in is a valid year
 * Currently only checks from 1900 to 3000
 */
function is4DigitYear(aYear) {
    if ( isEmpty(aYear) ) return false;
    if ( aYear.length < 4 ) return false;
    if ( !isIntegerInRange(aYear,1900,3000) ) return false;
    return true;
}

/**
 * New phone number regular expression to
 * handle extensions
 */
function isPhoneNumber(s) {
    if( s.indexOf('X')>-1 || s.indexOf('x')>-1 ) {
        //var regexp = /^\d{3}\-\d{3}\-\d{4}?\s*-?\s*[Xx]\d{1,5}$/;
        var regexp = new RegExp(/^\d{3}\-\d{3}\-\d{4}?\s*-?\s*[Xx]\d{1,5}$/);
    } else {
        //var regexp = /^\d{3}\-\d{3}\-\d{4}$/;
        //var regexp = new RegExp("\\d{3}\-\d{3}\-\d{4}$","i");
        var regexp = new RegExp(/^\d{3}\-\d{3}\-\d{4}$/);
    }
    return regexp.test(s);
}

/**
 * Checks to see if the amount typed in is a valid amount
 * This number will be a FLOAT
 * TODO: Remove the dollar sign (they shouldn't be typing dollar signs in)
 */
function isAmount(s) {
    // (1) Get rid of the commas and dollar symbols
    s = str_replace('$','',s);
    s = str_replace(',','',s);
    s = str_replace('-','',s);
    if( isFloat(s) ) {
        return true;
    } 
    return false;
}

function isSSN(s) {
    // (1) Get rid of the commas and dollar symbols
    /*  
    s = str_replace('-','',s);
    if( isInteger(s) && s.length==9 ) {
        return true;
    } 
    return false;
    */
	var regexp = new RegExp(/^\d{3}\-\d{2}\-\d{4}$/);
	return regexp.test(s);  
}

/**
 * Original:  Simon Tneoh (tneohcb@pc.jaring.my) 
 */

var Cards = new makeArray(8);
Cards[0] = new CardType("MasterCard", "51,52,53,54,55", "16");
var MasterCard = Cards[0];
Cards[1] = new CardType("VisaCard", "4", "13,16");
var VisaCard = Cards[1];
Cards[2] = new CardType("AmExCard", "34,37", "15");
var AmExCard = Cards[2];
Cards[3] = new CardType("DinersClubCard", "30,36,38", "14");
var DinersClubCard = Cards[3];
Cards[4] = new CardType("DiscoverCard", "6011", "16");
var DiscoverCard = Cards[4];
Cards[5] = new CardType("enRouteCard", "2014,2149", "15");
var enRouteCard = Cards[5];
Cards[6] = new CardType("JCBCard", "3088,3096,3112,3158,3337,3528", "16");
var JCBCard = Cards[6];
var LuhnCheckSum = Cards[7] = new CardType();

/*************************************************************************\
START functions added by Chris to LIBRARY
\*************************************************************************/

// Main function used in the CALYX forms
function validateCreditCard(aCardNumber, aType, aExpireMonth, aExpireYear) {
    if( aType!=null ) {
        switch( parseInt(aType) ) {
            case 5020:
                aType = "MasterCard";
                break;
            case 5010:
                aType = "VisaCard";
                break;
            case 5040:
                aType = "AmExCard";
                break;
        //    case 5030:
        //        aType = "DiscoverCard";
        //        break;
        }
    } else {
        switch( aCardNumber.charAt(0) ) {
            case '3':
                aType = "AmExCard";
                break;
            case '4':
                aType = "VisaCard";
                break;
            case '5':
                aType = "MasterCard";
                break;
            case '6':
                aType = "DiscoverCard";
                return "Discover cards not accepted. Please use a different card";
                break;
        }
        if( aType==null ) {
            return "Cannot determine card type (check card number)";
        }
    }
    if( aExpireYear.length==2 ) {
        aExpireYear = "20"+aExpireYear;
    }
    today = new Date();
    currYear = today.getYear();
    if( currYear<2000 ) currYear = currYear + 1900;
    // Added by Chris, if month is > 13 and less than ZERO
    if( parseInt(aExpireMonth)>13 || parseInt(aExpireMonth)<0 ) { 
        return "This card expiration month is invalid (MMDD format)";
    }
    // Added by Chris, if EXPIRE YEAR is > 10 years from current year
    if( parseInt(aExpireYear) > (currYear+10) ) {
        return "This card expiration year is more than 10 years from now (MMDD format)";
    }
    if (!(new CardType()).isExpiryDate(aExpireYear, aExpireMonth)) {
        //alert("This card has already expired.");
        //return false;
        return "This card has already expired or is invalid (MMDD format)";
    }
    //card = form.CardType.options[form.CardType.selectedIndex].value;
    var isValidNumber = eval(aType + ".checkCardNumber(\"" + aCardNumber + "\", " + aExpireYear + ", " + aExpireMonth + ");");
    // If this is NOT a valid number, try to find out which one
    var cardname = "";
    if( !isValidNumber ) {
        for (var n = 0; n < Cards.size; n++) {
            //alert(Cards[n].getCardType() + " " + Cards[n].checkCardNumber(aCardNumber, aExpireMonth, aExpireYear));
            if (Cards[n].checkCardNumber(aCardNumber, aExpireMonth, aExpireYear)) {
                cardname = Cards[n].getCardType();
                break;
            }
        }
        if (cardname=="") {
            return "Invalid card number (check numbers again)";
        } else {
            return "This looks like a " + cardname + " number, not a " + aType + " number.";
        }
    }
    return "";
}

/*************************************************************************\
END functions added by Chris to LIBRARY
\*************************************************************************/

/*************************************************************************\
Object CardType([String cardtype, String rules, String len, int year, 
                                        int month])
cardtype    : type of card, eg: MasterCard, Visa, etc.
rules       : rules of the cardnumber, eg: "4", "6011", "34,37".
len         : valid length of cardnumber, eg: "16,19", "13,16".
year        : year of expiry date.
month       : month of expiry date.
eg:
var VisaCard = new CardType("Visa", "4", "16");
var AmExCard = new CardType("AmEx", "34,37", "15");
\*************************************************************************/
function CardType() {
    var n;
    var argv = CardType.arguments;
    var argc = CardType.arguments.length;
    
    this.objname = "object CardType";
    
    var tmpcardtype = (argc > 0) ? argv[0] : "CardObject";
    var tmprules = (argc > 1) ? argv[1] : "0,1,2,3,4,5,6,7,8,9";
    var tmplen = (argc > 2) ? argv[2] : "13,14,15,16,19";
    
    this.setCardNumber = setCardNumber;  // set CardNumber method.
    this.setCardType = setCardType;  // setCardType method.
    this.setLen = setLen;  // setLen method.
    this.setRules = setRules;  // setRules method.
    this.setExpiryDate = setExpiryDate;  // setExpiryDate method.
    
    this.setCardType(tmpcardtype);
    this.setLen(tmplen);
    this.setRules(tmprules);
    if (argc > 4)
    this.setExpiryDate(argv[3], argv[4]);
    
    this.checkCardNumber = checkCardNumber;  // checkCardNumber method.
    this.getExpiryDate = getExpiryDate;  // getExpiryDate method.
    this.getCardType = getCardType;  // getCardType method.
    this.isCardNumber = isCardNumber;  // isCardNumber method.
    this.isExpiryDate = isExpiryDate;  // isExpiryDate method.
    this.luhnCheck = luhnCheck;// luhnCheck method.
    return this;
}

/*************************************************************************\
boolean checkCardNumber([String cardnumber, int year, int month])
return true if cardnumber pass the luhncheck and the expiry date is
valid, else return false.
\*************************************************************************/

function checkCardNumber() {
    var argv = checkCardNumber.arguments;
    var argc = checkCardNumber.arguments.length;
    var cardnumber = (argc > 0) ? argv[0] : this.cardnumber;
    var year = (argc > 1) ? argv[1] : this.year;
    var month = (argc > 2) ? argv[2] : this.month;
    
    this.setCardNumber(cardnumber);
    this.setExpiryDate(year, month);
    
    if (!this.isCardNumber())
    return false;
    if (!this.isExpiryDate())
    return false;
    
    return true;
}

/*************************************************************************\
String getCardType()
return the cardtype.
\*************************************************************************/
function getCardType() {
    return this.cardtype;
}
/*************************************************************************\
String getExpiryDate()
return the expiry date.
\*************************************************************************/
function getExpiryDate() {
    return this.month + "/" + this.year;
}
/*************************************************************************\
boolean isCardNumber([String cardnumber])
return true if cardnumber pass the luhncheck and the rules, else return
false.
\*************************************************************************/
function isCardNumber() {
    var argv = isCardNumber.arguments;
    var argc = isCardNumber.arguments.length;
    var cardnumber = (argc > 0) ? argv[0] : this.cardnumber;
    if (!this.luhnCheck())
        return false;
    for (var n = 0; n < this.len.size; n++)
        if (cardnumber.toString().length == this.len[n]) {
            for (var m = 0; m < this.rules.size; m++) {
                var headdigit = cardnumber.substring(0, this.rules[m].toString().length);
                if (headdigit == this.rules[m])
                    return true;
            }
            return false;
        }
    return false;
}

/*************************************************************************\
boolean isExpiryDate([int year, int month])
return true if the date is a valid expiry date,
else return false.
\*************************************************************************/
function isExpiryDate() {
    var argv = isExpiryDate.arguments;
    var argc = isExpiryDate.arguments.length;
    
    year = argc > 0 ? argv[0] : this.year;
    month = argc > 1 ? argv[1] : this.month;
    
    if (!isNum(year+""))
        return false;
    if (!isNum(month+""))
        return false;
    today = new Date();
    expiry = new Date(year, month);
    
    if (today.getTime() > expiry.getTime()) {
        return false;
    } else {
        return true;
    }
}

/*************************************************************************\
boolean isNum(String argvalue)
return true if argvalue contains only numeric characters,
else return false.
\*************************************************************************/
function isNum(argvalue) {
    argvalue = argvalue.toString();
    
    if (argvalue.length == 0)
        return false;
    
    for (var n = 0; n < argvalue.length; n++)
        if (argvalue.substring(n, n+1) < "0" || argvalue.substring(n, n+1) > "9")
            return false;
    
    return true;
}

/*************************************************************************\
boolean luhnCheck([String CardNumber])
return true if CardNumber pass the luhn check else return false.
Reference: http://www.ling.nwu.edu/~sburke/pub/luhn_lib.pl
\*************************************************************************/
function luhnCheck() {
    var argv = luhnCheck.arguments;
    var argc = luhnCheck.arguments.length;
    
    var CardNumber = argc > 0 ? argv[0] : this.cardnumber;
    
    if (! isNum(CardNumber)) {
        return false;
    }
    
    var no_digit = CardNumber.length;
    var oddoeven = no_digit & 1;
    var sum = 0;
    
    for (var count = 0; count < no_digit; count++) {
        var digit = parseInt(CardNumber.charAt(count));
        if (!((count & 1) ^ oddoeven)) {
            digit *= 2;
            if (digit > 9)
                digit -= 9;
        }
        sum += digit;
    }
    if (sum % 10 == 0)
        return true;
    else
        return false;
}

/*************************************************************************\
ArrayObject makeArray(int size)
return the array object in the size specified.
\*************************************************************************/
function makeArray(size) {
    this.size = size;
    return this;
}

/*************************************************************************\
CardType setCardNumber(cardnumber)
return the CardType object.
\*************************************************************************/
function setCardNumber(cardnumber) {
    this.cardnumber = cardnumber;
    return this;
}

/*************************************************************************\
CardType setCardType(cardtype)
return the CardType object.
\*************************************************************************/
function setCardType(cardtype) {
    this.cardtype = cardtype;
    return this;
}

/*************************************************************************\
CardType setExpiryDate(year, month)
return the CardType object.
\*************************************************************************/
function setExpiryDate(year, month) {
    this.year = year;
    this.month = month;
    return this;
}

/*************************************************************************\
CardType setLen(len)
return the CardType object.
\*************************************************************************/
function setLen(len) {
    // Create the len array.
    if (len.length == 0 || len == null)
        len = "13,14,15,16,19";
    
    var tmplen = len;
    n = 1;
    while (tmplen.indexOf(",") != -1) {
        tmplen = tmplen.substring(tmplen.indexOf(",") + 1, tmplen.length);
        n++;
    }
    this.len = new makeArray(n);
    n = 0;
    while (len.indexOf(",") != -1) {
        var tmpstr = len.substring(0, len.indexOf(","));
        this.len[n] = tmpstr;
        len = len.substring(len.indexOf(",") + 1, len.length);
        n++;
    }
    this.len[n] = len;
    return this;
}

/*************************************************************************\
CardType setRules()
return the CardType object.
\*************************************************************************/
function setRules(rules) {
    // Create the rules array.
    if (rules.length == 0 || rules == null)
        rules = "0,1,2,3,4,5,6,7,8,9";
      
    var tmprules = rules;
    n = 1;
    while (tmprules.indexOf(",") != -1) {
        tmprules = tmprules.substring(tmprules.indexOf(",") + 1, tmprules.length);
        n++;
    }
    this.rules = new makeArray(n);
    n = 0;
    while (rules.indexOf(",") != -1) {
        var tmpstr = rules.substring(0, rules.indexOf(","));
        this.rules[n] = tmpstr;
        rules = rules.substring(rules.indexOf(",") + 1, rules.length);
        n++;
    }
    this.rules[n] = rules;
    return this;
}



