$.validator.setDefaults({
	errorElement: 'div',
	errorClass: 'form_error',
	meta: 'validate',
	onfocusout: function(element) {
		if ( ! this.checkable(element) && (element.name in this.submitted || ! this.optional(element)) ) {
			if ( ! this.element(element)) {
				$.validator.reflow();
			}
		}
	},
	onkeyup: function(element) {
		if ( element.name in this.submitted || element == this.lastElement ) {
			if (!this.element(element)) {
				var $form = $(element.form),
				validator = $form.data('validator'),
				$error = $form.find(validator.settings.errorElement+'.'+validator.settings.errorClass+'[for="'+element.id+'"]');
				$error.length && $.validator.reflowError($error);
			}
		}
	},
	errorPlacement: function(error, $ele, render_error) {
		var insertFunction = 'insertAfter',
			$insertAt = $ele;

		// Make sure the error receives the correct arrow direction.
		if ($ele.hasClass('error_arrow_top'))
			error.addClass('arrow_top');
		else if ($ele.hasClass('error_arrow_bottom'))
			error.addClass('arrow_bottom');
		else if ($ele.hasClass('error_arrow_left'))
			error.addClass('arrow_left');
		else if ($ele.hasClass('error_arrow_right'))
			error.addClass('arrow_right');
		else
			error.addClass('arrow_left');

		if (render_error === false)
			return;

		if ($ele.is(':radio,:checkbox') && $ele.next().is('label')) {
			$insertAt = $ele.next();
		}
		else if ($ele.is(':radio')) {
			$insertAt = $('input:radio[name="'+ $ele.attr('name') +'"]').eq(-1);
		}
		else {
			var validator = $ele.parents('form').data('validator');

			if (typeof validator.settings.groups != 'undefined') {
				var group = error.attr('for');

				if (typeof validator.settings.groups != 'undefined' && validator.settings.groups.hasOwnProperty(group)) {
					var field = validator.settings.groups[group].split(' ').pop();
					$insertAt = $("input[name='"+field+"'], select[name='"+field+"'], textarea[name='"+field+"']").eq(-1);
				}
			}
		}

		error.css('visibility', 'hidden');
		error[insertFunction]($insertAt);
	},
	invalidHandler: function(form, validator) {
		var errors = validator.numberOfInvalids(),
			interval;

		for (var group in validator.settings.groups) {
			if (validator.settings.groups.hasOwnProperty(group)) {
				var fields = validator.settings.groups[group].split(' ');
				errors -= fields.length - 1;
				if (errors <= 0) errors = 1;
			}
		}

		// Not all errors may be in the DOM yet, so wait until they are.
		var _this = {
			do_reflow: function() {
				// console.log('num error elements: ', $("div.form_error").length, '   validator reports: ', errors);
				if (errors && $("div.form_error").length >= errors) {
					clearInterval(interval);
					$.validator.reflow();
				}
			}
		};
		interval = setInterval(_this.do_reflow, 100);

		if (FORMALIZE) {
			FORMALIZE.misc.add_placeholder();
		}
	},
	prependToError: '<em/>'
});

$.extend($.validator, {
	hideErrors: function() {
		$("form").each(function() {
			var validator = $.data(this, 'validator');
			$(validator.settings.errorElement+'.'+validator.settings.errorClass).hide();
		})
	},
	reflow: function() {
		$.extend($.validator, {
			winWidth: $(window).width(),
			winHeight: $(window).height()
		});

		$("form").each(function() {
			// There is a bug in the validation process that affects validator.errorList.
			// If that bug didn't exist, we could loop through errorList and avoid the .errors() workaround.
			// TODO: If that bug is fixed, loop through .errorList instead of .errors(). It's much easier.
			var validator = $.data(this, 'validator');

			if (typeof validator == "object") {
				var $errors = validator.errors();

				$errors.each(function(key, error) {
					var $error = $(error);
					$.validator.reflowError($error);
				});

				/* TODO: Use this once the bug mentioned above is fixed.
				// WARNING: The below code is missing a lot of key elements from the code above. Be sure to copy the parts that are needed.
				for (var i = 0; i < validator.errorList.length; i++) {
					var error = validator.errorList[i],
						$label = validator.errorsFor(error.element),
						$element = $(error.element);

					if ($label.length) {
						offset = $element.position();
						$label.css({
							'position': 'absolute',
							'left': offset.left + $element.outerWidth(),
							'top': offset.top
						});
					}
				}
				*/
			}
		});
	},
	reflowError: function($error) {
		var validator = $error.parents('form').data('validator'),
			$window = $(window);

		if (typeof validator == "object") {
			var labelFor = $error.attr("for"),
				$input = $('#'+labelFor);

			// Can't find an input with the given id {labelFor}.
			// Look for inputs whose name is {labelFor} instead.
			if ( ! $input.length) {
				$input = $("input[name='"+labelFor+"'], select[name='"+labelFor+"'], textarea[name='"+labelFor+"']");

				if ($input.length) {
					$input = $input.eq( $input.hasClass('error_last_element') ? -1 : 0 );
				}
			}

			// Can't find any inputs whose name is {labelFor}.
			// Error must be for a group of elements that have different names.
			// Find the last element in the group and use it for {$input}.
			if ( ! $input.length) {
				var lastField = false;

				if (typeof validator.settings.groups[labelFor] != 'undefined') {
					var fields = validator.settings.groups[labelFor].split(' ');
					lastField = fields.pop();
				}

				if (lastField) {
					$input = $("input[name='"+lastField+"'], select[name='"+lastField+"'], textarea[name='"+lastField+"']").eq(-1);
				}
				else {
					// Can't find an element tied to the error.
					// Nothing more we can do.
					return true;
				}
			}

			if ($error.length && $input.length) {
				var iPosition = $input.position(),
					$labelArrow = $error.find('em'),
					top = 0,
					left = 0;

				$error.css({
					'position': 'absolute'
				});

				var iWidth = $input.outerWidth(),
					eWidth = $error.outerWidth();

				if ($error.hasClass('arrow_top')) {
					top = iPosition.top + $input.outerHeight() + ($labelArrow.length ? 6 : 0);
					left = iPosition.left + iWidth - (eWidth > iWidth ? 20 : eWidth);
				}
				else if ($error.hasClass('arrow_bottom')) {
					top = iPosition.top - $error.outerHeight() - ($labelArrow.length ? 5 : 0);
					left = iPosition.left + iWidth - (eWidth > iWidth ? 20 : eWidth);
				}
				else if ($error.hasClass('arrow_right')) {
					top = iPosition.top;
					left = iPosition.left - eWidth - ($labelArrow.length ? 6 : 0);
				}
				else {
					top = iPosition.top;
					left = iPosition.left + iWidth + ($labelArrow.length ? 6 : 0);
				}

				if ($input.is(":checkbox") || $input.is(":radio")) {
					var $labelForElement = $('label[for="'+ $input.attr('id') +'"]');

					if ($labelForElement.length) {
						iPosition = $labelForElement.position();
						top = iPosition.top - 3;
						left = iPosition.left + $labelForElement.outerWidth() + 3 + ($labelArrow.length ? 6 : 0);
					}
				}

				$error.css({
					'left': left,
					'top': top,
					'visibility': 'visible'
				});

				// Keep error in screen horizontally.
				if ($error.offset().left + eWidth > $.validator.winWidth)
					$error.css('left', ($error.position().left - Math.abs($.validator.winWidth - ($error.offset().left + eWidth) - 5)) +'px');
			}
		}
	}
});

// Expand ruleset available in elements' class attrib.
$.validator.addClassRules('image', {image: true});

// Add new method to validator.
$.validator.addMethod("image", function(value, element, params) {
	var parts = value.split('.');
	var extension = parts[parts.length - 1].toLowerCase();
	return this.optional(element) || extension == "jpg" || extension == "jpeg" || extension == "gif" || extension == "png";
}, 'The format of the file you chose is not supported');
