/*

 * Copyright (c) 2007-2008 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.1.4

 * Release: 2008-07-29

 */ 

(function($) {



	//Helper Function for Caret positioning

	$.fn.caret=function(begin,end){	

		if(this.length==0) return;

		if (typeof begin == 'number') {

            end = (typeof end == 'number')?end:begin;  

			return this.each(function(){

				if(this.setSelectionRange){

					this.focus();

					this.setSelectionRange(begin,end);

				}else if (this.createTextRange){

					var range = this.createTextRange();

					range.collapse(true);

					range.moveEnd('character', end);

					range.moveStart('character', begin);

					range.select();

				}

			});

        } else {

            if (this[0].setSelectionRange){

				begin = this[0].selectionStart;

				end = this[0].selectionEnd;

			}else if (document.selection && document.selection.createRange){

				var range = document.selection.createRange();			

				begin = 0 - range.duplicate().moveStart('character', -100000);

				end = begin + range.text.length;

			}

			return {begin:begin,end:end};

        }       

	};



	//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;

		}

	};

	

	$.fn.unmask=function(){

		return this.trigger("unmask");

	};

	

	//Main Method

	$.fn.mask = function(mask,settings) {	

		settings = $.extend({

			placeholder: "_",			

			completed: null

		}, settings);		

		

		//Build Regex for format validation

		var re = new RegExp("^"+	

		$.map( mask.split(""), function(c,i){		  		  

		  return charMap[c]||((/[A-Za-z0-9]/.test(c)?"":"\\")+c);

		}).join('')+				

		"$");		



		return this.each(function(){		

			var input=$(this);

			var buffer=new Array(mask.length);

			var locked=new Array(mask.length);

			var valid=false;   

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

			var firstNonMaskPos=null; 

			

			//Build buffer layout from mask & determine the first non masked character			

			$.each( mask.split(""), function(i,c){				

				locked[i]=(charMap[c]==null);				

				buffer[i]=locked[i]?c:settings.placeholder;									

				if(!locked[i] && firstNonMaskPos==null)

					firstNonMaskPos=i;

			});		

			

			function focusEvent(){					

				checkVal();

				writeBuffer();

				setTimeout(function(){

					$(input[0]).caret(valid?mask.length:firstNonMaskPos);					

				},0);

			};

			

			function keydownEvent(e){				

				var pos=$(this).caret();

				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.								

								s=writeBuffer();

								input.val(s.substring(0,pos.begin)+" "+s.substring(pos.begin));

								$(this).caret(pos.begin+1);								

							}else{

								writeBuffer();

								$(this).caret(Math.max(firstNonMaskPos,pos.begin));								

							}									

							return false;								

						}

					}						

				}else if(k==46){//delete

					clearBuffer(pos.begin,pos.begin+1);

					writeBuffer();

					$(this).caret(Math.max(firstNonMaskPos,pos.begin));					

					return false;

				}else if (k==27){//escape

					clearBuffer(0,mask.length);

					writeBuffer();

					$(this).caret(firstNonMaskPos);					

					return false;

				}									

			};

			

			function keypressEvent(e){					

				if(ignore){

					ignore=false;

					//Fixes Mac FF bug on backspace

					return (e.keyCode == 8)? false: null;

				}

				e=e||window.event;

				var k=e.charCode||e.keyCode||e.which;						

				var pos=$(this).caret();

								

				if(e.ctrlKey || e.altKey){//Ignore

					return true;

				}else if ((k>=41 && k<=122) ||k==32 || k>186){//typeable characters

					var p=seekNext(pos.begin-1);					

					if(p<mask.length){

						if(new RegExp(charMap[mask.charAt(p)]).test(String.fromCharCode(k))){

							buffer[p]=String.fromCharCode(k);									

							writeBuffer();

							var next=seekNext(p);

							$(this).caret(next);

							if(settings.completed && next == mask.length)

								settings.completed.call(input);

						}				

					}

				}				

				return false;				

			};

			

			function clearBuffer(start,end){

				for(var i=start;i<end&&i<mask.length;i++){

					if(!locked[i])

						buffer[i]=settings.placeholder;

				}				

			};

			

			function writeBuffer(){				

				return input.val(buffer.join('')).val();				

			};

			

			function checkVal(){	

				//try to place charcters where they belong

				var test=input.val();

				var pos=firstNonMaskPos;

				for(var i=0;i<mask.length;i++){					

					if(!locked[i]){

						buffer[i]=settings.placeholder;

						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);

					valid=false;

				}else

					valid=true;

			};

			

			function seekNext(pos){				

				while(++pos<mask.length){					

					if(!locked[pos])

						return pos;

				}

				return mask.length;

			};

			

			input.one("unmask",function(){

				input.unbind("focus",focusEvent);

				input.unbind("blur",checkVal);

				input.unbind("keydown",keydownEvent);

				input.unbind("keypress",keypressEvent);

				if ($.browser.msie) 

					this.onpaste= null;                     

				else if ($.browser.mozilla)

					this.removeEventListener('input',checkVal,false);

			});

			input.bind("focus",focusEvent);

			input.bind("blur",checkVal);

			input.bind("keydown",keydownEvent);

			input.bind("keypress",keypressEvent);

			//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);

				

			checkVal();//Perform initial check for existing values

		});

	};

})(jQuery);
