/*!
 * jQValidate - Versatile form validation plugin for jQuery/jQuery UI
 *
 * Author : Jake Wharton <jakewharton@gmail.com>
 * Website: http://jakewharton.com/
 * Version: 1.0.0
 * License: Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Heavily inspired by and based off of Felix Nagel's jQuery-Accessible-RIA.
 */

 /*global jQuery: false, clearTimeout: false, setTimeout: false */
'use strict';

jQuery(function($) {
	$.widget('ui.validate', {
		_init: function(options) {
			var self = this;
			//Store defaults and instance settings
			self.options = $.extend(self.options, options);

			//Perform initial setup of validated elements
			$.each(self.options.selectors, function(selector) {
				//Create data storage array for matches to current selector
				self.options.selectors[selector].matches = [];
				//Save selector inside the selector data for easy reference
				self.options.selectors[selector].selector = selector;

				//Find and iterate over the selector's matching form elements
				self.element.find(selector).each(function(matchIndex) {
					//Get current form element match for the selector
					var match = $(this), type;

					if (match.is('input:radio,input:checkbox')) {
						type = 'group';
					} else if (match.is('select')) {
						type = 'select';
					} else {
						type = 'text';
					}

					self.options.selectors[selector].matches[matchIndex] = {
						'element': match,
						'errors': [],
						'invalid': false,
						'type': type
					};

					//Bind events
					match.bind((type === 'group' || type === 'select') ? 'change' : 'keyup', function() {
						if (self.options.enabled) {
							//Check if another event is has fired but is waiting for another event (us)
							if (self.options.selectors[selector].matches[matchIndex].timeout) {
								clearTimeout(self.options.selectors[selector].matches[matchIndex].timeout);
							}
							//Set a timeout to validate this match after the set timeout length
							self.options.selectors[selector].matches[matchIndex].timeout = setTimeout(function() {
								//Validate the element allowing it to be empty since it is still focused
								self._validate(self.options.selectors[selector], self.options.selectors[selector].matches[matchIndex], true);
							}, self.options.timeout);
						}
					});
					match.bind('blur', function() {
						if (self.options.enabled) {
							//Check if another event is has fired but is waiting for another event (us)
							if (self.options.selectors[selector].matches[matchIndex].timeout) {
								clearTimeout(self.options.selectors[selector].matches[matchIndex].timeout);
							}
							//Validate instantly disallowing it to be empty
							self._validate(self.options.selectors[selector], self.options.selectors[selector].matches[matchIndex]);
						}
					});
				});
			});

			//Initialization callback
			self._trigger('onInit');
		},

		_validate: function(selector, match, allowEmpty) {
			var self = this, valid = true, allValid, value = self._getValue(selector, match), errors = [];

			$.each(selector.rules, function(rule, ruleValue) {
				try {
					if (!self[rule](value, ruleValue, match)) {
						match.errors[rule] = true;
						valid = false;
						errors.push(selector);
					}
				}
				catch (e) {
					//Warn the user (hopefully a developer) about the invalid rule
					alert('Validation rule name does not exist.\n\nSelector: "' + selector.selector + '"\nRule Name: "' + rule + '"\nRule Value: "' + ruleValue + '"\n\nCheck the documentation at http://github.com/JakeWharton/jQValidate/');
					//Delete invalid rule so we do not trigger the error again
					delete selector.rules[rule];
				}
			});

			if (valid || (allowEmpty && value === '')) {
				if (match.invalid) {
					//if there is a valid selector-level callback then trigger it
					if (selector.onValid) {
						selector.onValid(match.element);
					}
					//if there is a valid options-level callback then trigger it
					if (self.options.onValid) {
						self.options.onValid(match.element);
					}
					//if a class was specified to indicate error then remove it
					if (self.options.invalidClass) {
						if (match.type === 'group') {
							if (selector.indicator) {
								self.element.find(selector.indicator).removeClass(self.options.invalidClass);
							}
							//update invalid state of all the matches for the selector
							$.each(selector.matches, function() { this.invalid = false; });
						} else {
							match.element.removeClass(self.options.invalidClass);
						}
					}
					//update invalid state so these are only called once
					match.invalid = false;
				}
				//If we are currently invalid and there is a valid callback check to see if everything is now valid
				if (self.options.invalid && self.options.onIsValid) {
					allValid = true;
					//Iterate over all the selectors
					$.each(self.options.selectors, function(selectorIndex, selector) {
						//Iterate over all of the selector's matches
						$.each(selector.matches, function(matchIndex) {
							if (this.invalid) {
								allValid = false;
								//Stops match iteration
								return false;
							}
						});
						//Stop selector iteration if we are already invalid
						if (!allValid) {
							return false;
						}
					});
					if (allValid) {
						self.options.invalid = false;
						self.onIsValid();
					}
				}
			} else { //if (!valid) {
				if (!match.invalid) {
					//if there is an invalid selector-level callback then trigger it
					if (selector.onInvalid) {
						selector.onInvalid(match.element, errors);
					}
					//if there is an invalid options-level callback then trigger it
					if (self.options.onInvalid) {
						self.options.onInvalid(match.element, errors);
					}
					//if a class is specified to indicate error then add it
					if (self.options.invalidClass) {
						if (match.type === 'group') {
							if (selector.indicator) {
								self.element.find(selector.indicator).addClass(self.options.invalidClass);
							}
							//update invalid state of all the matches for the selector
							$.each(selector.matches, function() { this.invalid = true; });
						} else {
							match.element.addClass(self.options.invalidClass);
						}
					}
					//update invalid state so these are only called once
					match.invalid = true;
				}
				//If we are currently valid and there is an invalid callback then trigger it
				if (!self.options.invalid && self.options.onIsInvalid) {
					self.options.invalid = true;
					self.onIsInvalid();
				}
			}

			return valid;
		},

		_getValue: function(selector, match) {
			var results;
			switch (match.type) {
				case 'text':
					return match.element.val();
				case 'select':
					results = match.element.filter('option:selected');
					return results.length ? results[0].val() : '';
				case 'group':
					return $.grep(selector.matches, function(match) { return match.element.is(':checked'); }).length;
				default:
					return '';
			}
		},

		validate: function() {
			var self = this, allValid = true, selector;
			//Iterate over all the selectors
			$.each(self.options.selectors, function() {
				selector = this;
				//Iterate over all of the selector's matches
				$.each(selector.matches, function() {
					//Validate the match
					if (!self._validate(selector, this, true)) {
						allValid = false;
					}
				});
			});

			//If the form is valid trigger an overall valid callback manually
			if (allValid && self.onIsValid) {
				self.onIsValid();
			}

			return allValid;
		},

		//Validation methods (NOTE: These return true if condition is met)
		required: function(value, enabled) { return (enabled && (value !== '' || value > 0)); },
		minlength: function(value, length) { return (value === '') || (value.length >= length); },
		maxlength: function(value, length) { return (value === '') || (value.length <= length); },
		equals: function(value, desired) { return (value === '') || (value === desired); },
		mirror: function(value, selector, match) { return this.equals(value, this._getValue(this.element.find(selector), match.type)); },
		regex: function(value, regex) { return (value === '') || (new RegExp(regex)).test(value); },
		isint: function(value, enabled) { return (value === '') || (/^-?\d+$/).test(value); },
		isuint: function(value, enabled) { return (value === '') || (/^\d+$/).test(value); },
		isfloat: function(value, enabled) { return (value === '') || (/^-?\d+(\.\d+)?$/).test(value); },
		isufloat: function(value, enabled) { return (value === '') || (/^\d+(\.\d+)?$/).test(value); },
		minchecked: function(value, number, match) { return (value === 0) || (value >= number); },
		maxchecked: function(value, number, match) { return (value === 0) || (value <= number); },
		email: function(value, enabled) { return (value === '') || (/^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$/).test(value); },
		url: function(value, enabled) { return (value === '') || (/^(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).test(value); }
	});

	$.ui.validate.defaults = {
		enabled: true,
		realtime: true,
		invalidClass: 'ui-state-error',
		timeout: 250,
		invalid: false
	};
	$.ui.validate.version = '1.0.0a';
});

/*jslint devel: true, onevar: true, undef: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true */