(function($)
{
	/*
	 * Asigna la validación al evento "click" de los elementos coincidientes
	 * Devuelve el objeto jquery en uso
	 */
	$.fn.sahFormValidator = function(Opts)
	{
		this.click(function(e)
		{
			e.preventDefault();
			var bValid = $.sah.formValidator(Opts);
			if(bValid && $.isFunction(Opts.success))
			{
				/* binds callback to element for easy acces to "this" */
				this.success = Opts.success;
				this.success();
			}
			else if(!bValid && $.isFunction(Opts.error))
			{
				this.error = Opts.error;
				this.error();
			}
		});
		return this;
	};
	
	/*
	 * Valida que el elemento cumpla los filtros especificados por sus clases
	 * Devuelve un objeto con las propiedas error(boolean) y message(string)
	 */
	$.fn.sahValidate =
		function(Opts)
		{
			var Validation = {
				error: false,
				message: ''
			};
			if(this.length > 0)
			{
				/* validation filters */
				var AlphanumFilter = /^[\w\sñ]*$/i;
				var NumericFilter = /^(-?\d+(\.\d+)?)?$/;
				var UsernameFilter = /^[\w\d\._]*$/i;
				var PasswordFilter = /^[\w\d]*$/i;
				var EmailFilter = /^([\w\.]+@[\w\.]+\.[a-z]{2,})?$/i;
				
				/* using with for simplicity */
				with($(this[0]))
				{
					/* Remove extra spaces if needed */
					if(Opts.removeExtraSpaces)
						sahTrimVal();
					
					/* Required Filter */
					if(hasClass('req-required') && ((Validation.error = (val() == ''))))
						Validation.message = Opts.msg_required;
					
					/* Alphanum Filter */
					if(!Validation.error && hasClass('req-alphanum')
						&& (Validation.error = !AlphanumFilter.test(val())))
						Validation.message = Opts.msg_alphanum;
					
					/* Numeric Filter */
					if(!Validation.error && hasClass('req-numeric')
						&& (Validation.error = !NumericFilter.test(val())))
						Validation.message = Opts.msg_numeric;
					
					/* User Name Filter */
					if(!Validation.error && hasClass('req-username')
						&& (Validation.error = !UsernameFilter.test(val())))
						Validation.message = Opts.msg_username;
					
					/* Password Filter */
					if(!Validation.error && hasClass('req-password')
						&& (Validation.error = !PasswordFilter.test(val())))
						Validation.message = Opts.msg_password;
					
					/* E-Mail Filter */
					if(!Validation.error && hasClass('req-email')
						&& (Validation.error = !EmailFilter.test(val())))
						Validation.message = Opts.msg_email;
					
					/* Custom filter */
					if(!Validation.error && hasClass('req-custom'))
					{
						var CustomFilter = eval(attr('reqcustom'));
						if((Validation.error = !CustomFilter.test(val())))
							Validation.message = Opts.msg_custom;
					}
					
					/* Max Chars filter */
					if(!Validation.error && attr('reqmaxchars') != undefined)
					{
						if((Validation.error = (val().length > attr('reqmaxchars'))))
							Validation.message = Opts.msg_maxchars;
					}
					
					/* En caso de encontrar error, completamos el mensaje */
					if(Validation.error)
					{
						/* Get field description */
						var sFieldName = attr('reqdesc');
						if(sFieldName == undefined)
							sFieldName = attr('name');
						/* add field description to message */
						Validation.message =
							Opts.prefix + ' "' + sFieldName + '" ' + Validation.message;
						/* add error class to element */
						addClass(Opts.err_class);
					}
				}/* end with */
			} /* end if this.length > 0 */
			return Validation;
		};
	
	/*
	 * Valida todos los elementos que esten dentro del scope Devuelve TRUE si
	 * todos los elementos son validos Devuelve FALSE al encontrar el primer
	 * elemento no valido
	 */
	$.sah.formValidator =
		function(Opts)
		{
			/*
			 * Opciones por default El primer argumento es un objeto vacio para
			 * no alterar ninguo de los otros objetos
			 */
			Opts = $.extend($.sah.formValidator.defaultOptions, Opts);
			
			/* get elements inside the scope and remove error class */
			var aFields =
				$(Opts.scope + ' .req-alphanum,' + Opts.scope + ' .req-required,' + Opts.scope
					+ ' .req-numeric,' + Opts.scope + ' .req-username,' + Opts.scope
					+ ' req-password,' + Opts.scope + ' .req-email,' + Opts.scope + ' .req-custom');
			aFields.removeClass(Opts.err_class);
			
			/* hide error message */
			if($(Opts.msg_div).length > 0)
				$(Opts.msg_div).hide();
			
			/* no fields, no validation */
			if(aFields.length == 0)
				return true;
			
			/*
			 * Utilizamos oldschool for en lugar de $(...).each(...) Para salir
			 * de cliclo (y metodo) con la primera invalidación detectada.
			 */
			else for( var i = 0; i < aFields.length; i++)
			{
				/* validate element */
				var Validation = $(aFields[i]).sahValidate(Opts);
				if(Validation.error)
				{
					/* show validation error message */
					if($(Opts.msg_div).length > 0)
						eval('$(Opts.msg_div).text(Validation.message).' + Opts.msg_div_show_fn);
					else alert(Validation.message);
					/* focus invalid element */
					aFields[i].focus();
					/* break for and return false */
					return false;
				}
			}
			return true;
		};
	
	/* default validator options, to extend options argument */
	$.sah.formValidator.defaultOptions = {
		scope: '',
		msg_div: '',
		msg_div_show_fn: 'show()',
		removeExtraSpaces: true,
		err_class: 'req-error',
		
		/* messages */
		prefix: 'Dato',
		msg_alphanum: 'Requiere valor alfa-numérico',
		msg_numeric: 'Requiere valor numérico',
		msg_required: 'Es obligatorio',
		msg_username: 'No es un nombre de usuario válido',
		msg_password: 'No es una clave válida',
		msg_email: 'No es un E-Mail válido',
		msg_custom: 'No cumple el formato específico',
		msg_maxchars: 'Sobrepasa el limite de caracteres permitido',
		
		/* callbacks */
		success: null,
		error: null
	};
	
})(jQuery);