/**
 * 
 * 	DEFININDO
 * 
 * 		SINTAXE:
 * 			
 * 			.maskedTo({String}maskName,{Object}options);
 * 			
 * 		PARAMETROS:
 * 			
 * 			- maskName:String
 * 				Nome da máscara
 * 
 * 			- options:Object
 * 				Opções de tratamento:
 * 
 * 					- validate : function(o,event) {}
 *					 	verificar os dados do objeto. Deve retornar um valor BOOLEANO
 *
 * 					- validatedTrue : function(o) {}
 * 						será executada se "validate" retorna TRUE
 * 
 * 					- validatedFalse : function(o) {}
 * 						será executada se "validate" retorna FALSE
 * 						
 * 					- event : String|Array
 * 						em quais eventos "validate" será executada
 * 						
 * 					- eventTime : int (em milesegundos)
 * 						o tempo de espera no evento para que "validate" seja executada
 * 				}
 * 		
 * 		RETORNO:
 * 			jQuery object
 * 			
 * 		EXEMPLO:
 * 
 *	  		$('.required').maskedTo(
 * 	 			'required',
 * 	 			{
 *					validate : function(o) {
 *						return o.val().length > 0;
 *					},
 *					validatedTrue : function(o) {
 *						o.css('border','1px solid #666');
 *					},
 *					validatedFalse : function(o) {
 *						o.css('border','1px solid red');
 *					},
 *					event : 'blur'
 *				}
 * 			);
 * 
 * 	VALIDANDO
 * 		
 * 		Permite fazer a validação do objeto sem que o evento tenha ocorrido.
 * 
 * 		OBS.: É necessário que se tenha definido a máscara solicitada à	
 * 			estes campos
 * 		
 * 		SINTAXE:
 * 
 * 			.maskedToNow({String}maskName[optional],{Function}callback[optional]);
 * 		
 * 		PARAMETROS:
 * 			
 * 			- maskName:String[optional]
 * 				Nome da máscara
 * 			
 * 			- callback:Function[optional]
 * 				função callback
 * 		
 * 		EXEMPLO:
 * 
 * 			- validar os campos ".required" com a máscara "required"
 * 		
 * 				$('.required').maskedToNow('required');
 * 
 * 			- validar os campos ".required" com a máscara "required" e executar
 * 				uma função callback
 * 		
 * 				$('.required').maskedToNow('required', function(result){
 * 					
 * 				});
 * 
 * 			- validar os campos com com todas as máscares definidas à eles
 * 
 * 				$('.required').maskedTo();
 * 
 * 			- validar os campos com com todas as máscares definidas e executar
 * 				uma função callback
 * 
 * 				$('.required').maskedTo(function(result) {
 * 					
 * 				});
 * 
 * 	CHECANDO
 * 
 * 		SINTAXE:
 * 			
 * 			.isMaskedTo({String}maskName[optional]);
 * 			
 * 		PARAMETROS:
 * 			
 * 			- maskName:String[optional]
 * 				Nome da máscara
 * 
 * 		RETORNO:
 * 			
 * 			Boolean TRUE se tiver a mascara solicitada, caso contrario, FALSE
 * 			
 * 		EXEMPLO
 * 			
 * 			- verificar se o objeto possui a mascara "required"
 * 
 *		  		alert($('.required').isMaskedTo('required'));
 * 			
 * 			- verificar se o objeto possui pelo menos uma máscara
 * 
 *		  		alert($('.required').isMaskedTo());
 *
 *	REMOVENDO
 * 
 * 		SINTAXE:
 * 			
 * 			.unMaskedTo({String}maskName[optional],{Function}callback[optional]);
 * 			
 * 		PARAMETROS:
 * 			
 * 			- maskName:String[optional]
 * 				Nome da máscara
 * 
 * 			- callback:Function[optional]
 * 				função callback a ser executada se pelo menos uma mascara 
 * 				for removida do objeto
 * 
 * 				PARAMENTROS:
 * 				
 * 					- objects:Object
 * 						jQuery Objetos cuja(s) máscara(s) foi(ram) removida(s)
 * 
 * 		RETORNO:
 * 			
 * 			jQuery Object
 * 			
 * 		EXEMPLO
 * 			
 * 			- remover todas as máscaras
 * 
 *		  		$('.required').unMaskedTo();
 * 			
 * 			- remover todas as máscaras e executar uma função calback
 * 
 *		  		$('.required').unMaskedTo(function(objects) {
 *					objects.each(function(){
 *						alert($(this).attr('name'));
 *					});
 *				});
 * 			
 * 			- remover a máscara "required" do objeto
 * 
 *		  		$('.required').unMaskedTo('required');
 * 			
 * 			- remover a máscara "required" do objeto e executar uma funçaõ callback
 * 
 *		  		$('.required').unMaskedTo('required', function(objects) {
 *					objects.each(function(){
 *						alert($(this).attr('name'));
 *					});
 *				});
 *		
 *	SELECIONANDO
 *
 *		- selecionar todos os objetos que possuem pelo menos uma máscara
 *		
 *			objects = $(':maskedTo');
 *		
 *		- selecionar todos os objetos que possuem a mascara "required"
 *		
 *			objects = $(':maskedTo(required)');
 *			
 *		- selecionar todos os objetos que possuem a máscara "required" e "integer"
 *		
 *			objects = $(':maskedTo(required,integer)');
 *		
 */
;(function($){
	if(!$) { return false; }
	
	/**
	 * Aplicar a mascara a um objeto
	 * 
	 * @return Object
	 */
	$.fn.maskedTo = function(maskName,options,calls) {
		var $this = $(this);
		
		options = $.fn.maskedTo.setOptions(options);
		
		if(maskName 
			&& maskName.constructor == String
			&& options ) 
		{ 
			$this.each(function(){
				$.fn.maskedTo.newMaskedTo(this, maskName, options, calls);
			});
		}
		else if(maskName && maskName.constructor == Object){
			var calls = maskName;
			$this.each(function(){
				$.fn.maskedTo.newMaskedTo(this,null,null,calls);
			});
		}
		else {
			$this.each(function(){
				$.fn.maskedTo.newMaskedTo(this);
			});
		}
		
		return $this;
	};
	
	$.fn.setMaskedToCalls = function(calls) {
		var $this = $(this);
		
		if(!calls || calls.constructor != Object) {
			return $this;
		}
		
		$this.each(function(){
			var $this = $(this);
			
			if($this.isMaskedTo()) {
				$this.getMaskedTo().setCalls(calls);
			}
			else {
				$this.maskedTo(calls);
			}
		});
		
		return $this;
	};
	
	$.fn.maskedTo.newMaskedTo = function(selector, maskName, options,calls) {
		var $this = $(selector);
		// se for objetos de entrada de dados aplica a mascara
	    if (/input|select|textarea/i.test($this[0].nodeName) 
	    	&& !/button|submit|reset/i.test($this[0].type) ) 
	    {
	    	if(maskName && options) {
		    	if(options.onReady && $.isFunction(options.onReady)) {
		    		options.onReady($this);
		    	}
				// define a mascara no objeto
				var data = $this.data('maskedTo') ? $this.data('maskedTo') : new $.fn.maskedTo.data();
				data.setCalls(calls);
				data.add(maskName, options, $this);
				
				$this.data('maskedTo',data);
	    	}
	    	else if(calls && calls.constructor == Object){
	    		var data = new $.fn.maskedTo.data();
	    		data.setCalls(calls);
	    		$this.data('maskedTo', data);
	    	}
	    	else {
	    		$this.data('maskedTo',new $.fn.maskedTo.data());
	    	}
	    }
	};
	
	/**
	 * Fazer a validação
	 * 
	 * @param {String|Function|Boolean} 
	 * 		maskName nome da máscara, função callback ou Boolean TRUE para 
	 * 		retornar o objeto $.fn.maskedTo.validateResult 
	 * @param {Function} callback[Optional] função callback ou Boolean TRUE para 
	 * 		retornar o objeto $.fn.maskedTo.validateResult
	 */
	$.fn.maskedToNow = function(maskName, callback) {
		var $this = $(this);
		// validatear agora
		var validate = new $.fn.maskedTo.validateResult();
		
		if(maskName && maskName.constructor == String){
			$this.each(function(){
				var o = $(this);
				var data = o.data('maskedTo');
				
				if(data
					//se a mascara existir
					&& data.get(maskName)) {
					data.validate(maskName,validate);
				}
			});
			
			if(callback
				&& callback.constructor == Boolean	
			) {
				return validate;
			}
			if($.isFunction(callback)) {
				callback(validate);
			}
		}
		else {
			$this.each(function(i){
				var o = $(this);
				
				if(o.data('maskedTo')) {
					o.data('maskedTo').validate(validate);
				}
			});
			
			if(maskName 
				&& maskName.constructor == Boolean	
			) {
				return validate;
			}
			if($.isFunction(maskName)) {
				maskName(validate);
			}
		}
		
		return $this;
	};
	
	$.fn.maskedTo.setOptions = function(options) {
		var defaults = {
			// verifica os dados do objeto
			validate : function($o,i) {},
			// executada se "validate" retorna TRUE
			validatedTrue : function($o) {},
			// executada se "validate" retorna FALSE
			validatedFalse : function($o) {},
			// eventos em que "validate" sera executada: (blur,change)
			events : [],
			// tempo (em milesegundos) de espera no evento atual para que
			// "validate" seja executada
			eventsTime : {}
		};
		
		options = $.extend(defaults,options);
		
		if(options.events.constructor == String)
			options.events = options.events.split(',');
		
		if(options.events.constructor != Array)
			return null;
		else {
			$.each(options.events,function(k,event) {
				if(/^.+\:\d+$/.test(event)) {
					options.events[k] = event.replace(/^(.+)\:\d+$/,'$1');
					options.eventsTime[options.events[k]] = parseInt(event.replace(/^.+\:(\d+)$/,'$1'));
				}
				else {
					options.eventsTime[event] = 0;
				}
			});
		}
		
		return options.events.constructor == Array ? options : null;
	};
	
	$.fn.getMaskedTo = function() {
		var $this = $(this);
		return $this.length > 0 ? $this.eq(0).data('maskedTo') : null;
	};
	
	/**
	 * 
	 */
	$.fn.maskedTo.data = function () {
		this.size = 0;
		this.masks = {};
		this.self = null;
		this.enabled = {};
		this.calls = {
			onBeforeAdd : function(maskName,options,data) {},
			onAfterAdd : function(maskName,options,data) {}
		};
		
		this.setCalls = function(calls) {
			if(!calls || calls.constructor != Object) return;
			this.calls = $.extend(this.calls,calls);
		};
		
		/**
		 * Adiciona um nova mascara
		 * @param {String} maskName Nome da mascara
		 * @param {Object} options Opções da máscara
		 * @return {Object} $object
		 */
		this.add = function(maskName, options, $object) {
			var _this = this;
			this.self = $object;
			
			this.calls.onBeforeAdd(maskName,options,this);
			
			if(!this.masks[maskName]) {
				this.size++;
			}
			
			this.masks[maskName] = options;
			this.enabled[maskName] = true;
			
			// adiciona o evento que ira executar a mascara
			$.each(
				this.get(maskName).events, 
				function(i,type) {
					$object.bind(type,function(event){
						_this.onValidate(maskName,event,type);
					});
				}
			);
			
			this.calls.onAfterAdd(maskName,options,this);
			
			return this;
		};
		
		this.onValidate = function(maskName, event, type) {
			var _this = this;
			
			setTimeout(function(){
				_this.validate(maskName, null, event, type);
			}, this.get(maskName).eventsTime[type]);
		};
		
		/**
		 * Executa a validação do objeto
		 * @param {String} maskName[optional] Nome da máscara a ser executada 
		 * @param {$.fn.maskedTo.validateResult} validate
		 * @return void
		 */
		this.validate = function(maskName,validateResult,event,type) {
			// validar com a mascara "maskName"
			if(maskName && this.masks[maskName]) {
				// se mascara tiver habilitada
				if(this.enabled[maskName]) {
					if(this.masks[maskName].validate(this.self,event,type)) {
						if(validateResult) {
							validateResult.addValid(this.self);
						}
						this.masks[maskName].validatedTrue(this.self);
					}
					else {
						if(validateResult) {
							validateResult.addNoValid(this.self);
						}
						this.masks[maskName].validatedFalse(this.self);
					}
				}
			}
			// validar todas as mascaras
			else {
				validateResult = maskName;
				for(var mask in this.masks) {
					if(this.enabled[mask]) {
						if(this.masks[mask].validate(this.self,event,type)) {
							if(validateResult) {
								validateResult.addValid(this.self);
							}
							this.masks[mask].validatedTrue(this.self);
						}
						else {
							if(validateResult) {
								validateResult.addNoValid(this.self);
							}
							this.masks[mask].validatedFalse(this.self);
						}
					}
				}
			}
		};
		
		/**
		 * Remove uma ou todas as mascara existentes
		 * @param {String}[optional] maskName Nome da mascara
		 * @return {Boolean} Caso remova TRUE, senao, FALSE
		 */
		this.remove = function(maskName) {
			if(maskName && this.masks[maskName]) {
				// remove os eventos
				for(var k in this.masks[maskName].event) {
					this.self.unbind(
						this.masks[maskName].event[k], 
						this.masks[maskName].onEvent
					);
				}
				// deleta a mascara
				delete this.masks[maskName];
				delete this.enabled[maskName];
				// reinicia o contador
				this.size--;
				
				return true;
			}
			// se maskName nao for passado como parametro
			else if(!maskName) {
				// remove os eventos
				for(maskName in this.masks) {
					for(var k in this.masks[maskName].event) {
						this.self.unbind(
							this.masks[maskName].event[k], 
							this.masks[maskName].onEvent
						);
					}
				}
				// reinicia a mascara
				this.masks = {};
				this.enabled = {};
				// reinicia o contador
				this.size = 0;
				return true;
			}
			else {
				return false;
			}
		};
		
		/**
		 * Habilita uma ou todas as mascara existentes
		 * @param {String}[optional] maskName Nome da mascara
		 * @return {Boolean} Caso remova TRUE, senao, FALSE
		 */
		this.enable = function(maskName) {
			if(maskName && this.masks[maskName] && this.enabled[maskName] == false) {
				this.enabled[maskName] = true;
				
				return true;
			}
			// se maskName nao for passado como parametro
			else if(!maskName) {
				// remove os eventos
				for(var mask in this.masks) {
					this.enabled[mask] = true;
				}
				
				return true;
			}
			else {
				return false;
			}
		};
		
		/**
		 * Desabilita uma ou todas as mascara existentes
		 * @param {String}[optional] maskName Nome da mascara
		 * @return {Boolean} Caso remova TRUE, senao, FALSE
		 */
		this.disable = function(maskName) {
			if(maskName && this.masks[maskName] && this.enabled[maskName] == true) {
				this.enabled[maskName] = false;
				
				return true;
			}
			// se maskName nao for passado como parametro
			else if(!maskName) {
				// remove os eventos
				for(var mask in this.masks) {
					this.enabled[mask] = false;
				}
				
				return true;
			}
			else {
				return false;
			}
		};
		
		/**
		 * Se a mascara "maskName" ou todas as máscaras estão habilitadas
		 * @param maskName
		 * @return {Boolean}
		 */
		this.isEnabled = function(maskName) {
			if(maskName && maskName.constructor == String) {
				return this.enabled[maskName];
			}
			else {
				var eLength = 0;
				for(var mask in this.masks) {
					if(this.enabled[maskName]) {
						eLength++;
					}
				}
				
				return eLength == this.size;
			}
		};
		
		/**
		 * Retornar as opcoes da mascara
		 * @param String maskName Nome da mascara
		 * @return {Object} opcoes da mascara
		 */
		this.get = function(maskName) {
			return this.masks[maskName];
		};
		
		/**
		 * Verificar este objeto possui ou nao a mascara
		 * @param String maskName Nome da mascara
		 * @return {Boolean} TRUE se possuir, caso contrario, FALSE
		 */
		this.is = function(maskName) {
			return this.masks[maskName] == Object;
		};
		
		/**
		 * Verificar este objeto passado é o mesmo SUPER
		 * @param {Object} self
		 * @return {Boolean} TRUE se for igual, caso contrario, FALSE
		 */
		this.equalSuper = function(self) {
			return this.self == self;
		};
	};
	
	/**
	 * Verificar se um objeto possui a mascara solicitada
	 * 
	 * @return Boolean
	 */
	$.fn.isMaskedTo = function(maskName) {
		
		var $this = $(this);
		var objectsLength = 0;
		
		// Se possuir a mascara "makName"
		if(maskName && maskName.constructor == String) {
			$this.each(function(){
				var o = $(this);
				
				if(o.data('maskedTo') && o.data('maskedTo').get(maskName)) {
					objectsLength++;
				}
			});
			
			return objectsLength == $this.length;
		}
		// Caso possua pelo menos uma mascara
		else {
			$this.each(function(){
				var o = $(this);
				
				if(o.data('maskedTo') && o.data('maskedTo').size > 0) {
					objectsLength++;
				}
			});
			
			return objectsLength == $this.length;
		}
	};
	
	/**
	 * Verificar se um objeto possui a mascara solicitada
	 * 
	 * @return Boolean
	 */
	$.fn.isEnabledMaskedTo = function(maskName) {
		
		var $this = $(this);
		var objectsLength = 0;
		
		// Se possuir a mascara "makName"
		if(maskName && maskName.constructor == String) {
			$this.each(function(){
				var o = $(this);
				
				if(o.data('maskedTo') 
					&& o.data('maskedTo').isEnabled(maskName)) {
					objectsLength++;
				}
			});
			
			return objectsLength == $this.length;
		}
		// Caso possua pelo menos uma mascara
		else {
			$this.each(function(){
				var o = $(this);
				
				if(o.data('maskedTo') 
					&& o.data('maskedTo').isEnabled()) {
					objectsLength++;
				}
			});
			
			return objectsLength == $this.length;
		}
	};
	
	/**
	 * Seletor personalizado para as mascaras
	 * 
	 * Ex.:
	 * 
	 * 		Todos os objetos que possum mascaras:
	 * 
	 * 			obj = $(':maskedTo');
	 * 
	 * 		Objetos que possuem a mascara "required"
	 * 
	 * 			obj = $(':maskedTo(required)');
	 * 
	 * 		Objetos que possuem as mascaras "required" e "integer"
	 * 
	 * 			obj = $(':maskedTo(required,integer)');
	 */
	$.fn.maskedTo.selector = function(domObject, index, param) {
	    // se nao sao objetos de entrada de dados retorna false
	    if (!/input|select|textarea/i.test(domObject.nodeName) 
	    	|| /button|submit|reset/i.test(domObject.type) ) 
	    {
	        return false;
	    }
		
	    // Incrementar o contador de elementos
	    arguments.callee.count++;
	    
	    // caso nao tenha passado nenhum parametro, retorna todos
	    // Ex.: ":maskedTo" ou ":maskedTo()"
	    if(!param || param[3] == null || $.trim(param[3]).length == 0) {
			return true;
	    }
	    else {
    		// uma ou varias mascaras
    		// Ex.: $(':maskedTo(required)')
    		// Ex.: $(':maskedTo(required,integer)')
	    	var masks = param[3].replace(/\s+/i,'').split(',');
	    	
	    	for(var k in masks) {
	    		if($(domObject).isMaskedTo(masks[k])) {
	    			return true;
	    		}
	    	}
	    	return false;
	    }
	};
	
	// Regisra o seletor
	$.expr[':'].maskedTo = '$.fn.maskedTo.selector(a, i, m);';
	
	/**
	 * Remover a(s) máscara(s) do(s) objeto(s)
	 * 
	 * @return Object
	 */
	$.fn.unMaskedTo = function(maskName,callback) {
		var $this = $(this);
		var objects = [];
		
		// adicionar elementos no array
		if(maskName && maskName.constructor == String){
			$this.each(function(){
				var $_this = $(this);
				var maskedToData = $_this.data('maskedTo');
				if(maskedToData && maskedToData.remove(maskName)) {
					objects.push(this);
				}
			});
			
			if($.isFunction(callback)) {
				callback($(objects));
			}
		}
		else {
			$this.each(function(){
				var maskedToData = $(this).data('maskedTo');
				if(maskedToData) {
					maskedToData.remove();
				}
			});
			
			if($.isFunction(maskName)) {
				maskName($(objects));
			}
		}
		return $this;
	};
	
	/**
	 * Habilitar a mascara para a validação
	 * 
	 * O objeto possui a mascara que esta dasativada,
	 * este metodo, ativa a mascara para a validacao
	 */
	$.fn.enableMaskedTo = function(maskName,callback) {
		var $this = $(this);
		var objects = [];
		
		// adicionar elementos no array
		if(maskName && maskName.constructor == String){
			$this.each(function(){
				var $_this = $(this);
				var maskedToData = $_this.data('maskedTo');
				if(maskedToData && maskedToData.enable(maskName)) {
					objects.push(this);
				}
			});
			
			if($.isFunction(callback)) {
				callback($(objects));
			}
		}
		else {
			$this.each(function(){
				var maskedToData = $(this).data('maskedTo');
				if(maskedToData) {
					maskedToData.enable();
				}
			});
			
			if($.isFunction(maskName)) {
				maskName($(objects));
			}
		}
		return $this;
	};
	
	/**
	 * Desabilitar a mascara para a validação
	 * 
	 * O objeto possui a mascara mas a mesma nao valida o objeto
	 */
	$.fn.disableMaskedTo = function(maskName,callback) {
		var $this = $(this);
		var objects = [];
		
		// adicionar elementos no array
		if(maskName && maskName.constructor == String){
			$this.each(function(){
				var $_this = $(this);
				var maskedToData = $_this.data('maskedTo');
				if(maskedToData && maskedToData.disable(maskName)) {
					objects.push(this);
				}
			});
			
			if($.isFunction(callback)) {
				callback($(objects));
			}
		}
		else {
			$this.each(function(){
				var maskedToData = $(this).data('maskedTo');
				if(maskedToData) {
					maskedToData.disable();
				}
			});
			
			if($.isFunction(maskName)) {
				maskName($(objects));
			}
		}
		return $this;
	};
	
	/**
	 * Resultados da validacao retornada por $.fn.maskedToNow(true)
	 */
	$.fn.maskedTo.validateResult = function() {
		this.valids	 = [];
		this.noValids = [];
		
		this.addValid = function(object) {
			this.valids.push(object);
		};
		
		this.addNoValid = function(object) {
			this.noValids.push(object);
		};
		
		this.isValid = function(object) {
			if(object.constructor == Object) {
				object = $(object);
				
				for(var k in this.valids) {
					if(this.valids[k] == object) {
						return true;
					}
				}
			}
			return false;
		};
		
		this.isNoValid = function(object) {
			if(object.constructor == Object) {
				object = $(object);
				
				for(var k in this.noValids) {
					if(this.noValids[k] == object) {
						return true;
					}
				}
			}
			return false;
		};
		
		this.validSize = function() {
			return this.valids.length;
		};
		
		this.noValidSize = function() {
			return this.noValids.length;
		};
	};
	
	/*
	 * Mascaras pre configuradas
	 * armazena o nome da mascara e suas opcoes
	 */
	$.fn.maskedTo.masks = {};
	/**
	 * retornar as opcoes da mascara solicitada
	 */
	$.fn.maskedTo.get = function(name) {
		return $.fn.maskedTo.masks[name];
	};
	
	/**
	 * Setar uma nova mascara
	 */
	$.fn.maskedTo.set = function(name,options) {
		if(name.constructor == Object) {
			$.fn.maskedTo.masks = $.extend($.fn.maskedTo.masks,name);
		}
		else if(name.constructor == String) {
			$.fn.maskedTo.masks[name] = options;
		}
	};
	
	/**
	 * Definir uma mascara existente ao um objeto atual
	 */
	$.fn.setMaskedTo = function(name) {
		var $this = $(this);
		var unIs = [];
		
		$this.each(function(){
			var $this = $(this);
			
			if($this.isMaskedTo(name)) {
				$this.data('maskedTo').enable(name);
			}
			else {
				unIs.push($this);
			}
		});
		
		if($.fn.maskedTo.masks[name]) {
			$.each(unIs,function(k, item){
				item.maskedTo(name, $.fn.maskedTo.get(name));
			});
		}
	};
})(jQuery);