/*
---

name: Meio.Mask

description: The base component for the Meio.Mask plugin.

authors:
  - Fábio Miranda Costa

requires:
  - Core/Class.Extras
  - Core/Element.Event
  - Core/Element.Style
  - More/Element.Forms

license: MIT-style license

provides: [Meio.Mask]

...
*/

(function(global, $){

	var Meio = global.Meio || {};

	// credits to Jan Kassens
	Object.append(Element.NativeEvents, {
		'paste': 2, 'input': 2
	});
	Element.Events.paste = {
		base : (Browser.Platform.ios || Browser.opera || (Browser.firefox && Browser.version < 3))? 'input': 'paste',
		condition: function(e){
			this.fireEvent('paste', e, 1);
			return false;
		}
	};

	Meio.Mask = new Class({

		Implements: [Options, Events],

		options: {
			autoTab: false

			//onInvalid: function(){},
			//onValid: function(){},

			//REVERSE MASK OPTIONS
			//autoSetSize: false,
			//autoEmpty: false,
			//alignText: true,
			//symbol: '',
			//precision: 2,
			//decimal: ',',
			//thousands: '.',
			//maxLength: 18

			//REPEAT MASK OPTIONS
			//mask: '',
			//maxLength: 0 // 0 for infinite

			//REGEXP MASK OPTIONS
			//regex: null
		},

		initialize: function(options){
			this.setOptions(options);
			this.ignore = false;
			this.bound = {focus: 0, blur: 0, keydown: 0, keypress: 0, paste: 0};
		},

		link: function(element){
			element = $(element);
			if (element.get('tag') != 'input') return;
			if (this.element) this.unlink();
			this.element = element;
			return this.attach();
		},

		unlink: function(){
			return this.detach();
		},

		attach: function(){
			var self = this;
			if (this.maxlength == null) this.maxlength = this.element.get('maxLength');
			this.element.removeAttribute('maxLength');
			for (var evt in this.bound){
				this.bound[evt] = (function(onMask, func){
					return function(e) {
						return onMask.call(self, e, func);
					}
				})(this.onMask, this[evt]);
				this.element.addEvent(evt, this.bound[evt]);
			}
			var elementValue = this.element.get('value');
			if (elementValue != '') this.element.set('value', this.mask(elementValue));
			return this;
		},

		detach: function(){
			var maxlength = this.maxlength;
			if (maxlength != null) this.element.set('maxlength', maxlength);
			for (var evt in this.bound){
				this.element.removeEvent(evt, this.bound[evt]);
				this.bound[evt] = 0;
			}
			this.element = null;
			return this;
		},

		dettach: function(){ this.detach(); }, // deprecated (incorrect syntax, sorry), use detach instead

		onMask: function(e, func){
			if (this.element.get('readonly')) return true;
			var o = {}, event = e.event, keyCode = (e.type == 'paste') ? null : event.keyCode;
			o.range = this.element.getSelectedRange();
			o.isSelection = (o.range.start !== o.range.end);
			// 8 == backspace && 46 == delete && 127 == iphone's delete
			o.isDelKey = (keyCode == 46 && (event.type != 'keypress' || ((Browser.firefox || Browser.opera) && !event.which)));
			o.isBksKey = (keyCode == 8 || (Browser.Platform.ios && e.code == 127));
			o.isRemoveKey = (o.isBksKey || o.isDelKey);
			func && func.call(this, e, o);
			return true;
		},

		keydown: function(e, o){
			this.ignore = (Meio.Mask.ignoreKeys[e.code] && !o.isRemoveKey) || e.control || e.meta || e.alt;
			if (this.ignore || o.isRemoveKey){
				var keyRepresentation = Meio.Mask.ignoreKeys[e.code] || '';
				this.fireEvent('valid', [this.element, e.code, keyRepresentation]);
			}
			return (Meio.Mask.onlyKeyDownRepeat && o.isRemoveKey) ? this.keypress(e, o) : true;
		},

		keypress: function(e, o){
			if (this.options.autoTab && this.shouldFocusNext()){
				var nextField = this.getNextInput();
				if (nextField){
					nextField.focus();
					if (nextField.select) nextField.select();
				}
			}
			return true;
		},

		focus: function(e, o){
			var element = this.element;
			element.store('meiomask:focusvalue', element.get('value'));
		},

		blur: function(e, o){
			var element = this.element;
			if (e && element.retrieve('meiomask:focusvalue') != element.get('value')){
				element.fireEvent('change');
			}
		},

		getCurrentState: function(e, o){
			var _char = String.fromCharCode(e.code),
				elValue = this.element.get('value');
			var start = o.range.start, end = o.range.end;
			if (o.isRemoveKey && !o.isSelection) o.isDelKey ? end++ : start--;
			return {value: elValue.substring(0, start) + (o.isRemoveKey ? '' : _char) + elValue.substring(end),
				_char: _char, start: start, end: end};
		},

		setSize: function(){
			if (!this.element.get('size')) this.element.set('size', this.maskArray.length);
		},

		shouldFocusNext: function(){
			var maxLength = this.options.maxLength;
			return maxLength && this.element.get('value').length >= maxLength;
		},

		getNextInput: function(){
			var fields = Array.from(this.element.form.elements), field;
			for (var i = fields.indexOf(this.element) + 1, l = fields.length; i < l; i++){
				field = fields[i];
				if (this.isFocusableField(field)) return $(field);
			}
			return null;
		},

		isFocusableField: function(field){
			return (field.offsetWidth > 0 || field.offsetHeight > 0) // is it visible?
				&& field.nodeName.toLowerCase() != 'fieldset';
		},

		isFixedChar: function(_char){
			return !Meio.Mask.matchRules.contains(_char);
		},

		mask: function(str){
			return str;
		},

		unmask: function(str){
			return str;
		}

	});

	Meio.Mask.extend({

		matchRules: '',

		rulesRegex: new RegExp(''),

		rules: {},

		setRule: function(ruleKey, properties){
			this.setRules({ruleKey: properties});
		},

		setRules: function(rulesObj){
			Object.append(this.rules, rulesObj);
			var rulesKeys = [];
			for (rule in rulesObj) rulesKeys.push(rule);
			this.matchRules += rulesKeys.join('');
			this.recompileRulesRegex();
		},

		removeRule: function(rule){
			delete this.rules[rule];
			this.matchRules = this.matchRules.replace(rule, '');
			this.recompileRulesRegex();
		},

		removeRules: function(){
			var rulesToRemove = Array.flatten(arguments);
			for (var i=rulesToRemove.length; i--;) this.removeRule(rulesToRemove[i]);
		},

		recompileRulesRegex: function(){
			this.rulesRegex.compile('[' + this.matchRules.escapeRegExp() + ']', 'g');
		},

		createMasks: function(type, masks){
			type = type.capitalize();
			for (mask in masks){
				this[type][mask.camelCase().capitalize()] = new Class({
					Extends: this[type],
					options: masks[mask]
				});
			}
		},

		// credits to Christoph Pojer's (cpojer) http://cpojer.net/
		upTo: function(number){
			number = '' + number;
			return function(value, index, _char){
				if (value.charAt(index-1) == number[0])
					return (_char <= number[1]);
				return true;
			};
		},

		// http://unixpapa.com/js/key.html
		// if only the keydown auto-repeats
		// if you have a better implementation of this detection tell me
		onlyKeyDownRepeat: !!(Browser.ie || Browser.chrome || (Browser.safari && Browser.version >= 4))

	}).extend(function(){
		var ignoreKeys;
		var desktopIgnoreKeys = {
			8		: 'backspace',
			9		: 'tab',
			13		: 'enter',
			16		: 'shift',
			17		: 'control',
			18		: 'alt',
			27		: 'esc',
			33		: 'page up',
			34		: 'page down',
			35		: 'end',
			36		: 'home',
			37		: 'left',
			38		: 'up',
			39		: 'right',
			40		: 'down',
			45		: 'insert',
			46		: 'delete',
			224		: 'command'
		},
		iphoneIgnoreKeys = {
			10		: 'go',
			127		: 'delete'
		};

		if (Browser.Platform.ios){
			ignoreKeys = iphoneIgnoreKeys;
		} else {
			// f1, f2, f3 ... f12
			for (var i=1; i<=12; i++) desktopIgnoreKeys[111 + i] = 'f' + i;
			ignoreKeys = desktopIgnoreKeys;
		}
		return {ignoreKeys: ignoreKeys};
	}())
	.setRules((function(){
		var rules = {
			'z': {regex: /[a-z]/},
			'Z': {regex: /[A-Z]/},
			'a': {regex: /[a-zA-Z]/},
			'*': {regex: /[0-9a-zA-Z]/},
			'@': {regex: /[0-9a-zA-ZçáàãâéèêíìóòõôúùüñÇÁÀÃÂÉÈÊÍÌÓÒÕÔÚÙÜÑ]/}, // 'i' regex modifier doesnt work well with unicode chars
			'h': {regex: /[0-9]/, check: Meio.Mask.upTo(23)},
			'd': {regex: /[0-9]/, check: Meio.Mask.upTo(31)},
			'm': {regex: /[0-9]/, check: Meio.Mask.upTo(12)}
		};
		for (var i=0; i<=9; i++) rules[i] = {regex: new RegExp('[0-' + i + ']')};
		return rules;
	})());

	global.Meio = Meio;

})(this, document.id);



