/*
 * 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: RC3
 * Release: 2007-07-01
 */ 
(function($) {
    //Helper Functions for Caret positioning
    getCaretPosition=function(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;
    }

    setCaretPosition=function(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);
                }                    
            }                
        });
    };
})(jQuery);