(function($) {
	function isEmailValid(email)
	{
		return (email && email.length && email.match(/^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/));
	}

	function checkType(element, type)
	{
		// any type
		if (!type) {
			return true;
		}

		var func = knownTypes[type];

		if (func) {
			return func(element);
		}
		else
		{
			return false;
		}
	}

	var knownTypes = {
		'email': isEmailValid,
		'number': function(element) { return (element && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(element)); },
		'alphanum': function(element) { return element && /^(.)+$/.test(element); }
	},

	messages = {
		mandatory: 'This field is required',
		email: 'Please enter a valid email',
		alphanum: 'please only use allowed characters: numbers, letters, _',
		equalsTo: 'Passwords don\'t match',
		min: 'Must be at least $1 characters',
		max: 'Must be less than $1 characters'
	};

	$.fn.showErrors = function(errors)
	{
		for (var i = 0, size = errors.length; i < size; i++) {
			// TODO: add border to input ?
			var div = $('#' + errors[i].element).siblings('div.error');

			div.fadeIn().find('p').html(errors[i].message);
		}
	}	

	$.fn.checkForm = function(options)
	{
		var options = $.extend({
				errorCallback: null,
				successCallback: null,
				elements: null,
				customTypes: null
			}, options);

		function checkSubmit()
		{
			var data = $(this).data(),
				errors = [];

			// hide help bubbles + any previous error message
			$(this).find('.bubble, span.error').hide();

			if (data && data.elements) {
				var elements = data.elements;
				for(var name in data.elements)
				{
					var value = this.find('#' + name).val(),
						placeholder = this.find('#' + name).attr('placeholder'),
						input = elements[name];

					if ( (input.mandatory && ( (!value || !value.length) || value === placeholder )))
					{
						errors.push({
							element: this.find('#' + name),
							message: messages['mandatory']
						});

						continue;
					}

					if (!checkType(value, input.type))
					{
						errors.push({
							element: this.find('#' + name),
							message: messages[input.type]
						});

						continue;
					}

					if (input.minMax) {
						if (value.length < input.minMax[0])
						{
							errors.push({
								element: this.find('#' + name),
								message: messages['min'].replace('$1', input.minMax[0])
							});

							continue;
						}
						else if (value.length > input.minMax[1])
						{
							errors.push({
								element: this.find('#' + name),
								message: messages['max'].replace('$1', input.minMax[1])
							});

							continue;						
						}
					}

					if (input.equalsTo && value !== placeholder) {
						confirmValue = this.find(input.equalsTo).val();
						if (value !== confirmValue) {
							errors.push({
								element: this.find('#' + name),
								message: messages['equalsTo']
							},
							{
								element: this.find(input.equalsTo),
								message: messages['equalsTo']
							});
						}
					}
				}
			}

			if (errors.length)
			{
				showErrors.apply(this, [errors]);
				
				if (options.errorCallback) {
					return options.errorCallback.apply(this, [errors]);
				}
			}
			else if (options.successCallback)
			{
				return options.successCallback.apply(this);
			}
		}

		function showErrors(errors)
		{
			for (var i = 0, size = errors.length; i < size; i++) {
				// TODO: add border to input ?
				var div = errors[i].element.siblings('div.error');

				div.fadeIn().find('p').html(errors[i].message);
			}
		}

		return this.each(function()
		{
			if ($(this)[0].nodeName.toLowerCase() === 'form') {

				if (options.elements) {
					$(this).data({ elements: options.elements, success: options.successCallback, error: options.errorCallback});
				}

				// add new checks
				if (options.customTypes) {
					knownTypes = $.extend(options.customTypes, knownTypes);

				}

				$(this).submit(jQuery.proxy(checkSubmit, $(this)));

				// $(this).find('input').blur(jQuery.proxy(checkSubmit, $(this)));
			}
		});
	}
})(jQuery);
