/**
 * validate.js
 *
 * @author Justen Robertson
 * @version 0.6b
 *
 * A jQuery extension for easy form validation. There are three parts to the code:
 * $.fn.validate(), which is used to extend a form for validation, $.validator
 * which holds a repository of validation objects, and the validation objects
 * themselves which are registered via
 *
 * $.validator.registerValidator(properties);
 *
 *
 *
 *
 * Basic Use
 *
 * $('form').validate();
 *
 * Apply classes to your form fields as you wish. A validation check gets run
 * each time a validator field is blurred (clicked away from), as well as when
 * the form is submitted.
 *
 * Current available classes include:
 *
 * vaild-alphabetical: only letters
 * valid-alpha-spaces: only letters and spaces
 * valid-alpha-numeric: only letters and numbers
 * valid-alpha-num-spaces: only letters, numbers, and spaces
 * valid-verify-password: must be identical to the "validate-password" field
 * valid-integer: must be an integer (whole number) (note, does not validate byte size)
 * valid-float: must be a floating point (decimal) number (note, does not validate byte size)
 * valid-ip-address: must be a valid ip address format (does not do sanity checking)
 * valid-us-phone: must be a U.S. phone number, format (123) 456-7890
 * valid-street-address: tries to validate against all common street address formats (take it or leave it)
 * valid-us-currency: a dollar amount
 * valid-na-postal: valid U.S. / Canada postal code
 * valid-url: validates urls according to the official spec
 * valid-email-address: validates email addresses according to the official spec
 * valid-minimum: mjust meet a min char length according to a rel="minimum:x" attribute
 * valid-select-option: selected option must not have an empty value
 *
 *
 *
 * To create your own validator, use:
 *
 * var properties = {
 *	classname:, // the class name for a form field to bind to
 *	regexp:, // a regular expression string to validate for
 *	helpText:, // help text when the string is invalid
 *	callback:, // a callback function for additional logic
 *	}
 *
 * $.validator.registerValidator(properties);
 *
 *	You must have either a regexp or a callback, but you need not supply both.
 *	The callback function accepts a reference to the field to be validated and
 *	returns a boolean - true if validation was successful, false if not.
 *
 */

(function($) {
	function Validator() {}
	$.fn.validate = function(settings) {
		settings = $.extend({},settings);
		var self = this;
		
		this.onSuccess = function(){return true;}

		this.onFailure = function(){return true;}
		
		this._bind = function() {
			this._unbind();
			var items = $(self.selector+' input,'+self.selector+' select');
			items.each(function(i) {
				if ($(this).attr('type') == 'hidden') return; // Don't validate hidden attributes (the backend should, though)
				$(this).bind('blur', function(event) {$.validator._check($(event.target))});
			});
			this.bind('submit', function(event) {self._submit(event);});
			return this;
		}




		this._unbind = function() {
			var items = $(self.selector+' input, '+self.selector+', select');
			items.unbind('blur', function(event) {$.validator._check($(event.target))});
			return this;
		}




		this._submit = function(event){
			var items = self.selector+' input, '+self.selector+' select';
			$(items).each(function() { // Should always be inputs in a form, but what the hell
				if (this.type == 'hidden') return;// Don't validate hidden attributes (the model will though)
				if (this.type == 'button') return;
				if (this.type == 'checkbox') return;
				if (this.type == 'radio') return;
				$.validator._check(this);
			});

			if ($(self.selector+' .validate-invalid')) {
				event.preventDefault();
				this.onFailure();
				alert('You have submitted invalid information! Please review the form and try again.');
				return this;
			}
			else {
				this.onSuccess();
				return this;
			}
		}




		/**
		 * Just a shortcut for chaining custom validators. Note that this
		 * *does* go into the global validator set, so it will apply to other forms
		 * that may be on page.
		 */

		this.registerValidator = function(properties) {
			$.validator.registerValidator(properties);
			return this;
		}

		return this._bind();
	}


	
		
	Validator.prototype = {
		validators:{},
		



		registerValidator:function(properties) {
			if(!properties.classname) return this;
			this.validators[properties.classname] = {
				helpText:properties.helpText?properties.helpText:"Invalid entry.",
				regexp:properties.regexp?properties.regexp:/.*/,
				callback:properties.callback?properties.callback:function(){return true;}
			}
			return this;
		},




		_check:function(item) {
			item = $(item);
			var self = this;
			if (!item.hasClass('validate-required') && item.value == '') return true;
			for(var classname in self.validators) {
				var ret = false;
				if (item.hasClass(classname)) {
					if(!item.attr('value').match(this.validators[classname].regexp) || !this.validators[classname].callback(item)) this.markInvalid(item, this.validators[classname]);
					else this.markValid(item);
				}
			}
			return this;
		},




		markInvalid:function(item, validator) {
			item = $(item);
			$('[name='+item.attr('name')+'] + span.validate-help-text').remove();
			item.after("<span class='validate-help-text'>"+validator.helpText+"</span>");
			item.removeClass('validate-valid');
			item.addClass('validate-invalid');
			return this;
		},




		markValid:function(item) {
			item = $(item);
			item.removeClass('validate-invalid');
			item.addClass('validate-valid');
			$('span.validate-help-text').remove();
			return this;
		}
	}

	$.validator = new Validator();

	$('document').ready(function() {
		/**
		 * The following are preset validators. Hopefully they cover common needs,
		 * but they can always be overridden or extended. Just make calls to
		 * registerValidator() as necessary.
		 */

		$.validator.registerValidator({
			classname:"validate-alphabetical",
			helpText:	'Must contain only alphabetical characters.',
			regexp:		/^[a-zA-Z]*$/
		});




		$.validator.registerValidator({
			classname:"valid-alpha-spaces",
			helpText:'Must contain only alphabetical characters and spaces.',
			regexp:  /^[a-zA-Z ]*$/
		});




		$.validator.registerValidator({
			classname:"valid-alpha-numeric",
			text:"Must contain only alphabetical characters and numbers.",
			regexp: /^[a-zA-Z0-9]*$/
		});




		$.validator.registerValidator({
			classname:"valid-alpha-num-spaces",
			helpText:'Must contain only alphabetical characters, numbers, and spaces.',
			regexp:/^[a-zA-Z0-9 ]*$/
		});




		$.validator.registerValidator({
			classname:"valid-verify-password",
			helpText:'Passwords do not match!',
			callback:function(item){
				var value = $(item).value;
				var first = $('.valid-password')[0].value;
				if (value != first) return false;
				else return true;
			}
		});




		$.validator.registerValidator({
			classname:"valid-integer",
			helpText:'Must contain only integers (whole numbers, no decimals).',
			regexp:/^[0-9]*$/
		});




		$.validator.registerValidator({
			classname:"valid-float",
			helpText:'Must contain only numbers.',
			regexp:/^[0-9\.]*$/
		});




		$.validator.registerValidator({
			classname:"valid-ip-address",
			helpText:'Please enter a valid IP address.',
			regexp:/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
		});




		$.validator.registerValidator({
			classname:"valid-us-phone",
			helpText:'Please enter a phone number in the format: (123) 456-7890.',
			regexp:/^(\+\d)*\s*(\(\d{3}\)\s*)*\d{3}(-{0,1}|\s{0,1})\d{2}(-{0,1}|\s{0,1})\d{2}$/
		});




		$.validator.registerValidator({
			classname:"valid-us-currency",
			helpText:'Please enter a valid dollar amount.',
			regexp:/^\d+\.\d{2}$/
		});




		/**
		 * Checks that the selected option on a selction box is not empty.
		 */
		$.validator.registerValidator({
			classname:"valid-select-option",
			helpText:"Please select a value.",
			callback:function(item){
				var value ="";
				try {
					value=$(item).options[item.selectedIndex].value;
				}
				catch(e){}
				if(value=="") return false;
				return true;
			}
		});




		$.validator.registerValidator({
			classname:"valid-url",
			helpText:'Please enter a valid URL in the format: http://www.servername.com/path/to/page.html?querystringlookslike=this&...',
			regexp:/^(http|https|ftp)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$/
		});




		$.validator.registerValidator({
			classname:"valid-street-address",
			helpText:'Please enter a valid street address.',
			regexp:/^[a-zA-Z\d]*(([\'\,\.\- #][a-zA-Z\d ])?[a-zA-Z\d]*[\.]*)*$/
		});




		$.validator.registerValidator({
			classname:"valid-na-postal-code",
			helpText:'Please enter a valid US/Canada postal code.',
			regexp:/^((\d{5}-\d{4})|(\d{5})|([AaBbCcEeGgHhJjKkLlMmNnPpRrSsTtVvXxYy]\d[A-Za-z]\s?\d[A-Za-z]\d))$/
		});




		$.validator.registerValidator({
			classname:"valid-email-address",
			helpText:'Please enter a valid email address.',
			regexp:/^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/
		});




		/**
		 * For minimum values, you'll need to set the rel attribute to include
		 * "minimum:x" where x is the min value.
		 */
		$.validator.registerValidator({
			classname:"valid-minimum",
			helpText:'Minimum length not met.',
			callback:function(item){
				var val = item.attr('rel').match(/(?:minimum\:)[0-9]*/);
				if(item.value.length < val) return false;
				return true;
			}
		});
	});
})(jQuery);