/*
a = [A-z]/i, [0-9]
A = [À-ÿ], [0-9], [A-Z]
9 = [0-9]
C = [À-ÿ]/i, [A-Z]
c = [A-Z]
* = .
money = money format -00.000,00
*/
(function(jQuery){
	var patterns = {
		'1': /[A-Z]/i, 
		'2': /[0-9]/, 
		'4': /[À-ÿ]/i, 
		'8': /./, 
		'16': /\s/};
	var rules = {
		'a': 3, 
		'A': 7, 
		'9': 2, 
		'C':5, 
		'c': 1, 
		'*': 8, 
		'W': 21};
	var allpatterns = /([A-Z]|[0-9]|[À-ÿ])/i;
	var rulepattern = /[A|9|C|W|\*]/i;

	function accept(c, rule){
		for(var i = 1, r = rules[rule] || 0; i <= r; i <<= 1){
			if(r & i && patterns[i].test(c)){
				break;
			}
		}
		return i <= r || c == rule;
	}

	jQuery.fn.mask = function(mask, options)
	{
		function inputmask(e){
			var selected = jQuery(this).selection().length;
			var selection = jQuery(this).getSelection();
			var start = Math.min(selection.start, selection.end);
			var end = Math.max(selection.start, selection.end);
			var c = String.fromCharCode(k = (e.charCode?e.charCode:e.keyCode));
			if(options.maxlength){
				mask = mask.pad(jQuery(this).attr('maxlength'), mask);
			}
			if(!k || k == 9 || k == 116 || k == 37 || k == 38 || k == 39 || k == 40 || k == 16 || k == 35 || k == 36 || k == 13){
				return true;
			}
			var allow = false;
			if(k == 8 || k == 46){ // Backspace and Delete
				allow = true;
				if(selected == 0){
					if(k == 8){
						start--;
						if(rulepattern.test(mask)){
							var rule = mask.substr(start, 1);
							while(!(rulepattern.test(rule))){
								start--;
								var rule = mask.substr(start, 1);
							}
						}
					}else{
						var rule = mask.substr(start, 1);
						if(rulepattern.test(mask)){
							while(!(rulepattern.test(rule))){
								start++;
								var rule = mask.substr(start, 1);
							}
							end = start;
							jQuery(this).one('keyup', function(){
								jQuery(this).setSelection({start:selection.start});
							});
						}
					}
				}
				jQuery(this).setSelection({'start': start, 'end': end});
			}else if(/m|n/i.test(mask)){
				if((c == '-' && selection.start == 0 && this.value.indexOf('-') == -1) || (/[0-9]/.test(c)) || (c == options.decimal && this.value.indexOf(options.decimal) == -1)){
					allow = true;
				}
				if(c == options.decimal){
					jQuery(this).one('keyup', function(){
						jQuery(this).setSelection({start: this.value.indexOf(options.decimal) + 1});
					});
				}
			}else if(this.value.length - selected < mask.length){
				var rule = '';
				var i = start;
				do{
					var rule = mask.substr(i, 1);
					if(rulepattern.test(rule)){
						if(accept(c, rule)){
							allow = true;
							start++;
							end = start;
						}
					}else{
						allow = false;
						start++;
						end = start;
					}
					i++;
				}while(!(rulepattern.test(rule)) && i < mask.length);
			}
			return allow;
		}
		
		function inputformat(e){
			var k = (e.charCode?e.charCode:e.keyCode);
			if(!k || k == 9 || k == 116 || k == 37 || k == 38 || k == 39 || k == 40 || k == 16 || k == 35 || k == 36 || k == 13){
				return true;
			}
			format(this, mask, options);
		}

		var options = jQuery.extend({
			maxlength: false,
			decimal: ',',
			thousands: '.',
			decimalPlace: 2}, options);

		this.each(function(){
			if(!(/m|n/i.test(mask))){
				jQuery(this).attr('maxlength', mask.length).attr('size', mask.length);
			}
			if(this._inputmask){
				jQuery(this).unkeypress(this._inputmask).unkeyup(this._inputformat);
			}
			this._inputmask = inputmask;
			this._inputformat = inputformat;
			jQuery(this).format(mask, options).keypress(inputmask).keyup(inputformat);
		});
		return this;
	}
	
	function format(element, mask, options){
		var options = jQuery.extend({
			maxlength: false,
			decimal: ',',
			thousands: '.'}, options || {});
		var originalSelection = jQuery(element).getSelection();
		var val = element.value;
		var selection = {start:originalSelection.start, end:originalSelection.end};
		var length = Math.min(mask.length, val.length);
		var value = '';
		var formated = '';
		if(/m|n/i.test(mask)){
			if(val.trim() != ''){
				var value = val;
				var originalThousands = 0;
				if(!isFinite(value)){
					while(value.indexOf(options.thousands) != -1){
						value = value.replace(options.thousands, '');
						originalThousands++;
					}
					value = value.replace(options.decimal, '.');
				}
				value = parseFloat(value);
				if(/m/i.test(mask)){
					formated = value.toMoney(options.decimalPlace, options.decimal, options.thousands);
				}else{
					formated = value.toMoney(0, options.decimal, options.thousands);
				}
				var thousands = 0;
				var pos = 0;
				while((pos = formated.indexOf(options.thousands, pos)) != -1){
					pos++;
					thousands++;
				}
				if(thousands > originalThousands){
					selection.start += (thousands - originalThousands);
					selection.end += (thousands - originalThousands);
				}
			}else{
				formated = '';
			}
		}else{
			for(var i = 0; i < val.length; i++){
				var c = val.substr(i, 1);
				if(allpatterns.test(c)){
					value += c;
				}else{
					if(i < originalSelection.start){
						selection.start--;
						selection.end--;
					}else if(i < selection.end){
						selection.end--;
					}
				}
			}
			if(value.trim() == ''){
				element.value = '';
				return true;
			}
			var vali = 0;
			for(var i = 0; vali < value.length && i < mask.length; i++){
				var rule = mask.substr(i, 1);
				var c = value.substr(vali, 1);
				if(!c){
					c = '';
				}
				if(rulepattern.test(rule)){
					if(accept(c, rule)){
						formated += c;
					}
					vali++;
				}else{
					formated += rule;
					if(i < originalSelection.start){
						selection.start++;
						selection.end++;
					}else if(i < originalSelection.end){
						selection.end++;
					}
				}
			}
			if(i < mask.length){
				i--;
				do{
					var rule = mask.substr(i, 1);
					if(!rulepattern.test(rule)){
						formated += rule;
						if(i < originalSelection.start || originalSelection.start == val.length){
							selection.start++;
							selection.end = selection.start;
						}
					}
					i++;
				}while(!(rulepattern.test(mask.substr(i, 1))) && i < mask.length);
			}
		}
		element.value = formated;
		jQuery(element).setSelection(selection);
	}

	jQuery.fn.format = function(mask, options){
		this.each(function(){
			format(this, mask, options);
		});
		return this;
	}
})(jQuery);