(function() {

  function stripHtml(value) {
    // remove html tags and space chars
    return value.replace(/<.[^<>]*?>/g, ' ').replace(/&nbsp;|&#160;/gi, ' ')
    // remove numbers and punctuation
    .replace(/[0-9.(),;:!?%#$'"_+=\/-]*/g,'');
  }
  jQuery.validator.addMethod("maxWords", function(value, element, params) {
      return this.optional(element) || (stripHtml(value).match(/\b\w+\b/g).length < params);
  }, jQuery.validator.format("Please enter {0} words or less."));

  jQuery.validator.addMethod("minWords", function(value, element, params) {
      return this.optional(element) || (stripHtml(value).match(/\b\w+\b/g).length >= params);
  }, jQuery.validator.format("Please enter at least {0} words."));

  jQuery.validator.addMethod("rangeWords", function(value, element, params) {
      return this.optional(element) || ((stripHtml(value).match(/\b\w+\b/g).length >= params[0]) && (value.match(/bw+b/g).length < params[1]));
  }, jQuery.validator.format("Please enter between {0} and {1} words."));

})();

jQuery.validator.addMethod("letterswithbasicpunc", function(value, element) {
  return this.optional(element) || /^[a-z-.,()'\"\s]+$/i.test(value);
}, "Please enter only letters or punctuation.");

jQuery.validator.addMethod("alnum", function(value, element) {
  return this.optional(element) || /^[a-z0-9]+$/i.test(value);
}, "Please enter only letters or numbers.");

jQuery.validator.addMethod("alphanumeric", function(value, element) {
  return this.optional(element) || /^\w+$/i.test(value);
}, "Please enter only letters, numbers, spaces or underscores.");

jQuery.validator.addMethod("letters", function(value, element) {
  return this.optional(element) || /^[a-z]+$/i.test(value);
}, "Please enter only letters.");

jQuery.validator.addMethod("nowhitespace", function(value, element) {
  return this.optional(element) || /^\S+$/i.test(value);
}, "No white space please");

jQuery.validator.addMethod("ziprange", function(value, element) {
  return this.optional(element) || /^90[2-5]\d\{2}-\d{4}$/.test(value);
}, "Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx");

jQuery.validator.addMethod("integer", function(value, element) {
  return this.optional(element) || /^-?\d+$/.test(value);
}, "Please enter a valid integer.");

/**
* Return true, if the value is a valid vehicle identification number (VIN).
*
* Works with all kind of text inputs.
*
* @example <input type="text" size="20" name="VehicleID" class="{required:true,vinUS:true}" />
* @desc Declares a required input element whose value must be a valid vehicle identification number.
*
* @name jQuery.validator.methods.vinUS
* @type Boolean
* @cat Plugins/Validate/Methods
*/
jQuery.validator.addMethod(
  "vinUS",
  function(v){
    if (v.length != 17) {
      return false;
    }
    var i, n, d, f, cd, cdv;
    var LL    = ["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"];
    var VL    = [1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9];
    var FL    = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];
    var rs    = 0;
    for(i = 0; i < 17; i++){
        f = FL[i];
        d = v.slice(i,i+1);
        if(i == 8){
            cdv = d;
        }
        if(!isNaN(d)){
            d *= f;
        }
        else{
            for(n = 0; n < LL.length; n++){
                if(d.toUpperCase() === LL[n]){
                    d = VL[n];
                    d *= f;
                    if(isNaN(cdv) && (n == 8)){
                        cdv = LL[n];
                    }
                    break;
                }
            }
        }
        rs += d;
    }
    cd = rs % 11;
    if(cd == 10){cd = "X";}
    if(cd == cdv){return true;}
    return false;
  },
  "The specified vehicle identification number (VIN) is invalid."
);

jQuery.validator.addMethod("time", function(value, element) {
    return this.optional(element) || /^([01][0-9])|(2[0123]):([0-5])([0-9])$/.test(value);
  }, "Please enter a valid time, between 00:00 and 23:59"
);

/**
 * matches US phone number format
 *
 * where the area code may not start with 1 and the prefix may not start with 1
 * allows '-' or ' ' as a separator and allows parens around area code
 * some people may want to put a '1' in front of their number
 *
 * 1(212)-999-2345
 * or
 * 212 999 2344
 * or
 * 212-999-0983
 *
 * but not
 * 111-123-5434
 * and not
 * 212 123 4567
 */
jQuery.validator.addMethod("phoneUS", function(phone_number, element) {
    phone_number = phone_number.replace(/\s+/g, "");
  return this.optional(element) || ((phone_number.length > 9) &&
    phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/));
}, "Please specify a valid phone number");

jQuery.validator.addMethod('phoneUK', function(phone_number, element) {
  return this.optional(element) || ((phone_number.length > 9) &&
  phone_number.match(/^(\(?(0|\+44)[1-9]{1}\d{1,4}?\)?\s?\d{3,4}\s?\d{3,4})$/));
}, 'Please specify a valid phone number');

jQuery.validator.addMethod('mobileUK', function(phone_number, element) {
  return this.optional(element) || ((phone_number.length > 9) &&
  phone_number.match(/^((0|\+44)7(5|6|7|8|9){1}\d{2}\s?\d{6})$/));
}, 'Please specify a valid mobile number');

// NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator
// Redistributed under the the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0
// Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings)
jQuery.validator.addMethod("creditcardtypes", function(value, element, param) {

  if (/[^0-9-]+/.test(value)) {
    return false;
  }

  value = value.replace(/\D/g, "");

  var validTypes = 0x0000;

  if (param.mastercard){validTypes |= 0x0001;}
  if (param.visa){validTypes |= 0x0002;}
  if (param.amex){validTypes |= 0x0004;}
  if (param.dinersclub){validTypes |= 0x0008;}
  if (param.enroute){validTypes |= 0x0010;}
  if (param.discover){validTypes |= 0x0020;}
  if (param.jcb){validTypes |= 0x0040;}
  if (param.unknown){validTypes |= 0x0080;}
  if (param.all){validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080;}

  if (validTypes & 0x0001 && /^(51|52|53|54|55)/.test(value)) { //mastercard
    return value.length == 16;
  }
  if (validTypes & 0x0002 && /^(4)/.test(value)) { //visa
    return value.length == 16;
  }
  if (validTypes & 0x0004 && /^(34|37)/.test(value)) { //amex
    return value.length == 15;
  }
  if (validTypes & 0x0008 && /^(300|301|302|303|304|305|36|38)/.test(value)) { //dinersclub
    return value.length == 14;
  }
  if (validTypes & 0x0010 && /^(2014|2149)/.test(value)) { //enroute
    return value.length == 15;
  }
  if (validTypes & 0x0020 && /^(6011)/.test(value)) { //discover
    return value.length == 16;
  }
  if (validTypes & 0x0040 && /^(3)/.test(value)) { //jcb
    return value.length == 16;
  }
  if (validTypes & 0x0040 && /^(2131|1800)/.test(value)) { //jcb
    return value.length == 15;
  }
  if (validTypes & 0x0080) { //unknown
    return true;
  }
  return false;
}, "Please enter a valid credit card number.");

/**
 * Parse rules set up with a "regex" attribute. The pattern can be specified
 * with or without // delimiters. If they are used, modifiers can be specified
 * after the closing / as normal.
 */
jQuery.validator.addMethod('regex', function(value, element, param) {
  var pattern, modifiers, re, last;

  // If the first char is not a /, the whole string is the pattern.
  if (param.charAt(0) !== '/') {
    pattern = param[0];
  } else {
    // Strip first /.
    param = substr(param, 1);

    // Get location of last /.
    last = strrpos(param, '/');

    // If the / is the last char, there are no modifiers to parse out.
    if (last === param.length - 1) {
      pattern = substr(param, 0, param.length - 1);
    } else {
      // Pattern is everything before the last /.
      pattern = substr(param, 0, last);

      // Modifers are everything after the last /.
      modifiers = substr(param, last + 1);
    }
  }

  re = new RegExp(pattern, modifiers);

  return this.optional(element) || re.test(value);
}, jQuery.validator.messages.remote);

