/*
 * 
 * FormValidator - Client-side form validation
 * Version 0.0.1
 * @requires $ v1.2.3+
 * 
 * Copyright (c) 2009 Perry Lucas
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * 
 */
/**
 *
 * @description Enable form validation using multiple validators
 * 
 * @example $('form').formvalidator();
 * @desc Enable form validation using multiple validators.
 *
 * @type $
 *
 * @name formvalidator
 * 
 * @cat Plugins/Formvalidation
 * 
 * @author Perry Lucas
 */

(function($) {
	$.extend({
		formvalidator: new function() {
			var validators = [];
			this.defaults = {
				validateOnBlur:true,
				validateOnSubmit:true,
				validateHidden:true,
				validateDisabled:false,
				enableCssBinding:true,
				onBlurMessageDisplay:'inline',  	//[alert|inline|none|summary]
				onSubmitMessageDisplay:'summary',  	//[alert|inline|none|summary]
				inlineMessageHideDelay:3000,
				cssValidationFailed:'validation-failed',
				cssValidationPassed:'validation-passed',
				cssAlpha:'alpha',
				cssAlphaNumeric:'alpha_numeric',
				cssBetween:'between',
				cssCreditCard:'credit_card',
				cssDate:'date',
				cssEmail:'email',
				cssEmpty:'empty',
				cssEquals:'equals',
				cssFloat:'float',
				cssInteger:'integer',
				cssIpAddress:'ip_address',
				cssIsoDate:'iso_date',
				cssLengthBetween:'length_between',
				cssLengthEquals:'length_equals',
				cssMatch:'match',
				cssMax:'max',
				cssMaxLength:'max_length',
				cssMin:'min',
				cssMinLength:'min_length',
				cssNumber:'number',
				cssPhone:'phone',
				cssRequired:'required',
				cssSsn:'ssn',
				cssUrl:'url',
				cssValidEntries:'valid_entries',
				cssZipCode:'zip_code',
				debug:false
			};
			
			// for debuging  //
			function log(message) {
				var now = new Date();
				var h = now.getHours();
				var m = now.getMinutes();
				var s = now.getSeconds();
				m = (m < 10) ? '0' + m : m;
				s = (s < 10) ? '0' + s : s;
				message = '[' + h + ':' + m + ':' + s + '] ' + message;			
				if (typeof console != "undefined" && typeof console.debug != "undefined") {
					console.log(message);
				} else {
					alert(message);
				}
			};

			function getFieldValidators(field){				
				var form = field.parents('form:first').data('formvalidator');
				var isDebug = form.config.debug;
				var fieldValidators = field.data('validators') || [];
				if(isDebug){ 
					$(fieldValidators).each(function(j, fieldValidator){
						log(' - validator retreived from cache: ' + fieldValidator.id);
					});
				}
				var cssBound = field.data('validators_cssBound') == true;
				if(form.config.enableCssBinding && !cssBound){
					 var cssBoundValidators = detectValidators(field);
					$(cssBoundValidators).each(function(i, cssBoundValidator){
						fieldValidators.push(cssBoundValidator);
					});
					field.data('validators', fieldValidators);
					field.data('validators_cssBound', true);
				}
				return fieldValidators;
			};			
			
			
			
			//parseValidator
			function parseFieldValidatorsFromCss(field){
				field = $(field);
				var form = field.parents('form:first').data('formvalidator');
				var isDebug = form.config.debug;				
				var s = field.attr('class');
				var fieldValidators = [];
				var className = '';
				var param = '';
				var params = [];
				var arr = [];
				var len = s.length;
				var stringChar;
				var nowParsing = 'class';
				var tryAddValidator = function(className, params){
					if(params && $(params).size() == 1){
						params = params[0];
					}
					var fieldValidator = $.formvalidator.getValidator(className, params);
					if(fieldValidator){
						if(isDebug){ 
							log('   - detected "' + fieldValidator.id + '"'); 
							if($.isArray(params)){
								$(params).each(function(p, param){
									log('     - initialized with param: "' + param + '"'); 
								});
							}else{
								log('     - initialized with param: "' + params + '"'); 
							}
						}
						fieldValidators.push(fieldValidator);
					}				
				}
				for(var i=0;i<len;i++){
					var c = s.charAt(i);
					switch(nowParsing){
						case 'class':
							switch(c){
								case ' ':
									if(className.length == 0) break;
									tryAddValidator(className, []);
									className = '';
									break;
								case '(':
									param = '';
									params = [];
									nowParsing = 'param';
									break;
								default:
									className += c;
									break;
							}
							break;
						case 'param':
							switch(c){
								case ' ':
									break;
								case '\'':
								case '"':
									stringChar = c;
									nowParsing = 'string';
									break;
								case '[':
									nowParsing = 'array';
									param = '';
									arr = [];
									break;
								case ',':
									if(param.length > 0 || $.isArray(param)){
										params.push(param);
									}
									param = '';
									break;
								case ')':
									if(param.length > 0 || $.isArray(param)){
										params.push(param);
									}
									param = '';
									tryAddValidator(className, params);
									className = '';
									nowParsing = 'class';
									break;
								default:
									param += c;
									break;
							}							
							break;
						case 'string':
							switch(c){
								case '\\':
									if(i < len -1){
										param += s.charAt(++i); 
									}
									break;
								case '\'':
								case '"':
									if(stringChar == c){
										nowParsing = 'param';
									}
									break;
								default:
									param += c;
									break;
							}							
							break;							
						case 'array':
							switch(c){
								case ' ':
									break;
								case '[':
									//TODO: add support for multi-dimensional arrays
									/*nowParsing = 'array';
									param = '';
									arr = [];*/
									break;
								case ',':
									if(param.length > 0){
										arr.push(param);
									}
									param = '';
									break;
								case ']':
									if(param.length > 0){
										arr.push(param);
										param = arr;
									}
									nowParsing = 'param';
									break;
								default:
									param += c;
									break;
							}							
							break;							
					}
				}
				if(className.length != 0){
					tryAddValidator(className, params);
				}				
				return fieldValidators;
			};
			
			function detectValidators(field){
				field = $(field);
				var isDebug = field.parents('form:first').data('formvalidator').config.debug;
				if(isDebug){ log(' - binding css validators...'); }
				var fieldValidators = parseFieldValidatorsFromCss(field);
				if(isDebug){ if(fieldValidators.length == 0){ log('   - no validators detected');}}
				return fieldValidators;
			};
			
			function pass(field, passedValidators){
				var form = field.parents('form:first').data('formvalidator');
				field.removeClass(form.config.cssValidationFailed);					
				field.addClass(form.config.cssValidationPassed);
			};
			
			function fail(field, failedValidators){
				var form = field.parents('form:first').data('formvalidator');
				field.removeClass(form.config.cssValidationPassed);
				field.addClass(form.config.cssValidationFailed);
				$(failedValidators).each(function(i, fieldValidator){
					form.failedTests.push(
						{
							message:	fieldValidator.message(field, fieldValidator.messageParams),
							field:	 	field
						}
					);
				});			
			};
			
			function onValidationEnd(form, event){
				var c = form.config;
				var isDebug = c.debug;
				if(isDebug){ log('onValidationEnd called on form "' + form.name + '"'); }				
				var failedTests = $(form.failedTests);
				if(failedTests.size() == 0){
					if(isDebug){ log('Validation Passed!'); }
				}else{
					if(isDebug){ log('Validation Failed with the following messages:');  failedTests.each(function(i, failedTest){ log(' - ' + failedTest.message); }); }
					var messageDisplay = (event === 'blur') ? c.onBlurMessageDisplay : c.onSubmitMessageDisplay;
					switch(messageDisplay){
						case 'alert':
							var messages = 'The following problems were found:\n\n';
							failedTests.each(function(i, failedTest){
								messages += failedTest.message + '\n';
							});
							alert(messages);
							break;
						case 'inline':
							failedTests.each(function(i, failedTest){
								var position = $(failedTest.field).position();
								var width = $(failedTest.field).width();
								var inlineMessage = $('<div></div>)')
									.text(failedTest.message)
									.addClass('inline-validation-message')
									.css({	
											'position':	'relative',
											'float':	'left',
											'left':		'5px'
										});
								$(failedTest.field).after(inlineMessage);
								setTimeout(function(){inlineMessage.slideUp(500);}, c.inlineMessageHideDelay);
							});
							break;
						case 'summary':	
							
							$(form).find('.validation-summary').remove();
							var validationSummary = $('<div class="validation-summary"><div class="validation-summary-header"><h2 class="validation-summary-title">' +
														'The following problems were found:' + 
														'</h2></div></div>)');
							var messages = $('<ul class="validation-messages"></ul>');
							failedTests.each(function(i, failedTest){
									messages.append('<li class="validation-message">' + failedTest.message + '</li>');
							});
							validationSummary.append(messages);
							$(form).prepend(validationSummary);
							
							var win = $(window);
							var p = validationSummary.position();
							win.scrollTop(p.top);
							win.scrollLeft(p.left);
							break;
						case 'none':
						default:
							break;
					}
					form.failedTests = [];
				}				
			};
			
			function bindValidators(form){
				var c = form.config;
				if(c.debug){
					log('Binding validators for form: '+ form.name);
				}
				var fields = $(form).find(':input').not(':button,:submit');
				if(!c.validateHidden){
					fields = $(fields).filter(':visible');
				}
				if(!c.validateDisabled){
					fields = $(fields).filter(':enabled');
				}
				if(c.validateOnBlur){
					$(fields).each(function(i, field){
						$(field).blur(function(){
							$(this).trigger('validationStart');
							$.formvalidator.validateField(this);							
							$(this).trigger('validationEnd');
							onValidationEnd(form, 'blur');
						});
					});
				}
				if(c.validateOnSubmit){
					$(form).submit(function(event){
						$(this).trigger('validationStart');
						var isValid = true;
						try{
							$(fields).each(function(i, field){
								isValid = $.formvalidator.validateField(field) && isValid;
							});
							
							$(this).trigger('validationEnd', [isValid]);
							onValidationEnd(form, 'submit');
							if(!isValid){
								event.preventDefault();
							}
							return isValid;
						}catch(e){alert(e);}
					});
				}
			};
			
			// public methods //
			this.construct = function(settings) {
				return this.each(function() {
					var form, config;
					form = $(this);
					this.config = {};
					this.cssBindingPerformed = false;
					this.failedTests = [];
					config = $.extend(this.config, $.formvalidator.defaults, settings);
					if(this.config.debug){
						log('******************************************************');
						log('*      $.formvalidator Debug Mode Enabled            *');
						log('******************************************************');
					}
					form.data('formvalidator', this);
					bindValidators(this);
				});
			};		
			
			this.validateField = function(field){
				var field = $(field);
				var form = field.closest('form').data('formvalidator');
				if(!form){
					return true;
				}
				var isDebug = form.config.debug;
				if(isDebug){ log('Validating field: ' + field.attr('name')); }
				var fieldValidators = getFieldValidators(field);
				var isValid = true;
				var passedValidators = [];
				var failedValidators = [];
				$(fieldValidators).each(function(j, fieldValidator){

					var thisTestIsValid = fieldValidator.test(field, fieldValidator.params);	 
					if(thisTestIsValid){
						if(isDebug){ log(' - validator "' + fieldValidator.id + '" passed.'); }
						passedValidators.push(fieldValidator);
					}else{
						if(isDebug){ log(' - validator "' + fieldValidator.id + '" failed.'); }
						failedValidators.push(fieldValidator);
					}
					isValid = isValid && thisTestIsValid;
				});
				fieldValidators='';
				if(isValid){
					if(isDebug){ log(' - triggering "validationPassed" event.'); }
					$(field).trigger("validationPassed", [field, passedValidators]);
					pass(field, passedValidators);
				}else{
					if(isDebug){ log(' - triggering "validationFailed" event.'); }
					$(field).trigger("validationFailed", [field, failedValidators]);
					fail(field, failedValidators);
				}	
				return isValid;
			};			
			
			this.addValidator = function(validator) {
				validators[validator.id] = validator;
			
				/*var len = validators.length, a = true;
				for(var i=0; i < len; i++) {
					if(validators[i].id.toLowerCase() == validator.id.toLowerCase()) {
						a = false;
						break;
					}
				}
				if(a) { validators.push(validator); }*/
			};
			
			this.getValidator = function(id, params){
				var validator = validators[id];
				/*var len = validators.length;
				for(var i=0; i < len; i++) {
					if(validators[i].id.toLowerCase() == id.toLowerCase()) {	
						validator = validators[i];
						break;
					}
				};*/
				if(!validator) return null;
				if(!params){
					params = [];
				}
				return $.extend(true, {params:params}, validator); //deep clone
			};

			this.getValidators = function(){
				return validators;
			};
			
			this.getLabel = function(field){
				field = $(field);
				var label = $('label[for="' + field.attr('name') + '"]:first');
				if(label.size() == 0){
					return field.attr('name');
				}
				var labelText = $.trim(label.text().replace(/[\:|\*]/g, ''));
				return labelText;
			};
		}
	});
	
	// extend plugin scope
	$.fn.extend({
        formvalidator: $.formvalidator.construct
	});
	
	//utility
	function isString() {
		if (typeof arguments[0] == 'string') return true;
		if (typeof arguments[0] == 'object') {  
			var criterion = arguments[0].constructor.toString().match(/string/i); 
			return (criterion != null);  
		}
		return false;
	}
	
	// add default validators
	
	$.formvalidator.addValidator({
		id: "alpha",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^[a-zA-Z]+$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must only contain letters.';
		}
	});		

	$.formvalidator.addValidator({
		id: "alpha_numeric",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^[a-zA-Z\d]+$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must only contain letters and numbers.';
		}
	});
	
	$.formvalidator.addValidator({
		id: "between",
		test: function(field, params) {
			field = $(field);
			field.data('validators_min_value', parseFloat(params[0]));
			field.data('validators_max_value', parseFloat(params[1]));
			return $.formvalidator.getValidator('min').test(field, params[0]) && $.formvalidator.getValidator('max').test(field, params[1]) ;
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be between ' + field.data('validators_min_value') + ' and ' + field.data('validators_max_value') + '.';
		}
	});

	$.formvalidator.addValidator({
		id: "credit_card",
		test: function(field, type) {
			field = $(field);
			var ccType = type.toLowerCase();
			var isCC = function(type, ccnum){
				if (type == "visa") {
				  // Visa: length 16, prefix 4, dashes optional.
				  var re = /^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/;
				} else if (type == "mastercard") {
				  // Mastercard: length 16, prefix 51-55, dashes optional.
				  var re = /^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/;
				} else if (type == "discover") {
				  // Discover: length 16, prefix 6011, dashes optional.
				  var re = /^6011-?\d{4}-?\d{4}-?\d{4}$/;
				} else if (type == "amex") {
				  // American Express: length 15, prefix 34 or 37.
				  var re = /^3[4,7]\d{13}$/;
				} else if (type == "diners") {
				  // Diners: length 14, prefix 30, 36, or 38.
				  var re = /^3[0,6,8]\d{12}$/;
				}
				if (!re.test(ccnum)) return false;
				// Remove all dashes for the checksum checks to eliminate negative numbers
				ccnum = ccnum.split("-").join("");
				// Checksum ("Mod 10")
				// Add even digits in even length strings or odd digits in odd length strings.
				var checksum = 0;
				for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) {
				  checksum += parseInt(ccnum.charAt(i-1));
				}
				// Analyze odd digits in even length strings or even digits in odd length strings.
				for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) {
				  var digit = parseInt(ccnum.charAt(i-1)) * 2;
				  if (digit < 10) { checksum += digit; } else { checksum += (digit-9); }
				}
				if ((checksum % 10) == 0) return true; else return false;			
			};
			
			return $.formvalidator.getValidator('empty').test(field) || isCC(ccType, field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid credit card number.';
		}
	});
	
	$.formvalidator.addValidator({
		id: "date",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^(((0[13578]|1[02])[- \/\.](0[1-9]|[12]\d|3[01])[- \/\.]((1[6-9]|[2-9]\d)\d{2}))|((0[13456789]|1[012])[- \/\.](0[1-9]|[12]\d|30)[- \/\.]((1[6-9]|[2-9]\d)\d{2}))|(02[- \/\.](0[1-9]|1\d|2[0-8])[- \/\.]((1[6-9]|[2-9]\d)\d{2}))|(02[- \/\.]29[- \/\.]((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid date in the following format: "mm-dd-yyyy".';
		}
	});
	
	$.formvalidator.addValidator({
		id: "email",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid email address, like "username@domain.com".';
		}
	});	

	$.formvalidator.addValidator({
		id: "empty",
		test: function(field) {
			if(field == null) return true;
			field = $(field).get(0);
			switch(field.type){
				case 'text':
				case 'password':
				case 'select-one':
				case 'textarea':
				case 'hidden':
					return $(field).val() == null || $(field).val().length == 0;
				case 'checkbox':
					return $(':checkbox[name="' + field.name + '"]:checked').size() == 0;
				case 'radio':
					return $(':radio[name="' + field.name + '"]:checked').size() == 0;
				case 'select-many':
					return $(field).find('option:selected').size() == 0;
				default:
					alert(field.type);
					break;
			}
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be empty.';
		}
	});
	
	$.formvalidator.addValidator({
		id: "equals",
		test: function(field, equals) {
			field = $(field);
			field.data('validators_equals', equals);
			return $.formvalidator.getValidator('empty').test(field) || field.val() == equals;
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be "' + field.data('validators_equals') + '".';
		}
	});
	
	$.formvalidator.addValidator({
		id: "float",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || ( $.formvalidator.getValidator('number').test(field) && !isNaN(parseFloat(field.val())));
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a number.';
		}
	});	
	
	$.formvalidator.addValidator({
		id: "integer",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /(^-?\d\d*$)/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be an integer.';
		}
	});
	
	$.formvalidator.addValidator({
		id: "ip_address",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^\d{2,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid ip address, like "127.0.0.1".';
		}
	});	

	$.formvalidator.addValidator({
		id: "iso_date",
		test: function(field) {
			field = $(field);		
			return $.formvalidator.getValidator('empty').test(field) || /^((((19|20)(([02468][048])|([13579][26]))-02-29))|((20[0-9][0-9])|(19[0-9][0-9]))-((((0[1-9])|(1[0-2]))-((0[1-9])|(1\d)|(2[0-8])))|((((0[13578])|(1[02]))-31)|(((0[1,3-9])|(1[0-2]))-(29|30)))))$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid date in the following format: "yyyy-mm-dd".';
		}
	});	

	$.formvalidator.addValidator({
		id: "length_between",
		test: function(field, params) {
			field = $(field);
			minLength = parseInt(params[0]);
			maxLength = parseInt(params[1]);
			field.data('validators_min_length', minLength);
			field.data('validators_max_length', maxLength);
			return $.formvalidator.getValidator('min_length').test(field, minLength) && $.formvalidator.getValidator('max_length').test(field, maxLength);
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be between ' + field.data('validators_min_length') + ' and ' + field.data('validators_max_length') + ' characters long.';
		}
	});

	$.formvalidator.addValidator({
		id: "length_equals",
		test: function(field, lengthEquals) {
			field = $(field);
			field.data('validators_length_equals', lengthEquals);
			return $.formvalidator.getValidator('empty').test(field) || field.val().length == lengthEquals;
		},
		message: function(field) {
			var lengthEquals = field.data('validators_length_equals');
			if(lengthEquals == 1){
				return '"' + $.formvalidator.getLabel(field) + '" must be one character long.';
			}else{
				return '"' + $.formvalidator.getLabel(field) + '" must be ' + lengthEquals + ' characters long.';
			}
		}
	});
	
	$.formvalidator.addValidator({
		id: "match",
		test: function(field, match) {
			field = $(field);
			if(isString(match)){ //support for cssBinding
				match = $(match);
			}
			if(match){
				match.unbind('change');
				match.change(function(){
					$.formvalidator.validateField(field);
				});
			}
			field.data('validators_match', match);
			return field.val() === $(match).val();
		},
		message: function(field) {
			var match = field.data('validators_match');
			return '"' + $.formvalidator.getLabel(field) + '" must match "' + $.formvalidator.getLabel(match) + '".';
		}
	});
	
	$.formvalidator.addValidator({
		id: "max",
		test: function(field, maxValue) {
			field = $(field);
			maxValue = parseFloat(maxValue);
			field.data('validators_max_value', maxValue);
			return $.formvalidator.getValidator('empty').test(field) || parseFloat(field.val()) <= maxValue ;
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" cannot be more than ' + field.data('validators_max_value') + '.';
		}
	});	
	
	$.formvalidator.addValidator({
		id: "max_length",
		test: function(field, maxLength) {
			field = $(field);
			maxLength = parseInt(maxLength);
			field.data('validators_max_length', maxLength);
			return $.formvalidator.getValidator('empty').test(field) || field.val().length <= maxLength ;
		},
		message: function(field) {
			var maxLength = field.data('validators_max_length');
			if(maxLength == 1){
				return '"' + $.formvalidator.getLabel(field) + '" cannot be more than one character long.';
			}else{
				return '"' + $.formvalidator.getLabel(field) + '" cannot be more than ' + maxLength + ' characters long.';
			}
		}
	});
	
	$.formvalidator.addValidator({
		id: "min",
		test: function(field, minValue) {
			field = $(field);
			minValue = parseFloat(minValue);
			field.data('validators_min_value', minValue);
			return $.formvalidator.getValidator('empty').test(field) || parseFloat(field.val()) >= minValue ;
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be at least ' + field.data('validators_min_value') + '.';
		}
	});	
	
	$.formvalidator.addValidator({
		id: "min_length",
		test: function(field, minLength) {
			field = $(field);
			minLength = parseInt(minLength);
			field.data('validators_min_length', minLength);
			return $.formvalidator.getValidator('empty').test(field) || field.val().length >= minLength ;
		},
		message: function(field) {
			var minLength = field.data('validators_min_length');
			if(minLength == 1){
				return '"' + $.formvalidator.getLabel(field) + '" must be at least one character long.';
			}else{		
				return '"' + $.formvalidator.getLabel(field) + '" must be at least ' + minLength + ' characters long.';
			}
		}
	});
	
	$.formvalidator.addValidator({
		id: "number",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || (!isNaN(field.val()) && !/^\s+$/.test(field.val()));
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a number.';
		}
	});

	$.formvalidator.addValidator({
		id: "phone",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^(1\s*[-\/\.]?)?(\((\d{3})\)|(\d{3}))\s*[-\/\.]?\s*(\d{3})\s*[-\/\.]?\s*(\d{4})\s*(([xX]|[eE][xX][tT])\.?\s*(\d+))*$/.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid telephone number.';
		}
	});	
	
	$.formvalidator.addValidator({
		id: "required",
		test: function(field) {
			return !$.formvalidator.getValidator('empty').test(field);
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" is required.';
		}
	});

	$.formvalidator.addValidator({
		id: "ssn",
		test: function(field) {
			field = $(field);
			var isSSN = function(value){
				var re = /^([0-6]\d{2}|7[0-6]\d|77[0-2])([ \-]?)(\d{2})\2(\d{4})$/;
				if (!re.test(value)) { return false; }
				var temp = value;
				if (value.indexOf("-") != -1) { temp = (value.split("-")).join(""); }
				if (value.indexOf(" ") != -1) { temp = (value.split(" ")).join(""); }
				if (temp.substring(0, 3) == "000") { return false; }
				if (temp.substring(3, 5) == "00") { return false; }
				if (temp.substring(5, 9) == "0000") { return false; }
				return true; 			
			};
			return $.formvalidator.getValidator('empty').test(field) || (isSSN(field.val()));
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid social security number of the format: ###-##-####.';
		}
	});
	
	$.formvalidator.addValidator({
		id: "url",
		test: function(field) {
			field = $(field);
			return $.formvalidator.getValidator('empty').test(field) || /^(http|https|ftp|file):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid URL, like "http://www.domain.com"';
		}
	});	

	$.formvalidator.addValidator({
		id: "valid_entries",
		test: function(field, validEntries) {
			field = $(field);
			field.data('validators_valid_entries', validEntries);
			return $.formvalidator.getValidator('empty').test(field) || $.inArray(field.val(), validEntries) != -1;
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be one of the following: ' + field.data('validators_valid_entries') + '.';
		}
	});

	$.formvalidator.addValidator({
		id: "zip_code",
		test: function(field, params) {
			field = $(field);
			var type = 'usa';
			if(params && isString(params)){
				type = params.toLowerCase();
			}
			var isZip;
			switch (type){
				case 'canada':
					isZip = function(value){ return /^[abceghjklmnprstvxyABCEGHJKLMNPRSTVXY][0-9][abceghjklmnprstvwxyzABCEGHJKLMNPRSTVWXYZ][ ]?[0-9][abceghjklmnprstvwxyzABCEGHJKLMNPRSTVWXYZ][0-9]$/.test(value);};
					break;
				case 'usa':
				default:
					isZip = function(value){ return /^(?!0{5})(\d{5})(?!-?0{4})(-?\d{4})?$/.test(value);};
			}
			
			return $.formvalidator.getValidator('empty').test(field) || isZip(field.val());
		},
		message: function(field) {
			return '"' + $.formvalidator.getLabel(field) + '" must be a valid zip code.';
		}
	});
})(jQuery);

