/**
 * Fvalidate v1.0 - A validation plugin for jQuery
 * Copyright (C) 2010  Hasan Hameed
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Fvalidate v1.0.1
 * by Hasan Hameed - theculpritz@hotmail.com
 *
 * e.g. HTML
 * <form id="myform" name="myform">
 * <input name="userid" type="text" required="yes" maxlength="20" minlength="8" rule="alpha" />
 * <input name="conf-userid" type="text" required="yes" /><span class="err-msg">userid doesn't match</span>
 * <input name="address" type="text" required="yes" />
 * <input type="submit" value="Submit>
 * </form>
 *
 * e.g. Javascript - shiuld be inside document.ready jQuery function
 * $("#myform").Fvalidate();
 * $("input[name=conf-userid]").sameAs($("input[name=userid]"));
 * $("input[name=address]").rule(/^[a-z0-9-\s]*$/);
 */

(function($) {

	var _errors = [],
		_rules = [],
		_sameAs = [];

	$.fn.validform = function(settings, onSubmit)
	{
		var $form = $(this),
			formId = $form.attr("id"),
			$fields = $form.find("input, textarea, select").filter(":not(:submit)"),
			submitFunction = onSubmit,

			defaults = {
				alert:	true,											/* alert error messages */
				attrForName:		"name",								/* define field attribute to use as field name in error message (chnge only when other one is defined) */
				focusClass:			"input-focus",						/* class to use when focused */
				errorClass: 		"input-error",						/* error class name */
				messageClass:		"error-messages",					/* error message class name */
				inlineMessageClass:	"inline-message",					/* inline error message class name */
				inactiveClass:		"inactive" 							/* inactive class for submit button */
			},
			options = $.extend(defaults, settings);

		_errors[formId] = [];


		$fields.focus(function() {
			$(this).addClass(options.focusClass);
		});
		$fields.blur(function() {
			$(this).removeClass(options.focusClass);
		});

		$fields.keyup(function(){
			_validateForm(false);
			_validate(this);
		});

		$fields.change(function(){
			_validateForm(false);
			_validate(this);
		});

		$form.submit(function(event){
			event.preventDefault();
			
			if (!_validateForm()) return false;	/* Bind Validation on form submit */
			if (submitFunction != undefined)
			{
				submitFunction();
				return false;
			}
		});
		

		/**
		 * _validate - Form validation on submit
		 * @return boolean
		 */
		function _validateForm(showErrors)
		{
			var showErrors = (showErrors == undefined) ? true : showErrors;
			
			$fields.each(function(){
				_validate(this, showErrors);	/* Validate each field */
			});
			/* Focus first error field */
			if ($form.countErrors() > 0)
			{
				if (showErrors) 
				{
					$form.showErrors();
				
					for (var x in _errors[formId]){
						_errors[formId][x]['elem'].focus(); break;
					}
				}
				
				// add inactive class button
				$('submit, button[type="submit"]', $form).addClass('inactive');
				
				return false;
			}
			
			// remove inactive class button
			$('submit, button[type="submit"]', $form).removeClass('inactive');
			
			return true;
		};

		/**
		 * _validate - Input field validation
		 */
		function _validate(obj, showErrors)
		{
			var showErrors = (showErrors == undefined) ? true : showErrors;
			
			var $input = $(obj),
				name = $input.attr(options.attrForName),
				val = $input.val();

			/* required => yes */
			if ($input.hasClass('required') )
			{
				if($input.is(":checkbox") || $input.is(":radio"))
				{
					if ( ! $input.attr('checked') || ! $input.is(':checked') )
					{
						$input.setError('Select a ' + name, showErrors); 
						return false; /* on error */
					}
				}
				
				if (val == '' || val == undefined || val == $input[0].defaultValue)
				{
					$input.setError('Enter a ' + name, showErrors);
					return false;
				}
			}

			/* Check for rules */
			var mask = _rules[name] || false;
			
			if ($input.attr('class').indexOf('rule-') != -1)
			{
				if ($input.hasClass('rule-alpha') ) mask = /^[a-zA-Z\s]*$/;
				else if ($input.hasClass('rule-phone') ) mask = /^\+?([()\s-]*[0-9]){10,}$/;
				else if ($input.hasClass('rule-numeric') ) mask = /^[0-9\s]*$/;
				else if ($input.hasClass('rule-floating') ) mask = /^[0-9-\.\s]*$/;
				else if ($input.hasClass('rule-alnum') ) mask = /^[a-zA-Z-0-9\s]*$/;
				else if ($input.hasClass('rule-alnumhyph') ) mask = /^[a-zA-Z0-9-\s]*$/;
				else if ($input.hasClass('rule-email') ) mask = /^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i;
				else if ($input.hasClass('rule-phone-email') ) mask = /^(\+?([()\s-]*[0-9]){10,})|([^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,})$/i;
				
				if( ! mask.test(val) )
				{
					$input.setError('Invalid ' + name, showErrors);
					return false;
				}
			}
	
			/* Check for min. length */
			var minlen = $input.attr("minlength") || 0;
			
			if ( minlen )
			{
				var thislen = val.length;
				if (minlen > thislen)
				{
					$input.setError(name + ' must be more than ' + minlen + 'characters long', showErrors); 
					return false;
				}
			}

			/* Check for Same as value against another element */
			var elem = _sameAs[name] || false;
			
			if (elem)
			{
				if (elem.val() !== val)
				{
					$input.setError(elem.attr('name') + ' does not match.', showErrors);
					if (showErrors) $input.nextAll('.' + options.inlineMessageClass).slideDown();
					return false;
				}
				if (showErrors) $input.nextAll('.' + options.inlineMessageClass).slideUp();
			}
			
			/* No Error */
			$input.removeError();
		};

		var methods = {

			/* Set error 
			 * @param string [form id], string [message]
			 */
			setError:	function(msg, showErrors)
			{
				var showErrors = (showErrors == undefined) ? true : showErrors;
				
				var name = $(this).attr("name"),
				fid = $(this).parents("form:last").attr("id");
				_errors[fid][name] = [];
				_errors[fid][name]['elem'] = this;
				_errors[fid][name]['msg'] = msg;
				
				if (showErrors) this.addClass(options.errorClass);
				
				return this;
			},

			/* Remove error */
			removeError:	function()
			{
				var fid = $(this).parents("form:last").attr("id");
				delete _errors[fid][$(this).attr("name")];
				this.next('.' + options.inlineMessageClass).slideUp('fast');
				if(this.hasClass(options.errorClass) )
					return this.removeClass(options.errorClass);
			},

			/* showErrors - Show errors */
			showErrors:	function() {
				var s = '', 
					fid = $(this).attr("id");
					
				for(x in _errors[fid])
				{
					var message = _errors[fid][x]['msg'];
					
					// show inline message
					$(_errors[fid][x]['elem']).next('.' + options.inlineMessageClass)
						.text(message).slideDown('fast');
					
					s += "\n " + message + '.';
				}
				
				$('.' + options.messageClass, this).text(s);
				
				if(options.alert){
					alert(s);
				}
				
				return this;
			},

			/* countErrors - Get no. of errors */
			countErrors:	function()
			{
				var c = 0, x;
				
				for (x in _errors[$(this).attr("id")])
					c++;
				return c;
			},

			/* rule - adds new rule for jquery element(s) to '_rules' array
			 * @param regex or 'alpha','numeric','email'
			 */
			rule:	function(mask)
			{
				this.each(function(){
					_rules[$(this).attr('name')] = mask;
				});
				return this;
			},

			/* sameAs - Check for same field values
			 * @param single jquery element
			 */
			sameAs:	function(elem)
			{
				_sameAs[$(this).attr('name')] = elem;
				return this;
			},

			changeClass:	function(from, to)
			{
				return this.removeClass(from).addClass(to);
			}
		};

		$.each(methods, function(i) {
			$.fn[i] = this;
		});

		return $form;
	};
})(jQuery);