/**
 *
 * @author Ardeleanu Ionut
 * @langversion JAVASCRIPT, jQuery
 *
 * http://www.neokinetics.ro
 * iardeleanu@neokinetics.ro
 *
 * Browsers tested: IE6+, Firefox 3+, Opera 8+, Chrome, Safari 4 for windows
 * Last Update: 15.06.2010
 */

(function($) {
	
$.fn.extend({
	inputvalidate: function(options) {
		
		options = $.extend({}, $.Inputvalidator.defaults, options);
		
		options.errorParams = $.extend({}, $.Inputvalidator.defaults.errorParams, options.errorParams);
		options.extraParams = $.extend({}, $.Inputvalidator.defaults.extraParams, options.extraParams);
		
		return this.each(function() {
			new $.Inputvalidator(this, options);
		});
	},
	SetOptions: function(options){
		return this.trigger("SetOptions", [options]);
	},
	GetOptions: function(){
		return this.data("options");
	},
	HideError: function(){
		return this.trigger("HideError");
	},
	ShowError: function(index){
		return this.trigger("ShowError",[index]);
	}
});	



/*****************************************************************************************/
/*                                INPUT VALIDATOR OBJECT                                 */
/*****************************************************************************************/
/**
 * create an Inputvalidator object for each input object
 * params: @input : DOM input element
 		   @options	: validation params
 */
$.Inputvalidator = function(input, options) {
	
	var KEY = {
		LEFT: 37,
		UP: 38,
		RIGHT: 39,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		ENTER: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8,
		END: 35,
		HOME: 36
	};
	
		
	// Create $ object for input element
	var $input = $(input);
	input.readOnly = false;
	options.form = input.form;
	$input.data("DOMDoc",options.DOMDoc);
	$input.data("options",options);
	
	
	
	/*****************************************************************************************/
	/*                                		SET OPTIONS                                      */
	/*****************************************************************************************/
	/**
	 * extends options params with new infos
	 * params: 1 - custom event name
	 		   2 - callback function ( @arguments[1] : new array options params )
	 */
	$input.bind("SetOptions", function() {
		$.extend(options, arguments[1]);
		options.errorParams = $.extend({}, $.Inputvalidator.defaults.errorParams, options.errorParams, arguments[1]);
		options.extraParams = $.extend({}, $.Inputvalidator.defaults.extraParams, options.extraParams, arguments[1]);
		
		//alert($input.get(0).id + " "+options.errorParams.textClass)
		$input.data("options",options);
	})
	//------------------------------------------------------------------------------------> end of 'SetOptions' callback function
	
	
	
	/*****************************************************************************************/
	/*                                		SHOW ERROR                                       */
	/*****************************************************************************************/
	/**
	 * show error message
	 * params: 1 - custom event name
	 		   2 - callback function ( @arguments[1] : error index to display from the array of errors )
	 */
	$input.bind("ShowError", function() {
		var index = arguments[1];
		var error_msg = options.errors[index];
		
		$input.SetOptions({valid : false});
		
		// if there is an error container, show it
		if (options.errorParams.container != null){
			$('#'+options.errorParams.container.id+' p.'+options.errorParams.textClass, options.DOMDoc).remove();
		}
		// else create it first
		else{
			$(this).parent().append("<div id='errorContainer_"+this.id+"'></div>");
			var divError = $('#errorContainer_'+this.id,$(this).parent()).get(0);
			
			// save the new div created in options params
			$input.SetOptions({errorParams: {container : divError }});
		}
		
		$(options.errorParams.container).append("<p class='"+options.errorParams.textClass+"'></p>");
		var p = $('p.'+options.errorParams.textClass, options.errorParams.container);
		
		// display error message through fade Effect
		if (options.errorParams.fadeEffect == true){ 
			p.css('display','none');
			p.html(error_msg);
			p.slideDown(200);
		}
		else{
			p.html(error_msg);	
		}
		
		// add background error css class
		if (options.errorParams.rowId != null){
			$("*[id="+options.errorParams.rowId+"]",options.DOMDoc).each(function(){
				$(this).addClass(options.errorParams.rowClass);
			});
		}
	})
	//-------------------------------------------------------------------------------------> end of 'ShowError' callback function
	
	
	
	/*****************************************************************************************/
	/*                                		HIDE ERROR                                       */
	/*****************************************************************************************/
	/**
	 * hide error message
	 * params: 1 - custom event name
	 		   2 - callback function
	 */
	$input.bind("HideError", function() {
		
		$input.SetOptions({valid : true});
		
		// if there is an error container, hide it
		if (options.errorParams.container != null){
			var div = $('#'+options.errorParams.container.id,options.DOMDoc);
			var p = $('p.'+options.errorParams.textClass,div.get(0));
			
			if (options.errorParams.fadeEffect == true){ 
				p.slideUp(200, function(){
					if (div.attr('id').match(/^errorContainer+/g)){ 
						p.parent().remove();
						$input.SetOptions({errorParams: {container : null }});	
					}
					else{
						p.remove();	
					}						
				});
			}
			else{
				if (div.attr('id').match(/^errorContainer+/g)){ 
					p.parent().remove();
					$input.SetOptions({errorParams: {container : null }});	
				}
				else{
					p.remove();	
				}
			}
		}
		
		// remove background css class
		if (options.errorParams.rowId != null){
			$("*[id="+options.errorParams.rowId+"]",options.DOMDoc).each(function(){
				$(this).removeClass(options.errorParams.rowClass);
			});
		}
	})
	//-------------------------------------------------------------------------------------> end of 'HideError' callback function
	
	
	
	var lastKeyPressCode;
	var lastCharPressCode;
	
	
	/*****************************************************************************************/
	/*                                		KEY DOWN                                         */
	/*****************************************************************************************/
	/**
	 * run when the key pressed is down
	 * params: callback function( @evt : the 'keydown' event, from options.DOMDoc document)
	 */
	$input.keydown(function(evt) {
		
		// track last key pressed
		if (options.DOMDoc.parentWindow){
			if (options.DOMDoc.parentWindow.event){
				var Event = options.DOMDoc.parentWindow.event;
			}
			else{
				var Event = options.DOMDoc.parentWindow.evt;	
			}
		}
		else{
			var Event = evt;
		}
		
		
		lastKeyPressCode = Event.keyCode || Event.charCode || Event.which;
		
		var startPosition = $input.caret().start;
		var endPosition = $input.caret().end;
		var nextValue = "";
		
		
		switch(lastKeyPressCode) {
	
			case KEY.ESC:
				Event.preventDefault();
				return false;
				break;
			
			
			case KEY.ENTER:
				
				if (typeof options.SubmitFunction == "function"){
					$input.trigger('blur');
					$input.get(0).focus();
					options.SubmitFunction();
					
				}
				else if ($input.get(0).type == "textarea"){
					break;	
				}
				// stop default to prevent a form submit, Opera needs special handling
				else{
					Event.preventDefault();
					return false;
				}
				break;
			
			
			case KEY.DEL:
				nextValue = this.value.substr(0,startPosition) + this.value.substr(endPosition+1);
				$(this).data('nextValue',nextValue);
				return VerifyKeyPress(lastKeyPressCode);
				break;
				
				
			case KEY.BACKSPACE:
				nextValue = this.value.substr(0,startPosition-1) + this.value.substr(endPosition);
				$(this).data('nextValue',nextValue); 
				return VerifyKeyPress(lastKeyPressCode);
				break;
				
			case KEY.LEFT:
			case KEY.RIGHT:
			case KEY.UP:
			case KEY.DOWN:
			case KEY.END:
			case KEY.HOME:
			case KEY.TAB:
				$(this).data('nextValue',"undefined"); 
				return true;
				break;
				
			default:
				$(this).data('nextValue',null); 
				break;
		}	
	})
	//-------------------------------------------------------------------------------------> end of 'KeyDown' callback function
	
	
	
	/*****************************************************************************************/
	/*                                		KEY PRESS                                        */
	/*****************************************************************************************/
	/**
	 * run when the key pressed is down (for some KEYS like ENTER, TAB, HOME, on IE and Opera)
	 * params: callback function( @evt : the 'keydown event', from options.DOMDoc document)
	 */
	$input.keypress(function(evt) {
		
		// track last key pressed
		if (options.DOMDoc.parentWindow){
			if (options.DOMDoc.parentWindow.event){
				var Event = options.DOMDoc.parentWindow.event;
			}
			else{
				var Event = options.DOMDoc.parentWindow.evt;	
			}
		}
		else{
			var Event = evt;
		}
		
		lastKeyPressCode = Event.keyCode || Event.charCode || Event.which;
		
		return VerifyKeyPress(lastKeyPressCode);
	})
	//-------------------------------------------------------------------------------------> end of 'KeyPress' callback function
	
	
	
	/*****************************************************************************************/
	/*                                	    KEY UP                                           */
	/*****************************************************************************************/
	/**
	 * run when the key pressed is up (released)
	 * params: callback function
	 */
	$input.keyup(function() {
		
		// track last key pressed
		if (typeof options.OnKeyUpFunction == "function" ){
			options.OnKeyUpFunction();
		}
		
		return true;
	})
	//-------------------------------------------------------------------------------------> end of 'KeyUps' callback function
	
	
		
	/*****************************************************************************************/
	/*                                	     BLUR                                            */
	/*****************************************************************************************/
	/**
	 * run when input loses the focus
	 * params: callback function
	 */
	$input.blur(function() {
		
		// run the BeforeBlurFunction if there is one defined for the input 
		if (options.BeforeBlurFunction && typeof options.BeforeBlurFunction == "function"){
			options.BeforeBlurFunction();
		}
				
		// replace the whitespaces from the start and the end of the value
		var parseValue = $input.val().stripBlanks();
		
		// if there is no value in the input field
		if (parseValue.length == 0){
			$input.HideError();
			$input.SetOptions({ valid : (options.required == "yes") ? false : true});
		}
		// if there is a value
		else{
			
			$input.SetOptions({valid : true});
			
			// first, do content validation
			switch (options.type){
			
				case "normal":
					break;	
					
				case "alpha":
					var valid = parseValue.isAlpha(options.extendedChars);
					if (valid == false)	$input.ShowError(1);
					break;	
					
				case "numeric":
					var valid = parseValue.isNumeric(options.extendedChars);
					if (valid == false)	$input.ShowError(1);
					break;
			
				case "alphanumeric":
					var valid = parseValue.isAlphaNumeric(options.extendedChars);
					if (valid == false)	$input.ShowError(1);
					break;
					
				case "email":
					var valid = parseValue.isEmail();
					if (valid == false)	$input.ShowError(1);
					break;
					
				case "emails":
					var valid = parseValue.areEmails();
					if (valid == false)	$input.ShowError(1);
					break;
				
				case "oldpassword":
					break;
					
				case "password":
					if (($input.val().length > 0 && $input.val().length < 6) || $input.val().length > 32){
						$input.ShowError(1);
					}
					break;
				
				case "retypepassword":
					break;
					
				case "date":
					var valid = parseValue.isDate(options.dateFormat);
					if (valid == false)	$input.ShowError(1);
					break;
					
				case "arhive":
					var valid = parseValue.isArhive(options.docTypes);
					if (valid == false)	$input.ShowError(1);
					break;
				
				case "website":
					var valid = parseValue.isURL();
					if (valid == false)	$input.ShowError(1);
					break;
				
				case "float":
					
					if (isNaN(parseValue) == true){ 
						$input.val("");
					}
					else{
						
						$input.val((parseValue.length > 0) ? parseFloat(parseValue) : "");
						
						if (options.negative == false && parseFloat(parseValue) < 0){
							$input.val("");
						}
						
						if (parseValue.indexOf(".") != -1){
							if (parseValue.split(".")[1].length > options.decimals){
								$input.val(parseValue.substr(0,parseValue.indexOf(".")+1) + parseValue.substr(parseValue.indexOf(".")+1, options.decimals) );
							}
						}
						
						if (parseFloat(parseValue) > options.max) $input.val(options.max);
						if (parseFloat(parseValue) < options.min) $input.val(options.min);
					}
					
					if ($input.val() == ""){
						$input.HideError();
						$input.SetOptions({ valid : (options.required == "yes") ? false : true});	
					}
					break;
					
				case "integer":
					
					if (isNaN(parseValue) == true){ 
						$input.val("");
					}
					else{
						
						$input.val((parseValue.length > 0) ? parseInt(parseValue) : "");
						
						if (options.negative == false && parseInt(parseValue) < 0){
							$input.val("");
						}
						
						if (parseInt(parseValue) > options.max) $input.val(options.max);
						if (parseInt(parseValue) < options.min)	$input.val(options.min);
					}
					
					if ($input.val() == ""){
						$input.HideError();
						$input.SetOptions({ valid : (options.required == "yes") ? false : true});	
					}
					break;	
			}// end content validation
		
			
			// do ajax validation if it is the case, and the 'options.valid' is true
			if (options.valid){
				$input.HideError();
				
				if (options.ServerFunction && typeof options.ServerFunction == "function"){
					//run ajax verifying function
					options.ServerFunction();
				}
			}
		}
		
		
		// run the AfterBlurFunction if there is one defined for the input 
		if (options.AfterBlurFunction && typeof options.AfterBlurFunction == "function"){
			options.AfterBlurFunction();
		}
		
		
		return true;
	})
	//-------------------------------------------------------------------------------------> end of 'Blur' callback function
	
	
	
	
	/*****************************************************************************************/
	/*                                		VERIFY KEY PRESSED                               */
	/*****************************************************************************************/
	/**
	 * verify if the next posible value is ok according with the input type
	 * params: @lastKeyPressCode : the last key pressed
	 */
	function VerifyKeyPress(lastKeyPressCode){
		
		var nextValue = "";
		var startPosition = $input.caret().start;
		var endPosition = $input.caret().end;
		
		//if NAVIGATION ARROWS are pressed
		if ($input.data('nextValue') == "undefined"){
			return true;
		}
		//if DEL KEY, BACKSPACE KEY are pressed
		if ($input.data('nextValue') != null){
			nextValue = $input.data('nextValue');
		}
		// for all other KEYS pressed
		else{
			nextValue = $input.val().substr(0,startPosition) + String.fromCharCode(lastKeyPressCode) + $input.val().substr(endPosition);
		}
		
		
		switch(options.type) {
			
			case "integer":
				
				if (isNaN(nextValue) == true){ 
					if (options.negative == true && nextValue == "-"){
						return true;	
					}
					return false;
				}
				else{
					if (options.negative == false && parseInt(nextValue) < 0){
						return false;	
					}
					if (String(parseInt(nextValue)) != nextValue && nextValue != "") return false;
					if (parseInt(nextValue) > options.max) return false;
					
				}
				break;
			
			
			case "float":
				
				if (isNaN(nextValue) == true){ 
					if (options.negative == true && nextValue == "-"){
						return true;	
					}
					return false;
				}
				else{
					if (options.negative == false && parseFloat(nextValue) < 0){
						return false;	
					}
					if (parseFloat(nextValue) > options.max) return false;
					if (nextValue.indexOf(".") != -1){
						if (nextValue.split(".")[1].length > options.decimals) return false;
					}
					
				}
				break;
		}
		
		return true;
	}
	
}

$.Inputvalidator.defaults = {
	required: "no",
	valid: true,					//	if the input is ready for submiting
	type: "normal",					/*  numeric, alphanumeric, alpha
										oldpassword, password, retypepassword,
										email, emails, normal(no validation),
										float, integer,
										date, arhive, website*/
	
	ServerFunction: null,			// 	ajax function for checking the input value
	SubmitFunction: null,			//  function to run when the user hits ENTER KEY and the input has the focus
	BeforeBlurFunction:	null,		//	function to run before input loses its focus
	AfterBlurFunction: null,		//	function to run after input loses its focus
	OnKeyUpFunction: null,			//	function to run when the key is released
	
	extendedChars: null,			//	array with extended chars [@,#,$,%,*,...]
	docTypes: null, 				//	array with arhive types like [zip, rar, doc, pdf]
	
	max: Infinity,					//	max value for the input value
	min: - Infinity,				//  min value for the input	value
	decimals: 0,
	negative: false,				//  if the value inside input is a positive or negative Number
	dateFormat: new String(),		//	date format type: "mm/dd/yyyy" or "dd-mm-yyyy", etc	
	
	errorParams: {
				container: null,		//	a div container for the error message; if it is null, than an automatic container is created
				fadeEffect: false, 		//	if error messages are displayed with fade effects
				textClass: "error_txt",	//	css class for the error message written in the 'container'
				rowClass: "error_row",	//	css class for the 'entire row'
				rowId: null},			//	string: error row's id
	errors: null,					//	array with errors messages
	
	form: null,
	DOMDoc: document,				//	current window document where is located the input field
	extraParams: {}	
};

	
})(jQuery);


// Detect Caret Position Using jQuery Caret (jCaret) Plugin
(function (k, e, i, j) {
    k.fn.caret = function (b, l) {
        var a, c, f = this[0],
            d = k.browser.msie;
        if (typeof b === "object" && typeof b.start === "number" && typeof b.end === "number") {
            a = b.start;
            c = b.end
        } else if (typeof b === "number" && typeof l === "number") {
            a = b;
            c = l
        } else if (typeof b === "string") if ((a = f.value.indexOf(b)) > -1) c = a + b[e];
        else a = null;
        else if (Object.prototype.toString.call(b) === "[object RegExp]") {
            b = b.exec(f.value);
            if (b != null) {
                a = b.index;
                c = a + b[0][e]
            }
        }
        
		if (typeof a != "undefined") {
            if (d) {
                d = this[0].createTextRange();
                d.collapse(true);
                d.moveStart("character", a);
                d.moveEnd("character", c - a);
                d.select()
            } else {
                this[0].selectionStart = a;
                this[0].selectionEnd = c
            }
            this[0].focus();
            return this
        } else {
            
			if (d) {
                c = $(this[0]).data("DOMDoc").selection;
                
				if (this[0].tagName.toLowerCase() != "textarea") {
                    d = this.val();
                    a = c[i]()[j]();
                    a.moveEnd("character", d[e]);
                    var g = a.text == "" ? d[e] : d.lastIndexOf(a.text);
                    a = c[i]()[j]();
                    a.moveStart("character", -d[e]);
                    var h = a.text[e]
                } else {
                    a = c[i]();
                    c = a[j]();
                    c.moveToElementText(this[0]);
                    c.setEndPoint("EndToEnd", a);
                    g = c.text[e] - a.text[e];
                    h = g + a.text[e]
                }
            } else {
                g = f.selectionStart;
                h = f.selectionEnd
            }
            a = f.value.substring(g, h);
            return {
                start: g,
                end: h,
                text: a,
                replace: function (m) {
                    return f.value.substring(0, g) + m + f.value.substring(h, f.value[e])
                }
            }
        }
    }
})(jQuery, "length", "createRange", "duplicate");
