/**
 * jquery.meio.mask.js
 * @author: fabiomcosta
 * @version: 1.1.3
 *
 * Created by Fabio M. Costa on 2008-09-16. Please report any bug at http://www.meiocodigo.com
 *
 * Copyright (c) 2008 Fabio M. Costa http://www.meiocodigo.com
 *
 * The MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

(function($){

  var isIphone = (window.orientation != undefined),
    // browsers like firefox2 and before and opera doenst have the onPaste event, but the paste feature can be done with the onInput event.
    pasteEvent = (($.browser.opera || ($.browser.mozilla && parseFloat($.browser.version.substr(0,3)) < 1.9 ))? 'input': 'paste');

  $.event.special.paste = {
    setup: function() {
        if(this.addEventListener)
            this.addEventListener(pasteEvent, pasteHandler, false);
        else if (this.attachEvent)
        this.attachEvent(pasteEvent, pasteHandler);
    },

    teardown: function() {
      if(this.removeEventListener)
            this.removeEventListener(pasteEvent, pasteHandler, false);
        else if (this.detachEvent)
        this.detachEvent(pasteEvent, pasteHandler);
    }
  };

  // the timeout is set because we can't get the value from the input without it
  function pasteHandler(e){
    var self = this;
    e = $.event.fix(e || window.e);
    e.type = 'paste';
    // Execute the right handlers by setting the event type to paste
    setTimeout(function(){ $.event.handle.call(self, e); }, 1);
  };

  $.extend({
    mask : {

      // the mask rules. You may add yours!
      // number rules will be overwritten
      rules : {
        'z': /[a-z]/,
        'Z': /[A-Z]/,
        'a': /[a-zA-Z]/,
        '*': /[0-9a-zA-Z]/,
        '@': /[0-9a-zA-ZçÇáàãâéèêíìóòôõúùü]/
      },

      // these keys will be ignored by the mask.
      // all these numbers where obtained on the keydown event
      keyRepresentation : {
        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',
        116 : 'f5',
        123 : 'f12',
        224 : 'command'
      },

      iphoneKeyRepresentation : {
        10  : 'go',
        127 : 'delete'
      },

      signals : {
        '+' : '',
        '-' : '-'
      },

      // default settings for the plugin
      options : {
        attr: 'alt', // an attr to look for the mask name or the mask itself
        mask: null, // the mask to be used on the input
        type: 'fixed', // the mask of this mask
        maxLength: -1, // the maxLength of the mask
        defaultValue: '', // the default value for this input
        signal: false, // this should not be set, to use signal at masks put the signal you want ('-' or '+') at the default value of this mask.
                 // See the defined masks for a better understanding.

        textAlign: true, // use false to not use text-align on any mask (at least not by the plugin, you may apply it using css)
        selectCharsOnFocus: true, // select all chars from input on its focus
        autoTab: true, // auto focus the next form element when you type the mask completely
        setSize: false, // sets the input size based on the length of the mask (work with fixed and reverse masks only)
        fixedChars : '[(),.:/ -]', // fixed chars to be used on the masks. You may change it for your needs!

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

      // masks. You may add yours!
      // Ex: $.fn.setMask.masks.msk = {mask: '999'}
      // and then if the 'attr' options value is 'alt', your input should look like:
      // <input type="text" name="some_name" id="some_name" alt="msk" />
      masks : {
        'phone'       : { mask : '(99) 9999-9999' },
        'phone-us'      : { mask : '(999) 999-9999' },
        'cpf'       : { mask : '999.999.999-99' }, // cadastro nacional de pessoa fisica
        'rg'        : { mask : '99999999-4' }, // identidade brasil - pedro casado 16-04-2010
        'cnpj'        : { mask : '99.999.999/9999-99' },
        'date'        : { mask : '39/19/9999' }, //uk date
        'date-us'     : { mask : '19/39/9999' },
        'cep'       : { mask : '99999-999' },
        'time'        : { mask : '29:59' },
        'cc'        : { mask : '9999 9999 9999 9999' }, //credit card mask
        'integer'     : { mask : '999.999.999.999', type : 'reverse' },
        'decimal'     : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '000' },
        'decimal-us'    : { mask : '99.999,999,999,999', type : 'reverse', defaultValue : '000' },
        'signed-decimal'  : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '+000' },
        'signed-decimal-us' : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '+000' },
        'msk-es'      : { mask : '99.999,999,999,999', type : 'reverse' },
        'msk-pt'      : { mask : '99,999.999.999.999', type : 'reverse' }
      },

      init : function(){
        // if has not inited...
        if( !this.hasInit ){

          var self = this, i,
            keyRep = (isIphone)? this.iphoneKeyRepresentation: this.keyRepresentation;

          this.ignore = false;

          // constructs number rules
          for(i=0; i<=9; i++) this.rules[i] = new RegExp('[0-'+i+']');

          this.keyRep = keyRep;
          // ignore keys array creation for iphone or the normal ones
          this.ignoreKeys = [];
          $.each(keyRep,function(key){
            self.ignoreKeys.push( parseInt(key) );
          });

          this.hasInit = true;
        }
      },

      set: function(el,options){

        var maskObj = this,
          $el = $(el),
          mlStr = 'maxLength';

        options = options || {};
        this.init();

        return $el.each(function(){

          if(options.attr) maskObj.options.attr = options.attr;

          var $this = $(this),
            o = $.extend({}, maskObj.options),
            attrValue = $this.attr(o.attr),
            tmpMask = '';

          // then we look for the 'attr' option
          tmpMask = (typeof options == 'string')? options: (attrValue != '')? attrValue: null;
          if(tmpMask) o.mask = tmpMask;

          // then we see if it's a defined mask
          if(maskObj.masks[tmpMask]) o = $.extend(o, maskObj.masks[tmpMask]);

          // then it looks if the options is an object, if it is we will overwrite the actual options
          if(typeof options == 'object' && options.constructor != Array) o = $.extend(o, options);

          //then we look for some metadata on the input
          if($.metadata) o = $.extend(o, $this.metadata());

          if(o.mask != null){

            if($this.data('mask')) maskObj.unset($this);

            var defaultValue = o.defaultValue,
              reverse = (o.type=='reverse'),
              fixedCharsRegG = new RegExp(o.fixedChars, 'g');

            if(o.maxLength == -1) o.maxLength = $this.attr(mlStr);

            o = $.extend({}, o,{
              fixedCharsReg: new RegExp(o.fixedChars),
              fixedCharsRegG: fixedCharsRegG,
              maskArray: o.mask.split(''),
              maskNonFixedCharsArray: o.mask.replace(fixedCharsRegG, '').split('')
            });

            //setSize option (this is not removed from the input (while removing the mask) since this would be kind of funky)
            if((o.type=='fixed' || reverse) && o.setSize && !$this.attr('size')) $this.attr('size', o.mask.length);

            //sets text-align right for reverse masks
            if(reverse && o.textAlign) $this.css('text-align', 'right');

            if(this.value!='' || defaultValue!=''){
              // apply mask to the current value of the input or to the default value
              var val = maskObj.string((this.value!='')? this.value: defaultValue, o);
              //setting defaultValue fixes the reset button from the form
              this.defaultValue = val;
              $this.val(val);
            }

            // compatibility patch for infinite mask, that is now repeat
            if(o.type=='infinite') o.type = 'repeat';

            $this.data('mask', o);

            // removes the maxLength attribute (it will be set again if you use the unset method)
            $this.removeAttr(mlStr);

            // setting the input events
            $this.bind('keydown.mask', {func:maskObj._onKeyDown, thisObj:maskObj}, maskObj._onMask)
              .bind('keypress.mask', {func:maskObj._onKeyPress, thisObj:maskObj}, maskObj._onMask)
              .bind('keyup.mask', {func:maskObj._onKeyUp, thisObj:maskObj}, maskObj._onMask)
              .bind('paste.mask', {func:maskObj._onPaste, thisObj:maskObj}, maskObj._onMask)
              .bind('focus.mask', maskObj._onFocus)
              .bind('blur.mask', maskObj._onBlur)
              .bind('change.mask', maskObj._onChange);
          }
        });
      },

      //unsets the mask from el
      unset : function(el){
        var $el = $(el);

        return $el.each(function(){
          var $this = $(this);
          if($this.data('mask')){
            var maxLength = $this.data('mask').maxLength;
            if(maxLength != -1) $this.attr('maxLength', maxLength);
            $this.unbind('.mask')
              .removeData('mask');
          }
        });
      },

      //masks a string
      string : function(str, options){
        this.init();
        var o={};
        if(typeof str != 'string') str = String(str);
        switch(typeof options){
          case 'string':
            // then we see if it's a defined mask
            if(this.masks[options]) o = $.extend(o, this.masks[options]);
            else o.mask = options;
            break;
          case 'object':
            o = options;
        }
        if(!o.fixedChars) o.fixedChars = this.options.fixedChars;

        var fixedCharsReg = new RegExp(o.fixedChars),
          fixedCharsRegG = new RegExp(o.fixedChars, 'g');

        // insert signal if any
        if( (o.type=='reverse') && o.defaultValue ){
          if( typeof this.signals[o.defaultValue.charAt(0)] != 'undefined' ){
            var maybeASignal = str.charAt(0);
            o.signal = (typeof this.signals[maybeASignal] != 'undefined') ? this.signals[maybeASignal] : this.signals[o.defaultValue.charAt(0)];
            o.defaultValue = o.defaultValue.substring(1);
          }
        }

        return this.__maskArray(str.split(''),
              o.mask.replace(fixedCharsRegG, '').split(''),
              o.mask.split(''),
              o.type,
              o.maxLength,
              o.defaultValue,
              fixedCharsReg,
              o.signal);
      },

      // all the 3 events below are here just to fix the change event on reversed masks.
      // It isn't fired in cases that the keypress event returns false (needed).
      _onFocus: function(e){
        var $this = $(this), dataObj = $this.data('mask');
        dataObj.inputFocusValue = $this.val();
        dataObj.changed = false;
        if(dataObj.selectCharsOnFocus) $this.select();
      },

      _onBlur: function(e){
        var $this = $(this), dataObj = $this.data('mask');
        if(dataObj.inputFocusValue != $this.val() && !dataObj.changed)
          $this.trigger('change');
      },

      _onChange: function(e){
        $(this).data('mask').changed = true;
      },

      _onMask : function(e){
        var thisObj = e.data.thisObj,
          o = {};
        o._this = e.target;
        o.$this = $(o._this);
        // if the input is readonly it does nothing
        if(o.$this.attr('readonly')) return true;
        o.data = o.$this.data('mask');
        o[o.data.type] = true;
        o.value = o.$this.val();
        o.nKey = thisObj.__getKeyNumber(e);
        o.range = thisObj.__getRange(o._this);
        o.valueArray = o.value.split('');
        return e.data.func.call(thisObj, e, o);
      },

      _onKeyDown : function(e,o){
        // lets say keypress at desktop == keydown at iphone (theres no keypress at iphone)
        this.ignore = $.inArray(o.nKey, this.ignoreKeys) > -1 || e.ctrlKey || e.metaKey || e.altKey;
        if(this.ignore){
          var rep = this.keyRep[o.nKey];
          o.data.onValid.call(o._this, rep? rep: '', o.nKey);
        }
        return isIphone ? this._keyPress(e, o) : true;
      },

      _onKeyUp : function(e, o){
        //9=TAB_KEY 16=SHIFT_KEY
        //this is a little bug, when you go to an input with tab key
        //it would remove the range selected by default, and that's not a desired behavior
        if(o.nKey==9 || o.nKey==16) return true;

        if(o.data.type=='repeat'){
          this.__autoTab(o);
          return true;
        }

        return this._onPaste(e, o);
      },

      _onPaste : function(e,o){
        // changes the signal at the data obj from the input
        if(o.reverse) this.__changeSignal(e.type, o);

        var $thisVal = this.__maskArray(
          o.valueArray,
          o.data.maskNonFixedCharsArray,
          o.data.maskArray,
          o.data.type,
          o.data.maxLength,
          o.data.defaultValue,
          o.data.fixedCharsReg,
          o.data.signal
        );

        o.$this.val( $thisVal );
        // this makes the caret stay at first position when
        // the user removes all values in an input and the plugin adds the default value to it (if it haves one).
        if( !o.reverse && o.data.defaultValue.length && (o.range.start==o.range.end) )
          this.__setRange(o._this, o.range.start, o.range.end);

        //fix so ie's and safari's caret won't go to the end of the input value.
        if( ($.browser.msie || $.browser.safari) && !o.reverse)
          this.__setRange(o._this,o.range.start,o.range.end);

        if(this.ignore) return true;

        this.__autoTab(o);
        return true;
      },

      _onKeyPress: function(e, o){

        if(this.ignore) return true;

        // changes the signal at the data obj from the input
        if(o.reverse) this.__changeSignal(e.type, o);

        var c = String.fromCharCode(o.nKey),
          rangeStart = o.range.start,
          rawValue = o.value,
          maskArray = o.data.maskArray;

        if(o.reverse){
            // the input value from the range start to the value start
          var valueStart = rawValue.substr(0, rangeStart),
            // the input value from the range end to the value end
            valueEnd = rawValue.substr(o.range.end, rawValue.length);

          rawValue = valueStart+c+valueEnd;
          //necessary, if not decremented you will be able to input just the mask.length-1 if signal!=''
          //ex: mask:99,999.999.999 you will be able to input 99,999.999.99
          if(o.data.signal && (rangeStart-o.data.signal.length > 0)) rangeStart-=o.data.signal.length;
        }

        var valueArray = rawValue.replace(o.data.fixedCharsRegG, '').split(''),
          // searches for fixed chars begining from the range start position, till it finds a non fixed
          extraPos = this.__extraPositionsTill(rangeStart, maskArray, o.data.fixedCharsReg);

        o.rsEp = rangeStart+extraPos;

        if(o.repeat) o.rsEp = 0;

        // if the rule for this character doesnt exist (value.length is bigger than mask.length)
        // added a verification for maxLength in the case of the repeat type mask
        if( !this.rules[maskArray[o.rsEp]] || (o.data.maxLength != -1 && valueArray.length >= o.data.maxLength && o.repeat)){
          // auto focus on the next input of the current form
          o.data.onOverflow.call(o._this, c, o.nKey);
          return false;
        }

        // if the new character is not obeying the law... :P
        else if( !this.rules[maskArray[o.rsEp]].test( c ) ){
          o.data.onInvalid.call(o._this, c, o.nKey);
          return false;
        }

        else o.data.onValid.call(o._this, c, o.nKey);

        var $thisVal = this.__maskArray(
          valueArray,
          o.data.maskNonFixedCharsArray,
          maskArray,
          o.data.type,
          o.data.maxLength,
          o.data.defaultValue,
          o.data.fixedCharsReg,
          o.data.signal,
          extraPos
        );

        o.$this.val( $thisVal );

        return (o.reverse)? this._keyPressReverse(e, o): (o.fixed)? this._keyPressFixed(e, o): true;
      },

      _keyPressFixed: function(e, o){

        if(o.range.start==o.range.end){
          // the 0 thing is cause theres a particular behavior i wasnt liking when you put a default
          // value on a fixed mask and you select the value from the input the range would go to the
          // end of the string when you enter a char. with this it will overwrite the first char wich is a better behavior.
          // opera fix, cant have range value bigger than value length, i think it loops thought the input value...
          if((o.rsEp==0 && o.value.length==0) || o.rsEp < o.value.length)
            this.__setRange(o._this, o.rsEp, o.rsEp+1);
        }
        else
          this.__setRange(o._this, o.range.start, o.range.end);

        return true;
      },

      _keyPressReverse: function(e, o){
        //fix for ie
        //this bug was pointed by Pedro Martins
        //it fixes a strange behavior that ie was having after a char was inputted in a text input that
        //had its content selected by any range
        if($.browser.msie && ((o.range.start==0 && o.range.end==0) || o.range.start != o.range.end ))
          this.__setRange(o._this, o.value.length);
        return false;
      },

      __autoTab: function(o){
        if(o.data.autoTab
          && (
            (
              o.$this.val().length >= o.data.maskArray.length
              && !o.repeat
            ) || (
              o.data.maxLength != -1
              && o.valueArray.length >= o.data.maxLength
              && o.repeat
            )
          )
        ){
          var nextEl = this.__getNextInput(o._this, o.data.autoTab);
          if(nextEl){
            o.$this.trigger('blur');
            nextEl.focus().select();
          }
        }
      },

      // changes the signal at the data obj from the input
      __changeSignal : function(eventType,o){
        if(o.data.signal!==false){
          var inputChar = (eventType=='paste')? o.value.charAt(0): String.fromCharCode(o.nKey);
          if( this.signals && (typeof this.signals[inputChar] != 'undefined') ){
            o.data.signal = this.signals[inputChar];
          }
        }
      },

      __getKeyNumber : function(e){
        return (e.charCode||e.keyCode||e.which);
      },

      // this function is totaly specific to be used with this plugin, youll never need it
      // it gets the array representing an unmasked string and masks it depending on the type of the mask
      __maskArray : function(valueArray, maskNonFixedCharsArray, maskArray, type, maxlength, defaultValue, fixedCharsReg, signal, extraPos){
        if(type == 'reverse') valueArray.reverse();
        valueArray = this.__removeInvalidChars(valueArray, maskNonFixedCharsArray, type=='repeat'||type=='infinite');
        if(defaultValue) valueArray = this.__applyDefaultValue.call(valueArray, defaultValue);
        valueArray = this.__applyMask(valueArray, maskArray, extraPos, fixedCharsReg);
        switch(type){
          case 'reverse':
            valueArray.reverse();
            return (signal || '')+valueArray.join('').substring(valueArray.length-maskArray.length);
          case 'infinite': case 'repeat':
            var joinedValue = valueArray.join('');
            return (maxlength != -1 && valueArray.length >= maxlength)? joinedValue.substring(0, maxlength): joinedValue;
          default:
            return valueArray.join('').substring(0, maskArray.length);
        }
        return '';
      },

      // applyes the default value to the result string
      __applyDefaultValue : function(defaultValue){
        var defLen = defaultValue.length,thisLen = this.length,i;
        //removes the leading chars
        for(i=thisLen-1;i>=0;i--){
          if(this[i]==defaultValue.charAt(0)) this.pop();
          else break;
        }
        // apply the default value
        for(i=0;i<defLen;i++) if(!this[i])
          this[i] = defaultValue.charAt(i);

        return this;
      },

      // Removes values that doesnt match the mask from the valueArray
      // Returns the array without the invalid chars.
      __removeInvalidChars : function(valueArray, maskNonFixedCharsArray, repeatType){
        // removes invalid chars
        for(var i=0, y=0; i<valueArray.length; i++ ){
          if( maskNonFixedCharsArray[y] &&
            this.rules[maskNonFixedCharsArray[y]] &&
            !this.rules[maskNonFixedCharsArray[y]].test(valueArray[i]) ){
              valueArray.splice(i,1);
              if(!repeatType) y--;
              i--;
          }
          if(!repeatType) y++;
        }
        return valueArray;
      },

      // Apply the current input mask to the valueArray and returns it.
      __applyMask : function(valueArray, maskArray, plus, fixedCharsReg){
        if( typeof plus == 'undefined' ) plus = 0;
        // apply the current mask to the array of chars
        for(var i=0; i<valueArray.length+plus; i++ ){
          if( maskArray[i] && fixedCharsReg.test(maskArray[i]) )
            valueArray.splice(i, 0, maskArray[i]);
        }
        return valueArray;
      },

      // searches for fixed chars begining from the range start position, till it finds a non fixed
      __extraPositionsTill : function(rangeStart, maskArray, fixedCharsReg){
        var extraPos = 0;
        while(fixedCharsReg.test(maskArray[rangeStart++])){
          extraPos++;
        }
        return extraPos;
      },

      __getNextInput: function(input, selector){
        var formEls = input.form.elements,
          initialInputIndex = $.inArray(input, formEls) + 1,
          $input = null,
          i;
        // look for next input on the form of the pased input
        for(i = initialInputIndex; i < formEls.length; i++){
          $input = $(formEls[i]);
          if(this.__isNextInput($input, selector))
            return $input;
        }

        var forms = document.forms,
          initialFormIndex = $.inArray(input.form, forms) + 1,
          y, tmpFormEls = null;
        // look for the next forms for the next input
        for(y = initialFormIndex; y < forms.length; y++){
          tmpFormEls = forms[y].elements;
          for(i = 0; i < tmpFormEls.length; i++){
            $input = $(tmpFormEls[i]);
            if(this.__isNextInput($input, selector))
              return $input;
          }
        }
        return null;
      },

      __isNextInput: function($formEl, selector){
        var formEl = $formEl.get(0);
        return formEl
          && (formEl.offsetWidth > 0 || formEl.offsetHeight > 0)
          && formEl.nodeName != 'FIELDSET'
          && (selector === true || (typeof selector == 'string' && $formEl.is(selector)));
      },

      // http://www.bazon.net/mishoo/articles.epl?art_id=1292
      __setRange : function(input, start, end) {
        if(typeof end == 'undefined') end = start;
        if (input.setSelectionRange){
          input.setSelectionRange(start, end);
        }
        else{
          // assumed IE
          var range = input.createTextRange();
          range.collapse();
          range.moveStart('character', start);
          range.moveEnd('character', end - start);
          range.select();
        }
      },

      // adaptation from http://digitarald.de/project/autocompleter/
      __getRange : function(input){
        if (!$.browser.msie) return {start: input.selectionStart, end: input.selectionEnd};
        var pos = {start: 0, end: 0},
          range = document.selection.createRange();
        pos.start = 0 - range.duplicate().moveStart('character', -100000);
        pos.end = pos.start + range.text.length;
        return pos;
      },

      //deprecated
      unmaskedVal : function(el){
        return $(el).val().replace($.mask.fixedCharsRegG, '');
      }

    }
  });

  $.fn.extend({
    setMask : function(options){
      return $.mask.set(this, options);
    },
    unsetMask : function(){
      return $.mask.unset(this);
    },
    //deprecated
    unmaskedVal : function(){
      return $.mask.unmaskedVal(this[0]);
    }
  });
})(jQuery);



