/**
 * Validacion de Formularios -> jQuery Plugin
 * @author Monoku Team <http://www.monoku.com>
 */
(function($){	
/**
 * Metodo del plugin
 * @param {Object} rules Objeto que contiene las reglas de validacion
 * @param {Number} timeout Tiempo que demorara en aparecer el feedback de la validacion 
 */
$.fn.formValidation  = function(rules, timeout){	

/**
 * Timestamp que se actualiza cada vez que el usuario interactua
 * con elementos del formulario
 * @private
 */
var _timerTimestamp = null,

/**
 * Objeto que almacenara los timeout por elemento en una estructura clave -> valor
 * { nombrelemento: apuntadortimeout}
 * @private
 */
_timer = {},

/**
 * Clase CSS que se asignara al elemento en el caso de que pase la validacion
 * @private
 */
_cssSuccess = 'form-success',

/**
 * Clase CSS que se asignara al elemento en el caso de que NO pase la validacion
 * @private
 */
_cssError = 'form-error',

/**
 * Tiempo que demorara en aparecer el feedback de la validacion  
 * @private
 */
_timeout = timeout || 3000,


/**
 * Funcion callback que se encarga de validar el tiempo que ha pasado desde
 * la ultima interaccion del usuario con un elemento del formulario.
 * Si el tiempo que ha pasado es superior al establecido envia el realiza
 * la validacion y envia feedback al usuario
 * 
 * @private
 * @param {String} i Nombre del campo a validar
 * @param {String|Array} cond Condicion a utilizar para la validacion
 * @param {String} val Contenido del campo actual
 * @param {HTMLElement} field Campo del formulario a validar
 * @return {bool} El campo ha pasado la validacion?
 */
_timeoutCallback = function(i, cond, val, field){
	/**
	 * Verificamos si el usuario ha interactuado con algun campo y si es asi
	 * varificamos que el tiempo que ha pasado sea mayor o igual al establecido para mostrar el feedback
	 */
	if( _timerTimestamp && ( new Date().getTime() - _timerTimestamp ) >= (_timeout / 1000) ){
		//si el formato de la regla es una cadena
		if( cond.constructor === String ){
			result = $.fn.formValidation.rules[cond]( val );
		}
					
		//si el formato de la regla es un arreglo
		if( cond.constructor === Array ){
			/**
			 * Hacemos una copia del arreglo de condiciones
			 * la variable cond es actualmente una referencia a la regla original
			 * si modificamos directamente esta variable ocurrira un error cuando
			 * se vuelve a utilizar esta regla para validar
			 */
			var condCopy = Array.prototype.slice.apply(cond),
			//extraemos el primer elemento de la regla que debe ser siempre el nombre de la misma
			rl = condCopy.shift(),
			//traemos el metodo correspondiente para la regla
			fn = $.fn.formValidation.rules[ rl ];
			
			//agregando el contenido del campo al arreglo que va ser pasado como argumento al metodo de la regla
			condCopy.unshift(val);
			//agregamos el objeto del campo al arreglo de argumento
			condCopy.push(field);
			//llamando al metodo de la regla
			result = fn.apply(this, condCopy );
		}		
	}
	//se limpia el camino
	clearTimeout( _timer[i] );
	
	return result;
},
	

/**
 * Hash de metodos privados
 * @private
 */
_actions = {
	/**
	 * Actualiza el timestamp de interaccion
	 */
	updateTimer: function(){
		_timerTimestamp = new Date().getTime();		
		return true;
	},
	
	/**
	 * Cambia la clase del campo dependiendo si paso o no la validacion
	 * @param {bool} result  Resultado de la validacion para el campo
	 * @param {HTMLElement} field Campo del formulario
	 */
	putIndicator: function(result, field){
		if( result ){
			$(field).removeClass( _cssError ).addClass( _cssSuccess );
		}
		else{
			$(field).removeClass( _cssSuccess ).addClass( _cssError );
		}
		return true;
	}
};
	
/**
 * Inicia el cuerpo del plugin de jQuery
 */
return this.each(function(){
	
	/**
	 * Campos que van a ser validados
	 * @private
	 */
	var totalFields = {length : 0},
	
	/**
	 * Campos que han pasado la validacion correctamente
	 * se utiliza para activar el envio del formulario
	 * @private
	 */
	fieldsOK = {length: 0};
	
	/**
	 * Agregando la funcion al evento submit del formulario
	 * que se encargara de verificar que todos los campos 
	 * hayan validado para enviar el formulario
	 */
	$(this).submit(function(){
		return (fieldsOK.length >= totalFields.length);
	});
	
	/**
	 * Buscamos todos los elementos dentro del formulario que sean campos de texto,
	 * radios, checkbox y areas de texto y agregamos una funcion al evento keyup
	 * Al final actualiza la cantidad de campos a validar en 
	 */
	$(this).find('input:not(:submit), textarea').bind('keyup blur',function(e){
		
		if(e.keyCode === 9) return false;//si la tecla es tab
		
		var val = $(this).val(), //capturamos el valor actual del campo
			name = $(this).attr('name'),//el nombre del campo
			conds = rules[name] || [];//capturamos las reglas establecidas para el campo si las hubiere
		
		//si existen reglas para este campo quiere decir, obviamente, que se debe validar
		if(conds.length){
			//actualizamos el contador de campos a validar
			if( !totalFields[name] ){
				totalFields[name] = true;
				totalFields.length++;
			}
			_actions.updateTimer();//se actualiza el timestamp de la ultima actualizacion
		}
		
		/**
		 * Recorriendo las reglas establecidas para el campo
		 */
		for(var j = 0, n = conds.length; j < n; j++){
			clearTimeout( _timer[name] ); //quita un tiemout anterior para este campo
			
			/**
			 * Se ejecuta una solucion de contexto para poder usar el objeto [[this]]
			 * y arreglos
			 */
			(function(fld, tmpCond, value){
				//setup del timeout
				_timer[name] = setTimeout(function(){
					//se llama a la funcion que hace la validacion y
					//el valor que devuelve se pasa como argumento al metodo que muestra el feedback
					var r = _timeoutCallback(name, tmpCond, value, fld);
					
					//si el campo paso la validacion se agrega al objeto de campos
					if(r){
						if(!fieldsOK[name]){
							fieldsOK[name] = true;
							fieldsOK.length++;
						}
					}
					//de lo contrario se elimina o no se agrega
					else{
						if(fieldsOK[name]){
							delete fieldsOK[name];
							fieldsOK.length--;
						}
					}
					
					_actions.putIndicator( r, fld);
				}, _timeout);
			})(this, conds[j], val);//la magia de la solucion de contexto. Life is Good!
		}
		
		return true;
	});	
});
};

/**
 * Objeto con las diferentes reglas de validacion
 */
$.fn.formValidation.rules = {	
	/**
	 * Verifica si el campo esta vacio
	 * @param {String} val Contenido del campo actual
	 * @return {bool} El campo conteniene algun valor?
	 */
	required: function(val){
		return !!(typeof val !== 'undefined' && val && val !== '');
	},
	
	/**
	 * Verifica si el campo contiene un formato de email valido
	 * @param {String} val Contenido del campo actual
	 * @return {bool} Es un email valido?
	 */
	email: checkEmail,
	
	/**
	 * Verifica si el campo contiene un formato de URL valido
	 * @param {String} val Contenido del campo actual
	 * @return {bool} Es una URL valida?
	 */
	url: validateUrl,
	
	/**
	 * Verifica si el campo contiene una cantidad minima de caracteres
	 * @param {String} val Contenido del campo actual
	 * @param {Number} chars Cantidad de caracteres minimos
	 * @return {bool} Contiene esa cantidad minima de caracteres?
	 */
	minLength: function(val, chars){
		return (val && val.length >= chars);
	},
	
	/**
	 * Verifica si el valor del campo es igual al de otro campo
	 * @param {String} val Contenido del campo actual
	 * @param {String} matchField Nombre del campo hermano
	 * @param {HTMLElement} field Campo actual
	 * @return {bool} Contiene esa cantidad minima de caracteres?
	 */
	match: function(val, matchField, field){		
		return !!( val === $(field.form[matchField]).val() );
	},
	
	/**
	 * Verifica el valor del campo dependiendo de una funcion personalizada
	 * @param {String} val Contenido del campo actual
	 * @param {Function} callback Funcion personalizada. Se le envian 2 parametros 1.valor del campo y 2. el objeto del campo
	 * @param {HTMLElement} field Campo actual
	 * @return {bool} El valor bool que devuelve la funcion personalizada
	 */
	custom: function(val, callback, field){
		//el doble callback en la condicion es intencional
		callback = (callback && callback.constructor === Function && callback) || function(){return true;};
		return callback(val, field);
	}
};

})(jQuery);
