;(function($){
	
	// Validate rules, ordered by importance
	var validators = [
		["num-only", /^([0-9]+[-. ]?)+$/],
		
		// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
		["email", /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i]
	];
	
	$.fn.dsgValidation = function(params){
		
		// Default settings
		var settings = $.extend({
			errors: {
				"num-only": "Num only",
				"required": "Field required",
				"email": "Email not valid",
				"checked": "Must be checked"
			},
			customEvents: {},
			submitToHidden: false,
			showError: function($field, env, error){
				$field.addClass("error");
				
				if ($field.data("errorMsg")){
					
					if ($field.data("errorMsg").is(":hidden")) {
						errorEltsAdd($field[0],env);
					}
					
					$field.data("errorMsg").text(error).show();
				}
				else {
					$field.data("errorMsg", $('<label for="'+ $field[0].id +'" class="error">'+error+'</label>').insertAfter($field));
				}
			},
			hideError: function($field, env){
				
				if ($field.data("errorMsg") && $field.data("errorMsg").is(":visible")){
					$field.removeClass("error").data("errorMsg").hide();
				}
			}
		}, params);
		
		return this.each(function(){
			
			// Form ?
			if(this.tagName.toUpperCase() !== "FORM") return;
			
			var env = {
				displayedErrors: 0,
				errorsElts: [],
				$form: $(this),
				radioGroups: {}
			};
			
			// Already init ?
			if (env.$form.data("init")) return;
			
			// env in jQuery form object
			env.$form.data("env", env);
			env.$form.data("settings", settings);
			env.$form.data("init", true);
			
			$.each(settings.customEvents, function(sEvent, fHandler){
				env.$form.unbind("submit." + sEvent);
			});
			
			env.$form
			
			// Check all on submit
			.bind("submit.dsg_validation", function(e){
				e.preventDefault();
				
				var jThis = env.$form;
				
				jThis.find(":text, :password, textarea, select").each(function(){
					validate(this, env, settings);
				});
				
				validateAllRadioGroups(env, settings);
				
				if (env.errorsElts.length == 0) {
					
					if (settings.submitToHidden) {
						var jSubmit = jThis.find("[type=submit]:first");
						jSubmit.after('<input type="hidden" name="'+ jSubmit.attr('name') +'" value="'+ jSubmit.val() +'">');
						jSubmit.removeAttr("name").removeAttr("value");
					}
					
					$.each(settings.customEvents, function(sEvent, fHandler){
						env.$form.bind("submit." + sEvent, fHandler);
					});
					
					jThis.unbind("submit.dsg_validation").submit();
				}
			})
			
			// Validate field on keyup
			.find(":text, :password, textarea, select").filter(".check-num-only, .check-required, .check-email")
			.bind("keyup", function(e) {
				
				// keycode 9 == <TAB>
				if (e.keyCode != 9 && getValidTest(this)[0]) {
					
					$(this).unbind("keyup").bind("keyup", function() {
						if (e.keyCode != 9) {
							validate(this, env, settings);
						}
					});
					
				}
			})
			
			// Validate field on blur
			.blur(function(){
				validate(this, env, settings);
			}).end().end()
			
			
			// Validate field on change
			.change(function(){
				validate(this, env, settings);
			})
			
			// Validate radios
			.find(":radio")
			.bind("blur focus click", function() {
				
				var curRadio = this;
				
				$.each(env.radioGroups, function(i, $radioGroup) {
					
					$radioGroup.each(function() {
						if (this == curRadio) {
							validateRadioGroup(env, settings, $radioGroup);
							return false;
						}
					});
				});
				
			})
			
			// Create radio groups
			.each(function() {
				var $this = $(this);
				if ($this.hasClass("check-checked")) {
					env.radioGroups[$this.attr("name")] = (env.radioGroups[$this.attr("name")])? env.radioGroups[$this.attr("name")].add($this) : $this;
				}
			});
		});
	};
	
	$.fn.removeDsgValidation = function(){
		
		return this.each(function(){
			
			// Form ?
			if(this.tagName.toUpperCase() !== "FORM") return;
			
			var $form = $(this);
			
			$form.unbind().find(":text, :password, textarea, select").filter(".check-num-only, .check-required, .check-email").unbind("keyup");
			
			while($form.data("env").errorsElts.length > 0) {
				$form.data("settings").hideError($($form.data("env").errorsElts[0]), $form.data("env"));
				errorEltsRemove($form.data("env").errorsElts[0], $form.data("env"));
			};
			
			$form.data("init", false);
		});
	};
	
	// Attach validators on a field
	function setValidators(validators, $field) {
		
		var fieldValidators = [];
		
		$.each(validators, function(i, validator){
			if ( $field.hasClass("check-" + validator[0]) ) {
				fieldValidators.push(validator);
			}
		});
		
		$field.data("validators", fieldValidators);
	};
	
	// Is the field valid ?
	// Return an array with a boolean, and (eventually) an error type ("num-only", "required"...)
	function getValidTest(field) {
		
		var $field = $(field);
		var errorType = "";
		
		// Empty ?
		if ( $field.hasClass("check-required") && ($field.val() == "" || $field.val() == " ") ) {
			errorType = "required";
		}
		
		// Pass all validators ?
		else if ( $field.val() != "" ) {
			$.each(validators, function (i, validator) {
				if ( $field.hasClass("check-" + validator[0]) && !(validator[1].test($field.val())) ) {
					errorType = validator[0];
				}
			});
		}
		
		return [ ((errorType === "")? true : false), errorType ];
	};
	
	// Add an element to "errorsElts"
	function errorEltsAdd(elt, env) {
		if ($.inArray(elt, env.errorsElts) == -1) {
			env.errorsElts.push(elt);
		}
		
		env.$form.find("[type=submit]:first").addClass("disabled");
	};
	
	// Remove an element from "errorsElts"
	function errorEltsRemove(elt, env) {
		if ($.inArray(elt, env.errorsElts) != -1) {
			env.errorsElts.splice($.inArray(elt, env.errorsElts), 1);
		}
		
		if (env.errorsElts.length < 1) {
			env.$form.find("[type=submit]:first").removeClass("disabled");
		}
	};
	
	// Validate field
	function validate(field, env, settings) {
		
		var $field = $(field);
		
		var validTest = getValidTest($field);
		
		// Hide error
		if (validTest[0]){
			settings.hideError($field, env);
			errorEltsRemove($field[0], env);
		}
		
		// Show error
		else {
			settings.showError($field, env, settings["errors"][validTest[1]]);
			errorEltsAdd($field[0], env);
		}
	};
	
	// Validate radio
	function validateRadioGroup(env, settings, $radioGroup) {
		
		if ($radioGroup.is(":checked")) {
			settings.hideError($radioGroup.parent(), env);
			errorEltsRemove($radioGroup[0], env);
		} else {
			settings.showError($radioGroup.parent(), env, settings["errors"]["checked"]);
			errorEltsAdd($radioGroup[0], env);
		}
	};
	
	// Validate all radio groups
	function validateAllRadioGroups(env, settings) {
		$.each(env.radioGroups, function(i, $radioGroup) {
			validateRadioGroup(env, settings, $radioGroup);
		});
	};

})(jQuery);
