/**
* Inline Form Validation Engine 2.2.4, jQuery plugin
*
* Copyright(c) 2010, Cedric Dugas
* http://www.position-absolute.com
*
* 2.0 Rewrite by Olivier Refalo
* http://www.crionics.com
*
* Extremally modified by m157y
*
* Form validation engine allowing custom regex rules to be added.
* Licensed under the MIT License
*/
(function($, undefined) {
//@todo do we really need rules array? O_O
	var validatorsMethods =
	{
		/**
		* Required validation
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		required: function($field, rules, i, options)
		{
			// Prepare variables
			var
				errorString = false;

			// Select error string
			switch ($field.prop('type'))
			{
				case 'text':
				case 'password':
				case 'textarea':
				case 'file':
				default:
					if ($field.val() === '')
					{
						errorString = ($field.prop('tagName') === 'SELECT') ? options.allrules[rules[i]].alertTextSelect : options.allrules[rules[i]].alertText;
					}
				break;

				case 'radio':
				case 'checkbox':
					// Prepare variables
					var
						$form = $field.closest('form'),

						// Catch field name
						name = $field.attr('name');

					if ($form.find('input[name="' + name + '"]:checked').size() === 0)
					{
						if ($form.find('input[name="' + name + '"]').size() === 1)
						{
							errorString = options.allrules[rules[i]].alertTextCheckboxe;
						}
						else
						{
							errorString = options.allrules[rules[i]].alertTextCheckboxMultiple;
						}
					}
				break;

				// Required for <select>
				case 'select-one':
					// Added by paul@kinetek.net for select boxes, Thank you
//@todo replace !condition with value !== ''/false ?
					if (!$field.val())
					{
						errorString = options.allrules[rules[i]].alertText;
					}

				break;

				case 'select-multiple':
					// Added by paul@kinetek.net for select boxes, Thank you
//@todo look below
					if (!$field.find('option:selected').val())
					{
						errorString = options.allrules[rules[i]].alertText;
					}
				break;
			}

			return errorString;
		},

		/**
		* Validate that 1 from the group field is required
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		groupRequired: function($field, rules, i, options)
		{
			// Prepare variables
			var
				// Valid state
				isValid = false;

			// Check all fields from the group
			$field.closest('form').find('[class*="' + rules[i + 1] + '"]').each(
				function()
				{
					if (validatorsMethods.required($(this), rules, i, options) === false)
					{
						isValid = true;

						return false;
					}
				}
			);

			// Return error string if need
			if (isValid === false)
			{
				return options.allrules[rules[i]].alertText;
			}

			return false;
		},

		/**
		* Validate Regex rules
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		custom: function($field, rules, i, options)
		{
			// Prepare variables
			var
				// Just catch a custom rule
				rule = rules[i + 1];

			// Return an error if rule doesn't exist
			if (rule === undefined)
			{
//@todo localizable error string
				return 'jqv:custom rule not found ' + rule;
			}

			// Return an error if RegEx doesn't exist
			if (options.allrules[rule].regex === undefined)
			{
//@todo localizable error string
				return 'jqv:custom regex not found ' + rule;
			}

			// Check field with specified RegExp and return an error if need
			if ((new RegExp(options.allrules[rule].regex)).test($field.val()) === false)
			{
				return options.allrules[rule].alertText;
			}

			return false;
		},

		/**
		* Validate custom function outside of the engine scope
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		funcCall: function($field, rules, i, options)
		{
			var
				func = privateMethods.getFunctionByName(rules[i + 1]);

			// Call custom function
			if (typeof(func) === 'function')
			{
				return func($field, rules, i, options);
			}

			return false;
		},

		/**
		* Field match
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		equals: function($field, rules, i, options)
		{
			// Check fields
			if ($field.val() !== $('#' + rules[i + 1]).val())
			{
				return options.allrules.equals.alertText;
			}

			return false;
		},

		/**
		* Check the maximum size (in characters)
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		maxSize: function($field, rules, i, options)
		{
			// Prepare variables
			var
				rule = options.allrules.maxSize;

			// Check field
			if ($field.val().length > rules[i + 1])
			{
				return rule.alertText + rules[i + 1] + rule.alertText2;
			}

			return false;
		},

		/**
		* Check the minimum size (in characters)
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		minSize: function($field, rules, i, options)
		{
			// Prepare variables
			var
				rule = options.allrules.minSize;

			// Check field
			if ($field.val().length < rules[i + 1])
			{
				return rule.alertText + rules[i + 1] + rule.alertText2;
			}

			return false;
		},

		/**
		* Check number minimum value
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		min: function($field, rules, i, options)
		{
			// Prepare variables
			var
				min = parseFloat(rules[i + 1]),
				rule = options.allrules.min;

			// Check field
			if (parseFloat($field.val()) < min)
			{
				if (rule.alertText2)
				{
					return rule.alertText + min + rule.alertText2;
				}

				return rule.alertText + min;
			}

			return false;
		},

		/**
		* Check number maximum value
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		max: function($field, rules, i, options)
		{
			// Prepare variables
			var
				max = parseFloat(rules[i + 1]),
				rule = options.allrules.max;
			var len = parseFloat($field.val());

			// Check field
			if (parseFloat($field.val()) > max)
			{
				if (rule.alertText2)
				{
					return rule.alertText + max + rule.alertText2;
				}
				//orefalo: to review, also do the translations

				return rule.alertText + max;
			}

			return false;
		},

		/**
		* Checks date is in the past
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		past: function($field, rules, i, options)
		{
			// Prepare variables
			var
				past = rules[i + 1],
				pastDate = (past.toLowerCase() === 'now') ? new Date() : privateMethods.parseDate(past),
				rule = options.allrules.past;

			// Check field
			if (privateMethods.parseDate($field.val()) < pastDate)
			{
				if (rule.alertText2)
				{
					return rule.alertText + privateMethods.dateToString(pastDate) + rule.alertText2;
				}

				return rule.alertText + privateMethods.dateToString(pastDate);
			}

			return false;
		},

		/**
		* Checks date is in the future
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		future: function($field, rules, i, options)
		{
			// Prepare variable
			var
				future = rules[i + 1],
				futureDate = (future.toLowerCase() === 'now') ? new Date() : privateMethods.parseDate(future),
				rule = options.allrules.future;

			if (privateMethods.parseDate($field.val()) > futureDate)
			{
				if (rule.alertText2)
				{
					return rule.alertText + privateMethods.dateToString(futureDate) + rule.alertText2;
				}

				return rule.alertText + privateMethods.dateToString(futureDate);
			}

			return false;
		},
//@todo not validator
		/**
		* Checks if valid date
		*
		* @param	string	Date string
		* @return	bool	True if date is valid or false at another case
		*/
		isDate: function(value)
		{
			return (new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/)).test(value);
		},
//@todo not validator
		/**
		* Checks if valid date time
		*
		* @param	string	Date string
		* @return	bool	True if date is valid or false at another case
		*/
		isDateTime: function(value)
		{
			return (new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1}$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^((1[012]|0?[1-9]){1}\/(0?[1-9]|[12][0-9]|3[01]){1}\/\d{2,4}\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1})$/)).test(value);
		},
//@todo not validator
		/**
		* Checks if the start date is before the end date
		*
		* @param	string	Date string
		* @param	string	Date string
		* @return	bool true if end is later than start or false at another case
		*/
		dateCompare: function(start, end)
		{
			return (new Date(start.toString()) < new Date(end.toString()));
		},

		/**
		* Checks date range
		*
		* @param	jqObject	First field
		* @param	jqObject	Second field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		dateRange: function(first, second, rules, i, options)
		{
			// Are not both populated
			if (!first.val() || !second.val())
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			// Are not both dates
			if (validatorsMethods.isDate(first.val()) === false || validatorsMethods.isDate(second.val()) === false)
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			// Are both dates but range is off
			if (validatorsMethods.dateCompare(first.val(), second.val()) === false)
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			return false;
		},

		/**
		* Checks date time range
		*
		* @param	jqObject	First field
		* @param	jqObject	Second field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		dateTimeRange: function(first, second, rules, i, options)
		{
			// Are not both populated
			if (!first.val() || !second.val())
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			// Are not both dates
			if (validatorsMethods.isDateTime(first.val()) === false || validatorsMethods.isDateTime(second.val()) === false)
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			// Are both dates but range is off
			if (validatorsMethods.dateCompare(first.val(), second.val()) === false)
			{
				return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
			}

			return false
		},

		/**
		* Max number of checkbox selected
		*
		* @param	jqObject	Form
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		maxCheckbox: function($form, $field, rules, i, options)
		{
			// Check field
			if ($form.find('input[name="' + $field.attr('name') + '"]:checked').size() > rules[i + 1])
			{
				options.showArrow = false;

				if (options.allrules.maxCheckbox.alertText2)
				{
					return options.allrules.maxCheckbox.alertText + ' ' + rules[i + 1] + ' ' + options.allrules.maxCheckbox.alertText2;
				}

				return options.allrules.maxCheckbox.alertText;
			}

			return false;
		},

		/**
		* Min number of checkbox selected
		*
		* @param	jqObject	Form
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		* @return	bool|string	false on success or error string
		*/
		minCheckbox: function($form, $field, rules, i, options)
		{
			// Check field
			if ($form.find('input[name="' + $field.attr('name') + '"]:checked').size() < rules[i + 1])
			{
				options.showArrow = false;

				if (options.allrules.minCheckbox.alertText2)
				{
					return options.allrules.minCheckbox.alertText + ' ' + rules[i + 1] + ' ' + options.allrules.minCheckbox.alertText2;
				}

				return options.allrules.minCheckbox.alertText;
			}

			return false;
		},

		/**
		* Ajax field validation
		*
		* @param	jqObject	Field
		* @param	array[string]
		* 						Rules
		* @param	int			i rules index
		* @param	map			user options
		*/
//@todo return value
		ajax: function($field, rules, i, options)
		{
			// Prepare variables
			var
				errorSelector = rules[i + 1],
				rule = options.allrules[errorSelector],
				extraData = rule.extraData || '',
				extraDataDynamic = rule.extraDataDynamic;

			if (extraDataDynamic)
			{
				var
					tmpData = [],
					domIds = String(extraDataDynamic).split(','),

					inputValue = '',
					keyValue = '',

					i = 0,
					il = domIds.length;

				for (; i < il; ++i)
				{
					if ($(domIds[i]).length)
					{
						inputValue = $field.closest('form').find(id).val(),
						keyValue = id.replace('#', '') + '=' + escape(inputValue);

						tmpData.push(keyValue);
					}
				}

				extraDataDynamic = tmpData.join('&');
			}
			else
			{
			  extraDataDynamic = '';
			}

			if (!options.isError)
			{
				$.ajax({
					type: 'GET',
					url: rule.url,
					cache: false,
					dataType: 'json',
					data: 'fieldId=' + $field.attr('id') + 'fieldName=' + $field.attr('name') + '&fieldValue=' + $field.val() + '&extraData=' + extraData + '&' + extraDataDynamic,
					field: $field,
					rule: rule,
					methods: methods,
					options: options,
					beforeSend: function()
					{
						// Build the loading prompt
						var
							loadingText = rule.alertTextLoad;

						if (loadingText)
						{
							privateMethods.showPrompt($field, loadingText, 'load', true, options);
						}
					},
					error: function(data, transport)
					{
						privateMethods.ajaxError(data, transport);
					},
					success: function(json)
					{
						// Asynchronously called on success, data is the json answer from the server
						var
							errorFieldId = json[0],
//@todo
							errorField = $($('#' + errorFieldId)[0]);
//@todo WTF?
						// Make sure we found the element
						if (errorField.length === 1)
						{
							var
								status = json[1],
								// Read the optional msg from the server
								msg = json[2];

							if (!status)
							{
								// Houston we got a problem - display an red prompt
								options.ajaxValidCache[errorFieldId] = false;
								options.isError = true;

								// Resolve the msg prompt
								if (msg)
								{
									if (options.allrules[msg])
									{
										var
											txt = options.allrules[msg].alertText;

										if (txt)
										{
											msg = txt;
										}
									}
								}
								else
								{
									msg = rule.alertText;
								}

								privateMethods.showPrompt(errorField, msg, '', true, options);
							}
							else
							{
								if (options.ajaxValidCache[errorFieldId] !== undefined)
								{
									options.ajaxValidCache[errorFieldId] = true;
								}

								// Resolves the msg prompt
								if (msg)
								{
									if (options.allrules[msg])
									{
										var
											txt = options.allrules[msg].alertTextOk;

										if (txt)
										{
											msg = txt;
										}
									}
								}
								else
								{
									msg = rule.alertTextOk;
								}

								// see if we should display a green prompt
								if (msg)
								{
									privateMethods.showPrompt(errorField, msg, 'pass', true, options);
								}
								else
								{
									privateMethods.closePrompt(errorField);
								}
							}

							errorField.trigger('jqv.field.result', [errorField, !options.isError, msg]);
						}
					}
				});
			}
		}
	};

	/**
	* Private methods
	*/
	var privateMethods =
	{
		/***********************************************************************
		* Core methods
		***********************************************************************/
		/**
		* Saves the user options and variables in the form.data
		*
		* @param	jqObject	Form - the form where the user option should be saved
		* @param	map			Options - the user options
		* @return	object		The user options (extended from the defaults)
		*/
		saveOptions: function(form, options)
		{
			// Is there a language localization?
			if ($.validationEngineLanguage === undefined)
			{
//@todo localizable error string
				$.error('jQuery.validationEngine rules are not loaded, plz add localization files to the page');
			}

			// --- Internals DO NOT TOUCH or OVERLOAD ---
			// Validation rules and i18n
			$.validationEngine.defaults.allrules = $.validationEngineLanguage.allRules;

			var
				userOptions = $.extend(true, {}, $.validationEngine.defaults, options);

			form.data('form-validation', true);
			form.data('form-validation-options', userOptions);

			return userOptions;
		},

		/**
		* Returns the error prompt matching the field if any
		*
		* @param	jqObject		Field
		* @return	undefined|string
		*							Undefined if prompt doesn't exists or
		* 							the error prompt object (jqObject)
		*/
		getPrompt: function($field)
		{
			var
				className = privateMethods.getClassName($field.attr('id')) + 'formError',
				match = $('.' + privateMethods.escapeExpression(className))[0];

			if (match)
			{
				return $(match);
			}
		},

		/***********************************************************************
		* UI methods
		***********************************************************************/
		/**
		* Calculates prompt position
		*
		* @param	jqObject	Field
		* @param	jqObject	The prompt
		* @param	map			options
		* @return	positions
		*/
		calculatePosition: function ($field, promptElement, options)
		{
			// Prepare variables
			var
				promptTopPosition = 0,
				promptLeftPosition = 0,
				marginTopSize = 0,
				fieldWidth = $field.width(),
				promptHeight = promptElement.height(),
				promptWidth = promptElement.width();

			// Is the form contained in an overflown container?
			if (options.isOverflown === true)
			{
				// Compensation for the arrow
				marginTopSize = -promptHeight;
			}
			else
			{
				var
					offset = $field.offset();

				promptTopPosition = offset.top;
				promptLeftPosition = offset.left;
			}

			// Prompt positioning adjustment support
			// usage: positionType:Xshift,Yshift
			// For example:
			//     bottomLeft:+20 means bottomLeft position shifted by 20 pixels right horizontally
			//     topRight:20, -15 means topRight position shifted by 20 pixels to right and 15 pixels to top
			// You can use +pixels, - pixels. If no sign is provided than + is default.
			var
				positionType = $field.data('promptPosition') || options.promptPosition,
				shift1 = '',
				shift2 = '',
				shiftX = 0,
				shiftY = 0;

			if (typeof(positionType) === 'string')
			{
				// Do we have any position adjustments?
				if (positionType.indexOf(':') !== -1)
				{
					shift1 = positionType.substring(positionType.indexOf(':') + 1);
					positionType = positionType.substring(0, positionType.indexOf(':'));

					// If any advanced positioning will be needed (percents or something else) -
					// parser should be added here for now we use simple parseInt()

					// Do we have second parameter?
					if (shift1.indexOf(',') !== -1)
					{
						shift2 = shift1.substring(shift1.indexOf(',') + 1);
						shift1 = shift1.substring(0, shift1.indexOf(','));
						shiftY = parseInt(shift2);
						if (isNaN(shiftY))
						{
							shiftY = 0;
						}
					}

					shiftX = parseInt(shift1);
					if (isNaN(shift1))
					{
						shift1 = 0;
					}
				}
			}

			// Calculate prompt positions
			switch (positionType)
			{
				case 'bottomLeft':
					promptTopPosition = promptTopPosition + $field.height() + 15;
				break;

				case 'bottomRight':
					promptLeftPosition += fieldWidth - 30;
					promptTopPosition += $field.height() + 5;
				break;

				case 'centerLeft':
					promptLeftPosition -= promptWidth + 13;
				break;

				case 'centerRight':
					promptLeftPosition += fieldWidth + 13;
				break;

				case 'topLeft':
					promptTopPosition += -promptHeight - 10;
				break;

				case 'topRight':
				default:
					// Is the form contained in an overflown container?
					if (options.isOverflown === true)
					{
						promptLeftPosition += fieldWidth - 30;
					}
					else
					{
						promptLeftPosition += fieldWidth - 30;
						promptTopPosition += -promptHeight -2;
					}
				break;
			}


			// Apply adjusments if any
			promptLeftPosition += shiftX;
			promptTopPosition += shiftY;

			return {
				callerTopPosition: promptTopPosition + 'px',
				callerleftPosition: promptLeftPosition + 'px',
				marginTopSize: marginTopSize + 'px'
			};
		},

		/**
		* Builds or updates a prompt with the given information
		*
		* @param	jqObject		Field
		* @param	string			HTML text to display type
		* @param	string			The type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param	boolean			Use to mark fields than being validated with ajax
		* @param	map				User options
		*/
		showPrompt: function($field, promptText, type, ajaxed, options, ajaxform)
		{
			var
				prompt = privateMethods.getPrompt($field);

			/**
			* The ajax submit errors are not see has an error in the form,
			* When the form errors are returned, the engine see 2 bubbles, but those are ebing closed by the engine at the same time
			* Because no error was found befor submitting
			*/
			if (ajaxform)
			{
				prompt = false;
			}

			if (prompt)
			{
				privateMethods.updatePrompt($field, prompt, promptText, type, ajaxed, options);
			}
			else
			{
				privateMethods.buildPrompt($field, promptText, type, ajaxed, options);
			}
		},

		/**
		* Builds and shades a prompt for the given field.
		*
		* @param	jqObject		Field
		* @param	string			HTML text to display type
		* @param	string			The type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param	boolean			Use to mark fields than being validated with ajax
		* @param	map				Options user options
		*/
		buildPrompt: function($field, promptText, type, ajaxed, options)
		{
			// Create the prompt
			var
				prompt = $('<div>');

			prompt.addClass(privateMethods.getClassName($field.attr('id')) + 'formError');

			// Add a class name to identify the parent form of the prompt
			if ($field.is(':input'))
			{
				prompt.addClass('parentForm' + privateMethods.getClassName($field.parents('form').attr('id')));
			}
			prompt.addClass('formError');

			switch (type)
			{
				case 'pass':
					prompt.addClass('greenPopup');
				break;

				case 'load':
					prompt.addClass('blackPopup');
				break;

				default:
					// It has error
					++options.InvalidCount;
			}

			if (ajaxed === true)
			{
				prompt.addClass('ajaxed');
			}

			// Create the prompt content
			var
				promptContent = $('<div>').addClass('formErrorContent').html(promptText).appendTo(prompt);

			// Create the css arrow pointing at the field
			// NOTE: that there is no triangle on max-checkbox and radio
			if (options.showArrow)
			{
				var
					arrow = $('<div>').addClass('formErrorArrow'),

					// Prompt positioning adjustment support
					// Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10)
					positionType = $field.data('promptPosition') || options.promptPosition;

				if (typeof(positionType) === 'string')
				{
					if (positionType.indexOf(':') !== -1)
					{
						positionType = positionType.substring(0, positionType.indexOf(':'));
					}
				}

				switch (positionType)
				{
					case 'bottomLeft':
					case 'bottomRight':
						prompt.find('.formErrorContent').before(arrow);
						arrow.addClass('formErrorArrowBottom').html('<div class="line1"><!-- --></div><div class="line2"><!-- --></div><div class="line3"><!-- --></div><div class="line4"><!-- --></div><div class="line5"><!-- --></div><div class="line6"><!-- --></div><div class="line7"><!-- --></div><div class="line8"><!-- --></div><div class="line9"><!-- --></div><div class="line10"><!-- --></div>');
					break;

					case 'topLeft':
					case 'topRight':
						arrow.html('<div class="line10"><!-- --></div><div class="line9"><!-- --></div><div class="line8"><!-- --></div><div class="line7"><!-- --></div><div class="line6"><!-- --></div><div class="line5"><!-- --></div><div class="line4"><!-- --></div><div class="line3"><!-- --></div><div class="line2"><!-- --></div><div class="line1"><!-- --></div>');
						prompt.append(arrow);
					break;
				}
			}

			// Cedric: Needed if a container is in position:relative
			// Insert prompt in the form or in the overflown container?
			if (options.isOverflown === true)
			{
				$field.before(prompt);
			}
			else
			{
				$('body').append(prompt);
			}

			// Wrap around prompt if need
			if (options.wrapper !== '')
			{
				prompt.wrap(options.wrapper);
			}

			// Catch prompt position
			var
				pos = privateMethods.calculatePosition($field, prompt, options);

			prompt.css({
				top: pos.callerTopPosition,
				left: pos.callerleftPosition,
				marginTop: pos.marginTopSize,
				opacity: 0
			}).data('callerField', $field);

			return prompt.animate({
				opacity: 0.87
			});
		},

		/**
		* Updates the prompt text field - the field for which the prompt
		* @param	jqObject		Field
		* @param	string			HTML text to display type
		* @param	string			The type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param	boolean			Use to mark fields than being validated with ajax
		* @param	map				options user options
		*/
		updatePrompt: function($field, prompt, promptText, type, ajaxed, options, noAnimation)
		{
			// Don't do anything if prompt doesn't exists
			if (prompt)
			{
				return;
			}

			// Remove classes
			prompt.removeClass('greenPopup blackPopup ajaxed');

			// Setup new class
			if (typeof type !== 'undefined')
			{
				if (type === 'pass')
				{
					prompt.addClass('greenPopup');
				}

				if (type === 'load')
				{
					prompt.addClass('blackPopup');
				}
			}
			if (ajaxed === true)
			{
				prompt.addClass('ajaxed');
			}

			// Set prompt text
			prompt.find('.formErrorContent').html(promptText);

			// Catch prompt position
			var
				pos = privateMethods.calculatePosition($field, prompt, options),
				css = {
					top: pos.callerTopPosition,
					left: pos.callerleftPosition,
					marginTop: pos.marginTopSize
				};

			// Setup prompt
			if (noAnimation === true)
			{
				prompt.css(css);
			}
			else
			{
				prompt.animate(css);
			}
		},

		/**
		* Closes the prompt associated with the given field
		*
		* @param	jqObject		Field
		*/
		closePrompt: function($field)
		{
			// Catch prompt
			var
				prompt = privateMethods.getPrompt($field),
				$form = $field.closest('form'),
				options = $form.data('form-validation-options');

			// Hide prompt if it exists
			if (prompt)
			{
				prompt.fadeTo('fast', 0, function() {
					// Remove prompt wrapper if need
					if (options.wrapper !== '')
					{
						prompt.parent().remove();
					}
					prompt.remove();
				});
			}
		},

		/***********************************************************************
		* Validation methods
		***********************************************************************/
		/**
		* Validates field, shows prompts accordingly
		*
		* @param	jqObject		Field
		* @param	array[string]	Field's validation rules
		* @param	map				User options
		* @return 	bool			True if field is valid and false at another case
		*/
		validateField: function($field, options, skipAjaxValidation)
		{
			// Append field ID, because we need it for next actions
			if ($field.attr('id') === undefined)
			{
				$field.attr('id', 'form-validation-field-' + $.validationEngine.fieldIdCounter);
				++$.validationEngine.fieldIdCounter;
			}

			// Catch validation rules
			var
				getRules = /validate\[(.*)\]/.exec($field.attr('class'));

			// Field is valid if here is no validator
			if (getRules === null)
			{
				return false;
			}

			// Prepare additional variables
			var
				$form = $field.closest('form'),

				str = getRules[1],
//@todo
// Patch by KrasniyRus
//				rules = str.split(/\[|,|\]/),
				rules = str.split(/\[|,\s*|\]/),

				// True if we ran the ajax validation, tells the logic to stop messing with prompts
				isAjaxValidator = false,
				fieldName = $field.attr('name'),
				promptText = '',
				required = false,

				errorMsg = false,

				i = 0,
				il = rules.length;

			options.isError = false;
			options.showArrow = true;

			// Validate by all rules
			for (; i < il; ++i)
			{
				errorMsg = false;

//@todo rework this patch (another addition for patch by KrasniyRus
//				// Fix for adding spaces in the rules
//				rules[i] = rules[i].replace(" ", "")

				switch (rules[i])
				{
					case 'required':
						required = true;
						errorMsg = validatorsMethods.required($field, rules, i, options);
					break;

					case 'groupRequired':
						// Check is its the first of group, if not, reload validation with first field
						// AND continue normal validation on present field
						var
							classGroup = '[class*="' + rules[i + 1] + '"]',
							firstOfGroup = $form.find(classGroup).eq(0);

						if (firstOfGroup[0] != $field[0])
						{
							privateMethods.validateField(firstOfGroup, options, skipAjaxValidation)
							options.showArrow = true;

							continue;
						}

						errorMsg = validatorsMethods.groupRequired($field, rules, i, options);

						if (errorMsg !== false)
						{
							required = true;
						}

						options.showArrow = false;
					break;

					case 'ajax':
						// AJAX has its own prompts handling technique
						if (skipAjaxValidation !== true)
						{
							validatorsMethods.ajax($field, rules, i, options);
							isAjaxValidator = true;
						}
					break;

					case 'custom':
					case 'minSize':
					case 'maxSize':
					case 'min':
					case 'max':
					case 'past':
					case 'future':
					case 'equals':
					case 'funcCall':
						errorMsg = validatorsMethods[rules[i]]($field, rules, i, options);
					break;

					case 'dateRange':
					case 'dateTimeRange':
						var
							classGroup = '[class*="' + rules[i + 1] + '"]',
							firstOfGroup = $form.find(classGroup).eq(0),
							secondOfGroup = $form.find(classGroup).eq(1);

/*
						if (firstOfGroup[0] != $field[0])
						{
							privateMethods.validateField(firstOfGroup, options, skipAjaxValidation);
							options.showArrow = true;

							continue;
						};
*/

						// If one entry out of the pair has value then proceed to run through validation
						if (firstOfGroup[0].value || secondOfGroup[0].value)
						{
							errorMsg = validatorsMethods[rules[i]](firstOfGroup, secondOfGroup, rules, i, options);
						}

						if (errorMsg !== false)
						{
							required = true;
						}

						options.showArrow = false;
					break;

					case 'maxCheckbox':
					case 'minCheckbox':
						errorMsg = validatorsMethods[rules[i]]($form, $field, rules, i, options);

						$field = $('input[name="' + fieldName + '"]');
					break;

					default:
						//$.error("jQueryValidator rule not found"+rules[i]);
				}

				if (errorMsg !== false)
				{
					promptText += errorMsg + '<br />';
					options.isError = true;

					// Don't check this field anymore if we shows only first alert
					if (options.showFirstAlertForFieldOnly === true)
					{
						break;
					}
				}
			}

			// If the rules required is not added, an empty field is not validated
			if (required === false && $field.val() === '')
			{
				options.isError = false;
			}

			// Hack for radio/checkbox group button, the validation go into the
			// first radio/checkbox of the group
			var
				fieldType = $field.prop('type');

			if ((fieldType === 'radio' || fieldType === 'checkbox') && $form.find('input[name="' + fieldName + '"]').size() > 1)
			{
				$field = $form.find('input[name="' + fieldName + '"][type!="hidden"]:first');
				options.showArrow = false;
			}
			else if (fieldType === 'text' && $form.find('input[name="' + fieldName + '"]').size() > 1)
			{
				$field = $form.find('input[name="' + fieldName + '"][type!=hidden]:first');
				options.showArrow = false;
			}

			if (options.isError)
			{
				privateMethods.showPrompt($field, promptText, '', false, options);
			}
			else if (!isAjaxValidator)
			{
				 privateMethods.closePrompt($field);
			}

			if (!isAjaxValidator)
			{
				$field.trigger("jqv.field.result", [$field, options.isError, promptText]);
			}

			$field.trigger('jqv.field.result', [$field, options.isError, promptText]);

//@todo optimize callback
			// Store error
			var errindex = $.inArray($field[0], options.InvalidFields);
			if (errindex == -1)
			{
				if (options.isError)
				{
					options.InvalidFields.push($field[0]);
				}
			}
			else if (!options.isError)
			{
				options.InvalidFields.splice(errindex, 1);
			}

			return options.isError;
		},

		/**
		* Validates form fields, shows prompts accordingly
		*
		* @param	jqObject		Form
		* @param	bool			When set to true, ajax field validation is skipped,
		* 							typically used when the submit button is clicked
		*
		* @return	bool|undefined	True if form is valid, false if not,
		* 							undefined if ajax form validation is done
		*/
//@todo comment this function
		validateFields: function($form, skipAjaxValidation)
		{
			var
				options = $form.data('form-validation-options'),

				// This variable is set to true if an error is found
				errorFound = false,

				// First error field handler
				$firstErrorField = null;

			// Trigger hook, start validation
			$form.trigger('jqv.form.validating');

			// First, evaluate status of non ajax fields
			$form.find('[class*="validate"]')
				.not(':hidden')
				.not(':disabled')
				.each(
					function()
					{
						// $(this) is equals to field
						errorFound |= privateMethods.validateField($(this), options, skipAjaxValidation);

						// Store first error field
						if (errorFound && $firstErrorField === null)
						{
							$firstErrorField = $(this);
						}

						// Show only first error
						if (options.doNotShowAllErrorsOnSubmit === true)
						{
							return false;
						}
					}
				);

			// Second, check to see if all ajax calls completed ok
			// errorFound |= !privateMethods.checkAjaxStatus(options);

			// Third, check status and scroll the container accordingly
			$form.trigger('jqv.form.result', [errorFound]);

			if (errorFound)
			{
				if (options.scroll)
				{
					var
						offsetTop = $firstErrorField.offset().top,
						offsetLeft = $firstErrorField.offset().left,

						// Prompt positioning adjustment support
						// Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10)
						positionType = options.promptPosition;

					// Prompt positioning adjustment support
					if (typeof(positionType) === 'string')
					{
						if (positionType.indexOf(':') !== -1)
						{
							positionType = positionType.substring(0, positionType.indexOf(':'));
						}
					}

					if (options.promptPosition !== 'bottomRight' && options.promptPosition !== 'bottomLeft')
					{
						var
							$errorPrompt = methods._getPrompt($firstErrorField);

							offsetTop = $errorPrompt.offset().top;
					}

					// Move to first error field
					$('html:not(:animated), body:not(:animated)')
						.animate(
							{
								scrollTop: offsetTop,
								scrollLeft: offsetLeft
							},
							1100,
							function()
							{
								if (options.focusFirstField)
								{
									$firstErrorField.focus();
								}
							}
						);

					if (options.isOverflown)
					{
						var
							$overflowDiv = $(options.overflownDiv),
							scrollContainerScroll = $overflowDiv.scrollTop(),
							scrollContainerPos = -parseInt($overflowDiv.offset().top),
							$scrollContainer = $(options.overflownDiv + ':not(:animated)');

						offsetTop += scrollContainerScroll + scrollContainerPos - 5;

						$scrollContainer
							.animate(
								{
									scrollTop: offsetTop
								},
								1100
							);
					}
				}
				else
				{
					if (options.focusFirstField)
					{
						$firstErrorField.focus();
					}
				}

				return false;
			}

			return true;
		},

		/***********************************************************************
		* AJAX methods
		***********************************************************************/
		/**
		* This method is called to perform an ajax form validation.
		* During this process all the (field, value) pairs are sent to the server which returns a list of invalid fields or true
		*
		* @param	jqObject		Form
		* @param	map				Options
		*/
		validateFormWithAjax: function(form, options)
		{
			// Prepare variables
			var
				data = form.serialize(),
				url = (options.ajaxFormValidationURL) ? options.ajaxFormValidationURL : form.attr('action');

			$.ajax({
				type: 'GET',
				url: url,
				cache: false,
				dataType: 'json',
				data: data,
				form: form,
				methods: methods,
				options: options,
				beforeSend: function()
				{
					return options.onBeforeAjaxFormValidation(form, options);
				},
				error: function(data, transport)
				{
					privateMethods.ajaxError(data, transport);
				},
				success: function(json)
				{
					if (json !== true)
					{
						/**
						* Getting to this case doesn't necessary means that the form is invalid
						* the server may return green or closing prompt actions
						* this flag helps figuring it out
						*/
						var
							errorInForm = false,

							value = '',
							errorFieldId = '',
							errorField = null,
							msg = '',
							text = '',

							i = 0,
							il = json.length;

						for (; i < il; ++i)
						{
							value = json[i];

							errorFieldId = value[0];
							errorField = $($('#' + errorFieldId)[0]);

							// Make sure we found the element
							if (errorField.length === 1)
							{
								// Prompt text or selector
								msg = value[2];

								// If the field is valid
								if (value[1] === true)
								{
									if (msg === '' || !msg)
									{
										// If for some reason, status==true and error='', just close the prompt
										privateMethods.closePrompt(errorField);
									}
									else
									{
										// The field is valid, but we are displaying a green prompt
										if (options.allrules[msg])
										{
											txt = options.allrules[msg].alertTextOk;

											if (txt)
											{
												msg = txt;
											}
										}
										privateMethods.showPrompt(errorField, msg, 'pass', false, options, true);
									}
								}
								else
								{
									// The field is invalid, show the red error prompt
									errorInForm |= true;

									if (options.allrules[msg])
									{
										txt = options.allrules[msg].alertText;

										if (txt)
										{
											msg = txt;
										}
									}

									privateMethods.showPrompt(errorField, msg, '', false, options, true);
								}
							}
						}
						options.onAjaxFormComplete(!errorInForm, form, json, options);
					}
					else
					{
						options.onAjaxFormComplete(true, form, '', options);
					}
				}
			});
		},

		/**
		* Return true if the ajax field validations passed so far
		* @param {Object} options
		* @return true, is all ajax validation passed so far (remember ajax is async)
		*/
		checkAjaxStatus: function(options)
		{
			var
				status = true;

			$.each(options.ajaxValidCache, function(key, value)
			{
				if (!value)
				{
					status = false;

					// break the each
					return false;
				}
			});

			return status;
		},

		/**
		* Common method to handle ajax errors
		*
		* @param	object		Data
		* @param	object		Transport
		*/
		ajaxError: function(data, transport)
		{
			if (data.status === 0 && transport === null)
			{
//@todo localizable error string
				alert('The page is not served from a server! ajax call failed');
			}
			else if(typeof console !== 'undefined')
			{
//@todo localizable error string
				console.log('Ajax error: ' + data.status + ' ' + transport);
			}
		},

		/***********************************************************************
		* Date time methods
		***********************************************************************/
		/**
		* Date to string conversion
		*
		* @param	Object		Date
		*/
		dateToString: function(date)
		{
			return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
		},

		/**
		* Parses an ISO date
		*
		* @param	string		Date
		* @return	date object
		*/
		parseDate: function(date)
		{
//@todo optimize
			var
				dateParts = date.split('-');

			if (dateParts === date)
			{
				dateParts = date.split('/');
			}

			return new Date(dateParts[0], (dateParts[1] - 1) ,dateParts[2]);
		},

		/***********************************************************************
		* Additional methods
		***********************************************************************/
		/**
		* Returns the escapade classname
		*
		* @param	string		Selector's class name
		*/
		escapeExpression: function(selector)
		{
			return selector.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g, "\\$1");
		},

		/**
		* Removes forbidden characters from class name
		*
		* @param	string		Class name
		*/
		getClassName: function(className)
		{
			className = className || '';
			return className.replace(/:/g, '_').replace(/\./g, '_');
		},

		/**
		* Return function from its name.
		* Capable of handling functions with namespaces
		*
		* @param	string		functionName
		* @return	function
		*/
		getFunctionByName: function(functionName)
		{
			// Prepare variables
			var
				namespaces	= functionName.split('.'),
				func		= namespaces.pop(),
				context		= window,

				i	= 0,
				il	= namespaces.length;

			// Catch result context
			for (; i < il; ++i)
			{
//@todo error checked
				context = context[namespaces[i]];
			}

			return context[func];
		}
	};

	/**
	* Events methods
	*/
	var eventsMethods =
	{
		/**
		* Typically called when user exists a field using tab or a mouse click, triggers a field
		* validation
		*/
		onFieldEvent: function(event)
		{
			var
				$field = $(this),
				form = $field.closest('form'),
				options = form.data('form-validation-options');

			// Validate the current field
			window.setTimeout(function()
			{
				privateMethods.validateField($field, options);
//@todo separate form and field onsuccess/onfailure events
				// Call onSuccess/onFailure callback
				if (options.onSuccess !== null && options.InvalidFields.length === 0)
				{
					options.onSuccess();
				}
				else if (options.onFailure !== null && options.InvalidFields.length !== 0)
				{
					options.onFailure();
				}
			}, (event.data) ? event.data.delay : 0);

		},

		/**
		* Called when the form is submited, shows prompts accordingly
		*
		* @param	jqObject		Form
		* @return	bool			false if form submission needs to be cancelled
		*/
		onSubmitEvent: function()
		{
			var
				form = $(this),
				options = form.data('form-validation-options'),
				// validate each field (- skip field ajax validation, no necessary since we will perform an ajax form validation)
				result = privateMethods.validateFields(form, true);

			if (result === true && options.ajaxFormValidation === true)
			{
				privateMethods.validateFormWithAjax(form, options);

				return false;
			}
			else if (options.onValidationComplete !== null)
			{
				options.onValidationComplete(form, result);

				return false;
			}

			return result;
		}
	};

	/**
	* Public methods
	*/
	var publicMethods =
	{
		/***********************************************************************
		* Core methods
		***********************************************************************/
		/**
		* Kind of the constructor, called before any action
		*
		* @param	Map		User options
		*/
		init: function(options)
		{
			// Prepare variables
			var
				$form = this;

			// Initialize only if it wasn't before
			if ($form.data('form-validation') === undefined || $form.data('form-validation') === false)
			{
				privateMethods.saveOptions($form, options);

				// Bind all formError elements to close on click
				$('.formError').live('click', function()
				{
					$(this).fadeOut(150, function()
					{
						// Remove prompt once invisible
						$(this).remove();
					});
				});
			}

			return this;
		},

		/**
		* Attachs jQuery.validationEngine to form.submit and field.blur events
		* Takes an optional params: a list of options
		* ie. jQuery("#formID1").validationEngine('attach', {promptPosition : "centerRight"});
		*
		* @param	Map		User options
		* @return	jqObject
		* 					Form object
		*/
		attach: function(userOptions)
		{
			// Prepare variables
			var
				$form = this;

			// Don't do anything if form already binded
			if ($form.data('form-validation-bind') === true)
			{
				return $form;
			}

			// Prepare variables
			var
				options = (userOptions) ? privateMethods.saveOptions($form, userOptions) : $form.data('form-validation-options'),
				validateAttribute = ($form.find('[data-validation-engine*="validate"]')) ? 'data-validation-engine' : 'class'

			if (options.bindMethod === 'bind')
			{
				// Bind fields
				$form.find('[class*="validate"]').not('[type="checkbox"]').not('[type="radio"]').not('.datepicker').bind(options.validationEventTrigger, eventsMethods.onFieldEvent);
				$form.find('[class*="validate"][type="checkbox"], [class*="validate"][type="radio"]').bind('click', eventsMethods.onFieldEvent);

				$form.find('[class*="validate"][class*="datepicker"]').bind(options.validationEventTrigger, {'delay': 300}, eventsMethods.onFieldEvent);

				// Bind $form.submit
				$form.bind('submit', eventsMethods.onSubmitEvent);
			}
			else if (options.bindMethod === 'live')
			{
				// Bind fields with LIVE (for persistant state)
				$form.find('[class*="validate"]').not('[type="checkbox"]').not('.datepicker').live(options.validationEventTrigger, eventsMethods.onFieldEvent);
				$form.find('[class*="validate"][type="checkbox"]').live('click', eventsMethods.onFieldEvent);

				$form.find('[class*="validate"][class*="datepicker"]').live(options.validationEventTrigger, {'delay': 300}, eventsMethods.onFieldEvent);

				// Bind $form.submit
				$form.live('submit', eventsMethods.onSubmitEvent);
			}

			$form.data('form-validation-bind', true);

			if (options.autoPositionUpdate === true)
			{
				$(window)
					.bind(
						'resize',
						{
							noAnimation: true,
							formElem: $form
						},
						methods.updatePromptsPosition
					);
			}

			return $form;
		},

		/**
		* Unregisters any bindings that may point to jQuery.validaitonEngine
		*/
		detach: function()
		{
			// Prepare variables
			var
				$form = this;

			// Don't do anything if form already destructed
			if ($form.data('form-validation-bind') === null || $form.data('form-validation-bind') === false)
			{
				return $form;
			}

			// Prepare variables
			var
				options = form.data('form-validation-options');

			// Unbind fields
			$form.find('[class*="validate"]').not('[type="checkbox"]').unbind(options.validationEventTrigger, eventsMethods.onFieldEvent);
			$form.find('[class*="validate"][type="checkbox"], [class*="validate"][type="radio"]').unbind('click', eventsMethods.onFieldEvent);

			// Unbind $form.submit
			$form.unbind('submit', options.onAjaxFormComplete);

			// Unbind live fields (kill)
			$form.find('[class*="validate"]"').not('[type="checkbox"]').die(options.validationEventTrigger, eventsMethods.onFieldEvent);
			$form.find('[class*="validate"][type="checkbox"]').die('click', eventsMethods.onFieldEvent);

			// Unbind $form.submit
			$form.die('submit', options.onAjaxFormComplete);

			// Remove data
			$form.removeData('form-validation');
			$form.removeData('form-validation-options');

			if (options.autoPositionUpdate === true)
			{
				$(window).unbind('resize', methods.updatePromptsPosition)
			}

			return $form;
		},

		/***********************************************************************
		* UI methods
		***********************************************************************/
		/**
		*  Redraw prompts position, useful when you change the DOM state when validating
		*/
		updatePromptsPosition: function(event)
		{
			var
				$form = (event !== undefined && this === window) ? event.data.formElem : this.closest('form'),
				noAnimation = (event !== undefined && this === window) ? event.data.noAnimation : false,
				options = $form.data('form-validation-options');

			// No option, take default one
			$form.find('[class*="validate"]').not(':hidden').not(':disabled').each(function()
			{
				var
					$field = $(this),
					prompt = privateMethods.getPrompt($field),
					promptText = $(prompt).find('.formErrorContent').html();

				if (prompt)
				{
//@todo $(prompt)???
					privateMethods.updatePrompt($field, $(prompt), promptText, undefined, false, options, noAnimation);
				}
			});

			return this;
		},

		/**
		* Displays a prompt on a element.
		* Note that the element needs an id!
		*
		* @param	String		HTML text to display type
		* @param	String		The type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param	String		Possible values: topLeft, topRight, bottomLeft, centerLeft, centerRight, bottomRight
		*/
		showPrompt: function(promptText, type, promptPosition, showArrow)
		{
			var
				form = this.closest('form'),
				options = form.data('form-validation-options');

			// No option, take default one
			if (!options)
			{
				options = privateMethods.saveOptions(this, options);
			}

			if (promptPosition)
			{
				options.promptPosition = promptPosition;
			}

			options.showArrow = (showArrow == true);

			privateMethods.showPrompt(this, promptText, type, false, options);

			return this;
		},

		/**
		* Closes all error prompts on the page
		*/
		hidePrompt: function(fade)
		{
			var
				promptClass = '.' + privateMethods.getClassName($(this).attr('id')) + 'formError';

			if (fade === true)
			{
				$(promptClass).fadeTo('fast', 0.3, function()
				{
					$(this).remove();
				});
			}
			else
			{
				$(promptClass).remove();
			}

			return this;
		},

		/**
		* Closes form error prompts, CAN be invidual
		*/
		hide: function(fade)
		{
			var
				closingtag = '';

			if ($(this).is('form') === true)
			{
				closingtag = 'parentForm' + $(this).attr('id');
			}
			else
			{
				closingtag = $(this).attr('id') + 'formError';
			}

			if (fade === true)
			{
				$('.' + closingtag).fadeTo('fast', 0.3, function() {
					$(this).remove();
				});
			}
			else
			{
				$('.' + closingtag).remove();
			}

			return this;
		},

		/**
		* Closes all error prompts on the page
		*/
		hideAll: function(fade)
		{
			if (fade === true)
			{
				$('.formError').fadeTo('fast', 0.3, function()
				{
					$(this).remove();
				});
			}
			else
			{
				$('.formError').remove();
			}

			return this;
		},

		/**
		* Close prompt for specified field
		*
		* @param	jqObject		Field
		*/
		closePrompt: function($field)
		{
			return privateMethods.closePrompt($field);
		},

		/***********************************************************************
		* Validation methods
		***********************************************************************/
		/**
		* Validates one field, shows prompt accordingly.
		* Note: There is no ajax form validation with this method, only field ajax validation are evaluated
		*
		* @param	jQobject	Element
		* @return	bool		true if the form validates and false at another case
		*/
		validateField: function(el)
		{
			var
				result = privateMethods.validateField($(el), $(this).data('form-validation-options'));

//@todo separate form and field onsuccess/onfailure events
			if (options.onSuccess !== null && options.InvalidFields.length === 0)
			{
				options.onSuccess();
			}
			else if (options.onFailure !== null && options.InvalidFields.length !== 0)
			{
				options.onFailure();
			}

			return result;
		},

		/**
		* Validates the form fields, shows prompts accordingly.
		* Note: There is no ajax form validation with this method, only field ajax validation are evaluated
		*
		* @return	bool		true if the form validates and false at another case
		*/
		validate: function()
		{
			return privateMethods.validateFields(this);
		},

		/**
		* Validates the form fields, shows prompts accordingly.
		* Note: this methods performs fields and form ajax validations(if setup)
		*
		* @return	bool|undefined
		* 						true if the form validates and false at another case, undefined if ajax is used for form validation
		*/
//@todo do we really need this?
		validateForm: function()
		{
			return eventsMethods.onSubmitEvent.call(this);
		}
	};

	/**
	* Plugin entry point.
	* You may pass an action as a parameter or a list of options.
	* if none, the init and attach methods are being called.
	* Remember: if you pass options, the attached method is NOT called automatically
	*
	* @param {String} method (optional) action
	*/
//@todo optimize
	$.fn.validationEngine = function(method)
	{
		var
			$form = $(this);

		// Stop here if the form does not exist
		if ($form.length === 0)
		{
			return false;
		}

		if (typeof(method) === 'string' && publicMethods[method] !== undefined)
		{
			// Make sure init is called once
//@todo optimize
			if ($form.data('form-validation') === undefined || $form.data('form-validation') === false)
			{
				publicMethods.init.apply($form);
			}

			return publicMethods[method].apply($form, Array.prototype.slice.call(arguments, 1));
		}
		else if (typeof method === 'object' || method === undefined)
		{
//@todo optimize
			// Default constructor with or without arguments
			publicMethods.init.apply($form, arguments);

			return publicMethods.attach.apply($form);
		}
		else
		{
			$.error('Method ' + method + ' does not exist in jQuery.validationEngine');
		}
	};

	/**
	* Store default options
	*/
	$.validationEngine =
	{
		defaults:
		{
			// Name of the event triggering field validation
			validationEventTrigger: 'blur',
			bindMethod: 'bind',

			// Automatically scroll viewport to the first error
			scroll: true,
			// Focus on the first error field
			focusFirstField: true,
			// Opening box position, possible locations are: topLeft,
			// topRight, bottomLeft, centerLeft, centerRight, bottomRight
			promptPosition: 'topRight',
			// Wrapper element
			wrapper: '',

			// Option to only show the first alert text when using multiple validators.
			showFirstAlertForFieldOnly: false,
			// Used when you have a form fields too close and the errors messages are on top of other disturbing viewing messages
			doNotShowAllErrorsOnSubmit: false,

			// Used when the form is displayed within a scrolling DIV
			isOverflown: false,
			overflownDiv: '',

			// internal, automatically set to true when it parse a _ajax rule
			inlineAjax: false,
			// if set to true, the form data is sent asynchronously via ajax to the form.action url (get)
			ajaxFormValidation: false,
			// Ajax form validation callback method: boolean onComplete(form, status, errors, options)
			// retuns false if the form.submit event needs to be canceled.
			ajaxFormValidationURL: false,
			// The url to send the submit ajax validation (default to action)
			onAjaxFormComplete: $.noop,
			// called right before the ajax call, may return false to cancel
			onBeforeAjaxFormValidation: $.noop,

			// Stops form from submitting and execute function assiciated with it
			onValidationComplete: null,

			// true when form and fields are binded
			binded: false,
			// set to true, when the prompt arrow needs to be displayed
			showArrow: true,
			 // Auto update prompt position after window resize
			autoPositionUpdate: false,
			// did one of the validation fail ? kept global to stop further ajax validations
			isError: false,
			// Caches field validation status, typically only bad status are created.
			// the array is used during ajax form validation to detect issues early and prevent an expensive submit
			ajaxValidCache: {},
			InvalidFields: [],
			onSuccess: null,
			onFailure: null
		},

		fieldIdCounter: 0
	};
})(jQuery);

