/*
* Copyright (c) 2007 Josh Bush (digitalbush.com)
*
* 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.
*/

/*
* Version: 1.0
* Release: 2007-07-25
*/
(function($) {
  //Helper Functions for Caret positioning
  function getCaretPosition(ctl){
    var res = {begin: 0, end: 0 };
    if (ctl.setSelectionRange){
      res.begin = ctl.selectionStart;
      res.end = ctl.selectionEnd;
    }else if (document.selection && document.selection.createRange){
      var range = document.selection.createRange();
      res.begin = 0 - range.duplicate().moveStart('character', -100000);
      res.end = res.begin + range.text.length;
    }
    return res;
  };

  function setCaretPosition(ctl, pos){
    if(ctl.setSelectionRange){
      ctl.focus();
      ctl.setSelectionRange(pos,pos);
    }else if (ctl.createTextRange){
      var range = ctl.createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
    }
  };

  //Predefined character definitions
  var charMap={
  '9':"[0-9]",
  'a':"[A-Za-z]",
  '*':"[A-Za-z0-9]"
  };

  //Helper method to inject character definitions
  $.mask={
    addPlaceholder : function(c,r){
      charMap[c]=r;
    }
  };

  //Main Method
  $.fn.mask = function(mask,settings) {
    settings = $.extend({
      placeholder: "_",
      completed: null
    }, settings);

    //Build Regex for format validation
    var reString="^";
    for(var i=0;i<mask.length;i++)
    reString+=(charMap[mask.charAt(i)] || ("\\"+mask.charAt(i)));
    reString+="$";
    var re = new RegExp(reString);

    return this.each(function(){
      var input=$(this);
      var buffer=new Array(mask.length);
      var locked=new Array(mask.length);

      //Build buffer layout from mask
      for(var i=0;i<mask.length;i++){
        locked[i]=charMap[mask.charAt(i)]==null;
        buffer[i]=locked[i]?mask.charAt(i):settings.placeholder;
      }

      /*Event Bindings*/
      input.focus(function(){
        checkVal();
        writeBuffer();
        setCaretPosition(this,0);
      });

      input.blur(checkVal);

      //Paste events for IE and Mozilla thanks to Kristinn Sigmundsson
      if ($.browser.msie)
      this.onpaste= function(){setTimeout(checkVal,0);};
      else if ($.browser.mozilla)
      this.addEventListener('input',checkVal,false);

      var ignore=false;  //Variable for ignoring control keys

      input.keydown(function(e){
        var pos=getCaretPosition(this);
        var k = e.keyCode;
        ignore=(k < 16 || (k > 16 && k < 32 ) || (k > 32 && k < 41));

        //delete selection before proceeding
        if((pos.begin-pos.end)!=0 && (!ignore || k==8 || k==46)){
          clearBuffer(pos.begin,pos.end);
        }
        //backspace and delete get special treatment
        if(k==8){//backspace
          while(pos.begin-->=0){
            if(!locked[pos.begin]){
              buffer[pos.begin]=settings.placeholder;
              if($.browser.opera){
                //Opera won't let you cancel the backspace, so we'll let it backspace over a dummy character.
                writeBuffer(pos.begin);
                setCaretPosition(this,pos.begin+1);
              }else{
                writeBuffer();
                setCaretPosition(this,pos.begin);
              }
              return false;
            }
          }
        }else if(k==46){//delete
          clearBuffer(pos.begin,pos.begin+1);
          writeBuffer();
          setCaretPosition(this,pos.begin);
          return false;
        }else if (k==27){
          clearBuffer(0,mask.length);
          writeBuffer();
          setCaretPosition(this,0);
          return false;
        }

      });

      input.keypress(function(e){
        if(ignore){
          ignore=false;
          return;
        }
        e=e||window.event;
        var k=e.charCode||e.keyCode||e.which;

        var pos=getCaretPosition(this);
        var caretPos=pos.begin;

        if(e.ctrlKey || e.altKey){//Ignore
          return true;
        }else if ((k>=41 && k<=122) ||k==32 || k>186){//typeable characters
          while(pos.begin<mask.length){
            var reString=charMap[mask.charAt(pos.begin)];
            var match;
            if(reString){
              var reChar=new RegExp(reString);
              match=String.fromCharCode(k).match(reChar);
            }else{//we're on a mask char, go forward and try again
              pos.begin+=1;
              pos.end=pos.begin;
              caretPos+=1;
              continue;
            }

            if(match)
            buffer[pos.begin]=String.fromCharCode(k);
            else
            return false;//reject char

            while(++caretPos<mask.length){//seek forward to next typable position
              if(!locked[caretPos])
              break;
            }
            break;
          }
        }else
        return false;

        writeBuffer();
        if(settings.completed && caretPos>=buffer.length)
        settings.completed.call(input);
        else
        setCaretPosition(this,caretPos);

        return false;
      });

      /*Helper Methods*/
      function clearBuffer(start,end){
        for(var i=start;i<end;i++){
          if(!locked[i])
          buffer[i]=settings.placeholder;
        }
      };

      function writeBuffer(pos){
        var s="";
        for(var i=0;i<mask.length;i++){
          s+=buffer[i];
          if(i==pos)
          s+=settings.placeholder;
        }
        input.val(s);
        return s;
      };

      function checkVal() {
        //try to place charcters where they belong
        var test=input.val();
        var pos=0;
        for(var i=0;i<mask.length;i++){
          if(!locked[i]){
            while(pos++<test.length){
              //Regex Test each char here.
              var reChar=new RegExp(charMap[mask.charAt(i)]);
              if(test.charAt(pos-1).match(reChar)){
                buffer[i]=test.charAt(pos-1);
                break;
              }
            }
          }
        }
        var s=writeBuffer();
        if(!s.match(re)){
          input.val("");
          clearBuffer(0,mask.length);
        }
      };

      this._checkVal = checkVal; // allows later remove
    });
  };
  $.fn.unmask = function() {
    this.unbind();
    var el = this.get(0);
    if ($.browser.msie) {
      el.onpaste = null;
    } else if ($.browser.mozilla) {
      el.removeEventListener('input', el._checkVal, false);
    }
    el._checkVal = null;
  };
})(jQuery);
