(function ($) {
		
	var rules = new Array();
	
	function addValidation (args, fieldGrouper) {
		var selectors = new Array();
		for (var i = 0; i < args.length - 2; i++)
			selectors.push(args[i]);
		rules.push({
			selectors: selectors,
			message: args[args.length - 2],
			validator: args[args.length - 1],
			fieldGrouper: fieldGrouper
		});
	}
	
	function trimString (str) {
		return str.replace(/^\s*/, "").replace(/\s*$/, "");
	}
	
	function compareFieldPosition (a, b) {
		if (a.compareDocumentPosition) {
			var n = a.compareDocumentPosition(b);
			return (n & 0x02) > 0 ? 1 : (n & 0x04) > 0 ? -1 : 0;
		}
		if (a.sourceIndex)
			return a.sourceIndex - b.sourceIndex;
		return 0;
	}
	
	

	$.addValidation = function () {
		addValidation(arguments, function (context, selectors) {
			var fieldSets = new Array();
			for (var i = 0; i < selectors.length; i++)
				$(selectors[i], context).each(function(j) {
					if (!fieldSets[j])
						fieldSets[j] = new Array();
					fieldSets[j][i] = this;
				});
			return fieldSets;
		});
	}

	$.addGroupValidation = function () {
		addValidation(arguments, function (context, selectors) {
			var fieldSets = new Array();
			for (var i = 0; i < selectors.length; i++)
				$(selectors[i], context).each(function(j) {
					if (!fieldSets[i])
						fieldSets[i] = new Array();
					fieldSets[i][j] = this;
				});
			return fieldSets;
		});
	}
	
	$.fn.validate = function (options, event) {
		options = $.extend(new Object(), $.fn.validate.options, options);
		var fieldsById = new Object();
		var testCases = new Array();
		
		if (event && options.invalidClass)
			$("*", event.currentTarget).removeClass(options.invalidClass);
		
		for (var i = 0; i < rules.length; i++)  {
			var fieldSets = rules[i].fieldGrouper(this, rules[i].selectors);
			for (var j = 0; j < fieldSets.length; j++) {
				var sortedFields = fieldSets[j].slice(0);
				sortedFields.sort(compareFieldPosition);
				testCases.push({fields: fieldSets[j], sortedFields: sortedFields, message: rules[i].message, validator: rules[i].validator});
				for (var k = 0; k < fieldSets[j].length; k++)
					fieldsById[String($.data(fieldSets[j][k]))] = fieldSets[j][k];
			}
		}
		
		testCases.sort(function (a, b) {
			for (var i = 0; i < Math.min(a.sortedFields.length, b.sortedFields.length); i++) {
				var n = compareFieldPosition(a.sortedFields[a.sortedFields.length - i - 1], b.sortedFields[b.sortedFields.length - i - 1]);
				if (n != 0)
					return n;
			}
			return a.sortedFields.length - b.sortedFields.length;
		});
		
		var errors = new Array();
		var invalidFields = new Object();
		for (var i = 0; (options.errorLimit == null || errors.length < options.errorLimit) && i < testCases.length; i++) {
			var invalid = false;
			if (!options.multipleErrorsPerField)
				for (var j = 0; !invalid && j < testCases[i].fields.length; j++)
					if (invalidFields[$.data(testCases[i].fields[j])])
						invalid = true;
			testCases[i].validator.apply(this, testCases[i].fields)
			if (!invalid && !testCases[i].validator.apply(this, testCases[i].fields)) {
				errors.push({fields: testCases[i].fields, message: options.messageGenerator(testCases[i].message, testCases[i].fields)});
				if (!options.multipleErrorsPerField)
					for (var j = 0; j < testCases[i].fields.length; j++)
						invalidFields[$.data(testCases[i].fields[j])] = true;
			
			}
		}
		
		if (errors.length > 0) {
			var str = "";
			var focusSet = !options.focus;
			for (var i = 0; i < errors.length; i++) {
				str += (i == 0 ? "" : "\n") + errors[i].message;
				if (options.invalidClass)
					$(errors[i].fields).addClass(options.invalidClass);
				if (!focusSet)
					try {
						focusSet = true;
						errors[i].fields[0].focus();
						errors[i].fields[0].select();
					} catch (e) {}
			}
			if (options.alertErrors)
				alert(str);
			if (options.stopEvent) {
				event.stopPropagation();
				event.preventDefault();
			}
		}
		
		return errors;
	}
	
	$.fn.validate.options = {
		errorLimit: null,
		multipleErrorsPerField: false,
		messageGenerator: function (message, fields) {
			return message.replace(/\{(\d+)\}/g, function (str, index) {
				var label;
				
				if (fields[index].id) {
					label = $("label[for='" + fields[index].id + "']");
					if (label.length > 0)
						return trimString(label.text());
				}
				
				label = $(fields[index]).closest("label");
				if (label.length > 0)
					return trimString(label.text());
				
				if ($(fields[index]).filter("button, object").length > 0)
					return trimString($(fields[index]).text());
				
				if ($(fields[index]).filter(":button, :submit, :reset").length > 0)
					return trimString($(fields[index]).val());
				
				return "";
				
			});
		},
		invalidClass: null,
		alertErrors: false,
		focus: false,
		stopEvent: false
	}
	
})(jQuery);