(function($){
	function defaultValidSubmit() {
		this.submit();
	}
	
	var settings = {
		messageSelector: "label.form_msg",
		events: "change focus keyup",
		validClass: "valid",
		invalidClass: "invalid",
		messages: {},
		rules: {
			wrapper: function(rule){
				return function(validator) {
					if($(validator.field).is(rule.selector))
						rule.check(validator);
					else
						validator.next();
				}
			},
			simple: {
				wrapper: function(rule) {
					return {
						check: function(validator) {
							if(rule.check(validator.field))
								validator.valid(settings.messages[rule.success]);
							else	
								validator.invalid(settings.messages[rule.error]);
						},
						selector: rule.selector
					};
				},
				dependancy: {
					wrapper: function(rule) {
						return {
							check: function(obj) {
								var mate = rule.mate(obj);
								$.each(mate.get(),function(){
									this.validator.addDependancy(obj);
								});
								return rule.check(obj,mate);
							},
							success: rule.success,
							error: rule.error,
							selector: rule.selector
						};
					},
					repeat: {
						check: function(obj,mate) {
							return $(obj).val() == $(mate).val();
						},
						mate: function(obj) {
							return $("#"+$(obj).attr("_repeatid"));
						},
						success: "valid",
						error: "missmatch",
						selector: "input[_repeatid],selector[_repeatid]"
					}
				},
				required: {
					check: function(obj) {
						return $(obj).val() != "";
					},
					success: "valid",
					error: "required",
					selector: "input:text.required,input:password.required,select.required,#recaptcha_response_field"
				},
				requiredCheckbox: {
					check: function(obj) {
						return $(obj).attr("checked");
					},
					selector: "input:checkbox.required"
				},
				requiredSelector: {
					check: function(obj) {
						return $(obj).val() != $(obj).attr("_nullval");
					},
					success: "valid",
					error: "required",
					selector: "select.required"
				},
				regexp: {
					wrapper: function(rule) {
						return {
							check: function(obj) {
								return rule.exp.test($(obj).val());
							},
							success: "valid",
							error: rule.error,
							selector: rule.selector
						};
					},
					email: {
						exp:/^((([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,
						error: "badEmail",
						selector: "input:text.email,input:text.newemail"
					},
					recaptcha: {
						exp: /^[^\s]+\s[^\s]+$/,
						selector: "#recaptcha_response_field"
					},
					url: {
						exp: /(^$)|(^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$)/i,
						error: "badUrl",
						selector: "input:text.url"
					},
					password: {
						exp: /^[\w\.\-\s\*+=(!)%@_?#,.<>&]+$/,
						error: "lettersNumberOnly",
						selector: "input:password"
					},
					name: {
						exp: /^[A-Za-zàèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸåÅæÆœŒçÇðÐøØßěščřžů\s\.]+(?!\s)$/,
						error: "badName",
						selector: "input:text.name"
					},
					username: {
						exp: /^[\w\.]+$/,
						error: "unsuitableChars",
						selector: "input:text.username,input:text.newuser"
					},
					invcode: {
						exp: /^[A-Z]{2}\-[0-9]{5}$/,
						error: "badInvCode",
						selector: "input.invcode"
					}
				}
			},
			minlen: {
				wrapper: function(rule) {
					return {
						check: function(validator) {
							var rem = rule.length - $(validator.field).val().length;
							if(rem <= 0)
								validator.valid(settings.messages.valid);
							else
								validator.invalid(settings.messages.minlength.replace("%length",rule.length).replace("%rem",rem));
						},
						selector: rule.selector
					};
				},
				password: {
					length: 6,
					selector: "input:password"
				}
			},
			remote: {
				wrapper: function(rule) {
					return {
						check: function(validator) {
							var request = rule.request(validator.field);
							if (request=== undefined)
								validator.next();
							else {
								$.ajax({
									type: "POST",
									url: rule.url,
									data: rule.request(validator.field),
									dataType: "text",
									success: function(response) {
										rule.response(response,validator);
									},
									error: function() {
										validator.valid(settings.messages.timeout);
									},
									timeout: 5000
								});
							}
						},
						selector: rule.selector
					};
				},
				checkDuplicate: {
					wrapper: function(rule) {
						return {
							response: function(response,validator) {
								if(response == rule.failResponse)
									validator.invalid(settings.messages[rule.error]);
								else
									validator.valid(settings.messages.valid);
								},
							request: function(obj) {
									request = {};
									request[rule.field] = $(obj).val();
									return request;
								},
							url: "general/checkduplicateuser.php",
							selector: rule.selector
						};
					},
					user: {
						failResponse: "username",
						field: "txtuname",
						error: "duplicateUser",
						selector: "input:text.newuser"
					},
					email: {
						failResponse: "email",
						field: "txtemail",
						error: "duplicateEmail",
						selector: "input:text.newemail"
					}
				}
			}
		}
	}
	
	function callWrapper(rules,wrap) {
		for(var name in rules)
			if(name != "wrapper")
				if(rules[name].wrapper === undefined)
					wrap(rules.wrapper(rules[name]));
				else
					callWrapper(rules[name],function(rule) {
						wrap(rules.wrapper(rule));
					});
	}
	var rules = [];
	callWrapper(settings.rules,function(rule) {
		rules.push(rule);
	});
	
	function validator(element) {
		this.field = element;
		var iteration,finalMessage,status;
		var isRunning = false;
		
		var selector = settings.messageSelector;
		var container = $(element).closest(":has("+selector+")");
		
		$(element).bind("validSubmit",defaultValidSubmit);
		
		if(container.find(":input").size() == 1) {
			var message = container.find(selector);
			var defaultMsg = message.html();
			var showMessage = function(msg) {
				if(msg === undefined)
					message.html(defaultMsg);
				else
					message.html(msg);
			};
		} else {
			var showMessage = function(msg){};
		}
		
		this.message = message;
		var validator = this;
		function next() {
			iteration++;
			if(rules.length > iteration) {
				rules[iteration](validator);
			}else
				stop();
		}
		
		this.next = next;
		var callbacks = [];
		
		this.start = function(callback) {
			if(!(callback===undefined))
				callbacks.push(callback);
			iteration = -1;
			finalMessage = "";
			status = "neutral";
			display(settings.messages.pending);
			if(!isRunning) {
				isRunning = true;
				next();
			}
		};
		
		function stop() {
			if(iteration != -1) {
				display(finalMessage);
				isRunning = false;
				$.each(callbacks,function(){
					this(status);
				});
				callbacks = [];
				$.each(dependancies,function() {
					$(this).checkField();
				});
			} else
				next();
		}
		
		this.valid = function(msg) {
			finalMessage = msg;
			status = "valid";
			next();
		};
		
		this.invalid = function(msg) {
			status = "invalid";
			finalMessage = msg;
			stop();
		};
		
		this.clear = function() {
			status = "neutral";
			display();
		};
		
		var mark = {
			valid: function(obj) {
				$(obj).addClass(settings.validClass).removeClass(settings.invalidClass);
			},
			invalid: function(obj) {
				$(obj).removeClass(settings.validClass).addClass(settings.invalidClass);
			},
			neutral: function(obj) {
				$(obj).removeClass(settings.validClass).removeClass(settings.invalidClass);
			}
		};
		
		function display(msg) {
			$.each([element,message],function(){
				mark[status](this);
			});
			showMessage(msg);
		}
		
		var dependancies = [];
		
		this.addDependancy = function(obj) {
			dependancies.push(obj);
			dependancies = $.unique(dependancies);
		};
	}
	
	$.extend({
		validatorSetup: function(options) {
			$.extend(settings,options);
		}
	});
	
	$.extend($.fn, {
		validate: function() {
			this.submit(function(){
				var allFields = $(this).find(":input");
				var countdown = allFields.size();
				var invalidCalls = 0;
				
				function callMeOnce() {
					invalidCalls!=0 || alert(settings.messages.reviseFormAlert);
					invalidCalls++;
				}
				
				var form = this;
				allFields.checkField(function(response) {
					if(response == "invalid")
						callMeOnce();
					else
						countdown--;
					countdown != 0 || eval(form.onSubmitString) === false || $(form).validSubmit();
				});
				return false;
			});
			
			return this.each(function() {
				this.onSubmitString = $(this).attr("onSubmit");
				$(this).attr("onSubmit","");
				
				$(this).find(":input").bind(settings.events,function() {
					this.validator.start();
				}).each(function(){
					this.validator !== undefined || (this.validator = new validator(this));
				});
			});
		},
		checkField: function(callback) {
			this.filter(":input").each(function(){
				this.validator !== undefined || (this.validator = new validator(this));
				this.validator.start(callback);
			});
			
			return this;
		},
		resetValidation: function() {
			this.filter(":input").each(function(){
				this.validator.clear();
			});
			return this;
		},
		message: function() {
			var arr = [];
			this.filter(":input").each(function() {
				this.validator.message === undefined || (arr = arr.concat(this.validator.message.get()));
			});
			return $($.makeArray(arr));
		},
		validSubmit: function(fn) {
			if(fn === undefined)
				return this.trigger('validSubmit');
			this.unbind('validSubmit',defaultValidSubmit);
			this.bind('validSubmit',function(event) {
				fn.apply(this,[event])===false || this.submit();
			});
		}
	});
})(jQuery);

