/**
 * @author Rodrigo Henrique Paiva <rhpaiva[at]gmail.com>
 * @version 0.6, 06/2008
 *
 * Usage:
 *
 * var Validator = new FormValidator();
 *
 * Validator.setup
 * ([
 * 	{id : 'Email'   , defType  : 'email'},
 * 	{id : 'Email2'  , equals : 'Email'},
 * 	{id : 'ZIP'     , handler  : validateZip},
 * 	{id : 'Number'  , minLen   : 1, defType : 'number'},
 * 	{id : 'City'    , minLen   : 2},
 * 	{id : 'State'   , fixedLen : 2, defType : 'string'}
 * ]);
 * 
 * Validator.setMsgTarget('msgValidacao');
 *
 * document.getElementById('faleconosco').onsubmit = function()
 * {
 *		return Validator.run({showErrors:true});
 * };
 *
 * Changelog
 *
 * v0.6: 
 *   - Added validation for two equal fields
 *
 */
 
/* Requires jQuery at least 1.2.3

The following functions/prototypes are required by the class

String.prototype.isEmail = function()
{
	var regex = /^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)+)\.([A-Za-z]{2,})$/

	if (regex.test(this))
	{
		return true;
	}
	
	return false;
};

*/

var FormValidator = function()
{
	// private variables
	var fieldsToValidate,        // JSON array that holds all the fields to be validated
		idMsgTarget,             // the ID of the target to display the errors message
		invalidFields,           // JSON array with all the invalid fields
		fieldNameSrc  = 'title'; // the attribute to be used when retrieving the name of the field
	
	// config the strings of the messages
	var errorMsgConfig = new Array();
	errorMsgConfig['fixFollowingErrors'] = '<h3>Por favor, corrija os seguintes erros:</h3>';
	errorMsgConfig['listTag']            = 'ul';
	errorMsgConfig['err.fixedLen']       = 'O campo <strong>"%field%"</strong> deve ter exatamente %len% caracter(es).';
	errorMsgConfig['err.maxLen']         = 'O campo <strong>"%field%"</strong> deve ter no máximo %len% caracter(es).';
	errorMsgConfig['err.minLen']         = 'O campo <strong>"%field%"</strong> deve ter no mínimo %len% caracter(es).';
	errorMsgConfig['err.empty']          = 'O campo <strong>"%field%"</strong> deve ser preenchido.';
	errorMsgConfig['err.number']         = 'O campo <strong>"%field%"</strong> deve ter apenas números.';
	errorMsgConfig['err.string']         = 'O campo <strong>"%field%"</strong> não deve ter números.';
	errorMsgConfig['err.email']          = 'O campo <strong>"%field%"</strong> deve conter um endereço de e-mail válido.';
	errorMsgConfig['err.cep']            = 'O campo <strong>"%field%"</strong> deve conter um CEP válido.';
	errorMsgConfig['err.rg']             = 'O campo <strong>"%field%"</strong> não contém um RG válido.';
	errorMsgConfig['err.equals']         = 'Os campos <strong>"%field%"</strong> e <strong>"%field2%"</strong> devem ser iguais.';
	errorMsgConfig['err.handler']        = 'O campo <strong>"%field%"</strong> não contém dados válidos.';

	return {
		
		/**
		 * Runs the whole validation.
		 * Possible values of the config are:
		 *   boolean showErrors = whether the message with the validation erros must be shwon [true|false]
		 *
		 * @param objConfig object Optional. An object with the configuration. See method description for possible values. Example: {showErrors:true}
		 * @return boolean If the fields are valid or not
		 */
		run : function(objConfig)
		{
			var objField, 
				validationHandler;
				
			objConfig = (objConfig === undefined ? null : objConfig);

			// clean the previous mess
			invalidFields = null;
			invalidFields = new Array();
			
			// loop thru all fields
			for (var i in fieldsToValidate)
			{
				objField = fieldsToValidate[i]; // get the current field

				// the id is not set, go to the next iteration
				if (objField.id === undefined) continue;

				/**
				 * length validation
				 */
				 // fixed length
				if (objField.fixedLen !== undefined)
				{
					if ( this.validateLength(objField, 'fixed') === false )
					{
						this.setInvalidField({
							fieldId   : objField.id, 
							errorType : 'fixedLen', 
							length    : objField.fixedLen, 
							index     : i
						});
					}
				}
				else
				{
					// mininum length
					if (objField.minLen !== undefined)
					{
						if ( this.validateLength(objField, 'min') === false )
						{
							this.setInvalidField({
								fieldId   : objField.id, 
								errorType : 'minLen', 
								length    : objField.minLen, 
								index     : i
							});
						}
					}
					// maximum length
					if (objField.maxLen !== undefined)
					{
						if ( this.validateLength(objField, 'max') === false )
						{
							this.setInvalidField({
								fieldId   : objField.id, 
								errorType : 'maxLen', 
								length    : objField.maxLen, 
								index     : i
							});
						}
					}
				}
				
				/**
				 * equals validation
				 */
				if (objField.equals !== undefined)
				{
					if ( this.validateEqual(objField) === false )
					{
						this.setInvalidField({
							fieldId   : objField.id, 
							errorType : 'equals', 
							equalsTo  : objField.equals,
							index     : i
						});
					}
				}
				
				/**
				 * pre-defined validation
				 */
				if (objField.defType !== undefined)
				{
					if ( this.validateDefined(objField) === false )
					{
						this.setInvalidField({
							fieldId   : objField.id, 
							errorType : objField.defType, 
							index     : i
						});
					}
				}
				
				/**
				 * external handler validation
				 * the handler receives an object with the field content and must return an object with some attributes set
				 */
				if (typeof(objField.handler) == 'function')
				{
					var handlerResult = objField.handler(objField);
					//alert(handlerResult.valid);
					if (handlerResult.valid === false)
					{
						this.setInvalidField({
							fieldId      : objField.id, 
							handlerError : handlerResult.error, 
							index        : i
						});
					}
				}

			} // end for
			
			// check if the error message must be shown
			if ( (typeof(objConfig) === 'object') && (objConfig !== null) && (invalidFields.length > 0) )
			{
				if (objConfig.showErrors !== false)
				{
					this.showErrors();
				}
			}
			
			return invalidFields.length == 0;
			
		}, // end run
		
		/**
		 * Shows the error messages
		 *
		 * @return void
		 */
		showErrors : function()
		{
			var objElement, fieldName, errorMsgContent, errorMsg;
			
			errorMsgContent = null;
			errorMsgContent = errorMsgConfig['fixFollowingErrors'] + '<' + errorMsgConfig['listTag'] + '>';

			for ( var i in invalidFields )
			{
				objElement = invalidFields[i];
				//console.log($('#' + objElement.id).attr(fieldNameSrc));
				
				fieldName  = $('#' + objElement.id).attr(fieldNameSrc);
				errorMsg   = (objElement.errorType !== undefined ? errorMsgConfig['err.' + objElement.errorType] : objElement.handlerError );

				errorMsgContent += '<li>' + errorMsg.replace('%field%', fieldName).replace('%len%', objElement.length).replace('%field2%', $('#' + objElement.equalsTo).attr(fieldNameSrc)) + '</li>';
			}
			
			errorMsgContent += '</' + errorMsgConfig['listTag'] + '>';
			
			$('#' + idMsgTarget).show().html(errorMsgContent);
		},
		
		/**
		 * =======================================================================
		 * VALIDATORS
		 * =======================================================================
		 */
		 
		/**
		 * Validates the proper length of the field
		 *
		 * @param objField object The object which holds the field attributes
		 * @param strType string The type of the length validation. Possible values: [min = minimum length | max = maximum length | fixed = specific length]
		 * @return boolean The result of the validation
		 */
		validateLength : function(objField, strType)
		{
			var objElement = $('#' + objField.id);

			if (objElement !== null)
			{
				switch (strType)
				{
					case 'min':
						return ( $.trim(objElement.val()).length >= objField.minLen );
						break;
						
					case 'max':
						return ( $.trim(objElement.val()).length <= objField.maxLen );
						break;

					case 'fixed':
						return ( $.trim(objElement.val()).length === objField.fixedLen );
						break;
				}
			}
		},
		
		/**
		 * Validates whether the field contains the same value as another one
		 *
		 * @param objField object The object which holds the field attributes
		 * @return boolean The result of the validation
		 */
		validateEqual : function(objField)
		{
			var objElement   = $('#' + objField.id)
				elmToCompare = $('#' + objField.equals);

			if (objElement !== null)
			{
				return ( $.trim(objElement.val()) === $.trim(elmToCompare.val()) );
			}
		},

		/**
		 * Validates the field using a pre-defined type of validator.
		 * Possible pre-defined validators are:
		 *   number = must contain only numbers
		 *   string = may contain anything but numbers
		 *   email  = must contain a valid email address
		 *   cep    = must contain a valid CEP (the equivalent of ZIP in Brazil)
		 *   rg     = must contain a valid RG (brazilian main personal ID)
		 *
		 * @param objField object The object which holds the field attributes
		 * @return boolean The result of the validation
		 */
		validateDefined : function(objField)
		{
			var objElement = $('#' + objField.id),
				strType    = objField.defType;

			if (objElement !== null)
			{
				switch (strType)
				{
					case 'number':
						return !( /\D+/g.test( $.trim(objElement.val()) ) );
						break;

					case 'string':
						return !( /\d+/g.test( $.trim(objElement.val()) ) );
						break;

					case 'email':
						return ( $.trim(objElement.val()).isEmail() );
						break;
						
					case 'cep':
						return ( /[0-9]{5}\-?[0-9]{3}/g.test( $.trim(objElement.val()) ) );
						break;

					case 'rg':
						return ( /[0-9A-Z]{2,3}\.?[0-9A-Z]{3}\.?[0-9A-Z]{3}\-?[0-9A-Z]{1,2}/g.test( $.trim(objElement.val()) ) );
						break;

				}
			}
		},
		
		/**
		 * =======================================================================
		 * SETTERS
		 * =======================================================================
		 */
		 
		/**
		 * Setup the fields to be validated.
		 * Example:
		 *   [{id : 'fieldID', minLen : 5}, {id : 'fieldID2', handler : functionToHandle}, {id : 'fieldID3', defType : 'email'}]
		 *
		 * Possible values for the attributes of the field:
		 *   string   id       = the ID of the field, the same as specified in the attribute of the tag
		 *   integer  minLen   = the minimum length required
		 *   integer  maxLen   = the maximum length required
		 *   integer  fixedLen = an specific length required
		 *   function handler  = an external function to handle the field validation
		 *   string   defType  = the pre-defined type of the field, see method validateDefined description for possible values
		 *   string   equals   = the id of the field to check whether both values are equal
		 *
		 * @param arrFields array An array of objects with the attributes of the field. See the description for an example.
		 * @return void 
		 */
		setup : function(arrFields)
		{
			fieldsToValidate = null;
			fieldsToValidate = arrFields;
			arrFields        = null;
		},
		
		/**
		 * Sets an invalid field
		 *
		 * Possible values for the attributes of the objConfig:
		 *   string  id           = the ID of the field, the same as specified in the attribute of the tag
		 *   string  errorType    = the type of error that ocurred
		 *   integer length       = the maximum, minimum or specific length of the field validated, used when the error message is shown
		 *   integer index        = the index number of the field in the fieldsToValidate array
		 *   string  handlerError = the error message returned by the external handler, when there is one set
		 *
		 * @param objConfig object An object with the attributes of the invalid field. See the method description for possible values.
		 * @return void 
		 */
		setInvalidField : function(objConfig)
		{
			invalidFields.push
			({
			 	id           : objConfig.fieldId, 
				errorType    : objConfig.errorType, 
				length       : objConfig.length,
				equalsTo     : objConfig.equalsTo,
				index        : objConfig.index,
				handlerError : objConfig.handlerError
			});
		},

		/**
		 * Sets the target where the error message will be shown
		 *
		 * @param strIdTarget string The id of the element where the errors will be shown
		 * @return void 
		 */
		setMsgTarget : function(strIdTarget)
		{
			idMsgTarget = strIdTarget;
		},
		
		/**
		 * =======================================================================
		 * GETTERS
		 * =======================================================================
		 */
		 
		/**
		 * Gets the invalid fields
		 *
		 * @return array An array with all invalid fields
		 */
		getInvalidFields : function()
		{
			return invalidFields;
		},
		
		/**
		 * Checks if the validation encountered any problem
		 *
		 * @return boolean True when there is any invalid field, false when none
		 */
		hasInvalidFields : function()
		{
			return invalidFields.length > 0;
		}
	} // end return
};