/*!
 * jQuery bValidator plugin
 *
 * http://code.google.com/p/bvalidator/
 *
 * Copyright (c) 2012 Bojan Mauser
 *
 * $Id: jquery.bvalidator.js 99 2012-08-18 13:11:54Z bmauser $
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */

(function($){

	// constructor
	$.fn.bValidator = function(overrideOptions, instanceName){
		return this.each(function(){
			new bValidator($(this), overrideOptions, instanceName);
   		});
	};

	// bValidator class
	bValidator = function(mainElement, overrideOptions, instanceName){

		// default options
		var options = {

			singleError:         false,		// validate all inputs at once
			offset:              {x:-23, y:-4},	// offset position for error message tooltip
			position:            {x:'right', y:'top'}, // error message placement x:left|center|right  y:top|center|bottom
			template:            '<div class="{errMsgClass}"><em/>{message}</div>', // template for error message
			templateCloseIcon:   '<div style="display:table"><div style="display:table-cell">{message}</div><div style="display:table-cell"><div class="{closeIconClass}">x</div></div></div>', // template for error message container when showCloseIcon option is true
			showCloseIcon:       true,	// put close icon on error message
			showErrMsgSpeed:    'normal',	// message's fade-in speed 'fast', 'normal', 'slow' or number of milliseconds
			scrollToError:       true,	// scroll to first error
			// css class names
			classNamePrefix:     'bvalidator_',	// prefix for css class names
			closeIconClass:      'close_icon',	// close error message icon class
			errMsgClass:         'errmsg',		// error message class
			errorClass:          'invalid',		// input field class name in case of validation error
			validClass:          '',		// input field class name in case of valid value

			lang: 'en', 				// default language for error messages 
			errorMessageAttr:    'data-bvalidator-msg',// name of the attribute for overridden error message
			validateActionsAttr: 'data-bvalidator', // name of the attribute which stores info what validation actions to do
			forceValidAttr:      'data-bvalidator-forcevalid', // name of the attribute which which makes validator to act like the field is valid
			modifyActionsAttr:   'data-bvalidator-modifier',
			paramsDelimiter:     ':',		// delimiter for validator action options inside []
			actionsDelimiter:    ',',		// delimiter for validator actions

			// when to validate
			validateOn:          null,		// null, 'change', 'blur', 'keyup'
			errorValidateOn:     'keyup',		// null, 'change', 'blur', 'keyup'

			// callback functions
			onBeforeValidate:    null,
			onAfterValidate:     null,
			onValidateFail:      null,
			onValidateSuccess:   null,
			onBeforeElementValidation: null,
			onAfterElementValidation:  null,
			onBeforeAllValidations:    null,
			onAfterAllValidations:     null,
			
			validateOnSubmit: true,  // should validation occur on form submit if validator is bind to a form
			stopSubmitPropagation: true, // should submit event be stopped on error if validator is bind to a form
			noMsgIfExistsForInstance: [],
			
			autoModifiers: {
				'digit':  ['trim'],
				'number': ['trim'],
				'email':  ['trim'],
				'url':    ['trim'],
				'date':   ['trim'],
				'ip4':    ['trim'],
				'ip6':    ['trim']
				},
			
			ajaxAnswerValid: 'ok',
			ajaxDelay: 300,
			ajaxOptions: {cache: false},
			ajaxParamName: 'bValue',

			// default messages
			errorMessages: {
				en: {
					'default':    'Please correct this value.',
					'equalto':    'Please enter the same value again.',
					'differs':    'Please enter a different value.',
					'minlength':  'The length must be at least {0} characters',
					'maxlength':  'The length must be at max {0} characters',
					'rangelength':'The length must be between {0} and {1}',
					'min':        'Please enter a number greater than or equal to {0}.',
					'max':        'Please enter a number less than or equal to {0}.',
					'between':    'Please enter a number between {0} and {1}.',
					'required':   'This field is required.',
					'alpha':      'Please enter alphabetic characters only.',
					'alphanum':   'Please enter alphanumeric characters only.',
					'digit':      'Please enter only digits.',
					'number':     'Please enter a valid number.',
					'email':      'Please enter a valid email address.',
					'image':      'This field should only contain image types',
					'url':        'Please enter a valid URL.',
					'ip4':        'Please enter a valid IPv4 address.',
					'ip6':        'Please enter a valid IPv6 address.',
					'date':       'Please enter a valid date in format {0}.'
				}
			}
		},

		_ajaxValidation = function(element, instanceName, ajaxUrl, sync){
			
			var ajax_data = element.data("ajaxData.bV" + instanceName);
			
			if(!ajax_data){
				ajax_data = {};
				element.data("ajaxData.bV" + instanceName, ajax_data);
			}
			else{
				clearTimeout(ajax_data.timeOut);
			}
			
			// value to validate
			ajax_data.val = element.val();
			
			// do not do ajax if value is already validated
			if(ajax_data.lastValidated === ajax_data.val)
				return validator.ajax(ajax_data.lastResponse);
			
			var ajaxOptions = $.extend({}, options.ajaxOptions);
			if(typeof ajaxOptions.data != 'object')
				ajaxOptions.data = {}
			ajaxOptions.url = ajaxUrl;
			
			if(sync){
				var ret = false;
				ajaxOptions.async = false;
				ajaxOptions.data[options.ajaxParamName] = ajax_data.val;
				
				$.ajax(ajaxOptions).done(function(ajaxResponse){
					ajax_data.lastValidated = ajax_data.val;
					ajax_data.lastResponse = ajaxResponse;
					ret = validator.ajax(ajaxResponse)
				});
		
				return ret;
			}
			else{
				ajax_data.timeOut = setTimeout(function() {
	
					var val =  element.val();
					
					// only check if the value has not changed
					if(ajax_data.val == val){
						
						ajaxOptions.async = true;
						ajaxOptions.data[options.ajaxParamName] = val;
						
						$.ajax(ajaxOptions).done(function(ajaxResponse){
							ajax_data.lastValidated = val;
							ajax_data.lastResponse = ajaxResponse;
							instance.validate(false, element, undefined, ajaxResponse)
						});
					}
				}, options.ajaxDelay);
			}
			
			return;
		},

		// returns all inputs
		_getElementsForValidation = function(element){
			// skip hidden and input fields witch we do not want to validate
			return element.is(':input') ? element : element.find(':input[' + options.validateActionsAttr + '], :input[' + options.modifyActionsAttr + ']').not(":button, :image, :reset, :submit, :hidden, :disabled");
		},

		// binds validateOn event
		_bindValidateOn = function(elements){
			elements.bind(options.validateOn + '.bV' + instanceName, {'bVInstance': instance}, function(event){
				event.data.bVInstance.validate(false, $(this));
			});
		},
		
		// checks does message from validator instance exists on element
		_isMsgFromInstanceExists = function(element, instance_names){
			for(var i=0; i<instance_names.length; i++){
				if(element.data("errMsg.bV" + instance_names[i]))
					return true;
			}
			
			return false;
		},

		// displays message
		_showMsg = function(element, messages){

			// if message already exists remove it from DOM
			_removeMsg(element);

			msg_container = $('<div class="bVErrMsgContainer"></div>').css('position','absolute');
			element.data("errMsg.bV" + instanceName, msg_container);
			msg_container.insertAfter(element);

			var messagesHtml = '';

			for(var i=0; i<messages.length; i++)
				messagesHtml += '<div>' + messages[i] + '</div>\n';

			if(options.showCloseIcon)
				messagesHtml = options.templateCloseIcon.replace('{message}', messagesHtml).replace('{closeIconClass}', options.classNamePrefix+options.closeIconClass);

			// make tooltip from template
			var tooltip = $(options.template.replace('{errMsgClass}', options.classNamePrefix+options.errMsgClass).replace('{message}', messagesHtml));
			tooltip.appendTo(msg_container);
			
			// bind close tootlip function
			tooltip.find('.' + options.classNamePrefix+options.closeIconClass).click(function(e){
				e.preventDefault();
				$(this).closest('.'+ options.classNamePrefix+options.errMsgClass).css('visibility', 'hidden');
			});

			var pos = _getErrMsgPosition(element, tooltip); 

			tooltip.css({visibility: 'visible', position: 'absolute', top: pos.top, left: pos.left}).fadeIn(options.showErrMsgSpeed);

			if(options.scrollToError){
				// get most top tolltip
				var tot = tooltip.offset().top;
				if(scroll_to === null || tot < scroll_to)
					scroll_to = tot;
			}
		},

		// removes message from DOM
		_removeMsg = function(element){
			var existingMsg = element.data("errMsg.bV" + instanceName)
			if(existingMsg){
				existingMsg.remove();
				element.data("errMsg.bV" + instanceName, null);
			}
		},

		// calculates message position
		_getErrMsgPosition = function(input, tooltip){

		        var tooltipContainer = input.data("errMsg.bV" + instanceName),
		         top  = - ((tooltipContainer.offset().top - input.offset().top) + tooltip.outerHeight() - options.offset.y),
		         left = (input.offset().left + input.outerWidth()) - tooltipContainer.offset().left + options.offset.x,
			 x = options.position.x,
			 y = options.position.y;

			// adjust Y
			if(y == 'center' || y == 'bottom'){
				var height = tooltip.outerHeight() + input.outerHeight();
				if (y == 'center') 	{top += height / 2;}
				if (y == 'bottom') 	{top += height;}
			}

			// adjust X
			if(x == 'center' || x == 'left'){
				var width = input.outerWidth();
				if (x == 'center') 	{left -= width / 2;}
				if (x == 'left')  	{left -= width;}
			}

			return {top: top, left: left};
		},

		// calls callback function
		_callBack = function(type, param1, param2, param3){
		        if($.isFunction(options[type])){
		        	return options[type](param1, param2, param3);
			}
		},
		
		// returns checkboxes in a group
		_chkboxGroup = function(chkbox){
			var name = chkbox.attr('name');
			if(name && /^[^\[\]]+\[.*\]$/.test(name)){
				return $('input:checkbox').filter(function(){
					var r = new RegExp(name.match(/^[^\[\]]+/)[0] + '\\[.*\\]$');
					return this.name.match(r);
				});	
			}
			
			return chkbox;
		},
		
		// gets element value	
		_getValue = function(element){

			var ret = {};

			// checkbox
			if(element.is('input:checkbox')){
				ret['value'] = element.attr('name') ? ret['selectedInGroup'] = _chkboxGroup(element).filter(':checked').length : element.attr('checked');
			}
			else if(element.is('input:radio')){
				ret['value'] = element.attr('name') ? ret['value'] = $('input:radio[name="' + element.attr('name') + '"]:checked').length : element.val();
			}
			else if(element.is('select')){
				ret['selectedInGroup'] =  $("option:selected", element).length;
				ret['value'] = element.val();
			}
			else if(element.is(':input')){
				ret['value'] = element.val();
			}
			
			return ret;
		},
		
		// parses bValidator attributes
		_parseAttr = function(attrVal){
		
			// value of validateActionsAttr input attribute
			var action_str = $.trim(attrVal).replace(new RegExp('\\s*\\' + options.actionsDelimiter + '\\s*', 'g'), options.actionsDelimiter);  
			
			if(!action_str)
				return null;

			return action_str.split(options.actionsDelimiter);
		},
		
		// parses validator action and parameters
		_parseAction = function(actionStr){
		
			var ap = $.trim(actionStr).match(/^(.*?)\[(.*?)\]/);
		
			if(ap && ap.length == 3){
				return {
					name: ap[1], 
					params: ap[2].split(options.paramsDelimiter)
				}
			}
			else{
				return {
					name: actionStr, 
					params:[]
				}
			}
		},
		
		// applys modifier
		_applyModifier = function(action, el){
			
			var oldVal, newVal = _callModifier(action, el);
			if(typeof newVal !== 'undefined'){
				oldVal = $(el).val();
				if(oldVal != newVal)
					$(el).val(newVal);
			}
		},
		
		// calls modifier
		_callModifier = function(action, el){

			action.params.unshift($(el).val());
			
			if(typeof modifier[action.name] == 'function')
				return modifier[action.name].apply(el, action.params);
			else if(typeof window[action.name] == 'function')
				return window[action.name].apply(el, action.params);
			else if(window.console.warn)
				window.console.warn('[bValidator] unknown modifier: ' + action.name);
		},
		
		// calls validator
		_callValidator = function(action, el, value){

			if(typeof validator[action.name] == 'function'){
				action.params.unshift(value); // add input value to beginning of action.params
				return validator[action.name].apply(el, action.params);
			}
			
			// call custom user defined function
			if(typeof window[action.name] == 'function'){
				action.params.unshift(value.value);
				return window[action.name].apply(el, action.params);
			}
			
			if(window.console.warn)
				window.console.warn('[bValidator] unknown validator: ' + action.name);
		},					

		// object with validator actions
		validator = {

			equalto: function(v, elementId){
				return v.value == $('#' + elementId).val();
			},

			differs: function(v, elementId){
				return v.value != $('#' + elementId).val();
			},

			minlength: function(v, minlength){
				return (v.value.length >= parseInt(minlength))
			},

			maxlength: function(v, maxlength){
				return (v.value.length <= parseInt(maxlength))
			},

			rangelength: function(v, minlength, maxlength){		
				return (v.value.length >= parseInt(minlength) && v.value.length <= parseInt(maxlength))
			},

			min: function(v, min){		
				if(v.selectedInGroup)
					return v.selectedInGroup >= parseFloat(min)
				else{
					if(!validator.number(v))
			 			return false;
			 		return (parseFloat(v.value) >= parseFloat(min))
				}
			},

			max: function(v, max){		
				if(v.selectedInGroup)
					return v.selectedInGroup <= parseFloat(max)
				else{
					if(!validator.number(v))
			 			return false;
			 		return (parseFloat(v.value) <= parseFloat(max))
				}
			},

			between: function(v, min, max){
				if(v.selectedInGroup)
					return (v.selectedInGroup >= parseFloat(min) && v.selectedInGroup <= parseFloat(max))
			   	if(!validator.number(v))
			 		return false;
				var va = parseFloat(v.value);
				return (va >= parseFloat(min) && va <= parseFloat(max))
			},

			required: function(v){
				if(!v.value || !$.trim(v.value))
					return false
				return true
			},

			alpha: function(v){
				return validator.regex(v, /^[a-z ._\-]+$/i);
			},
			
			alphanum: function(v){
				return validator.regex(v, /^[a-z\d ._\-]+$/i);
			},

			digit: function(v){
				return validator.regex(v, /^\d+$/);
			},

			number: function(v){
				return validator.regex(v, /^[-+]?\d+(\.\d+)?$/);
			},

			email: function(v){
				return validator.regex(v, /^([a-zA-Z\d_\.\-\+%])+\@(([a-zA-Z\d\-])+\.)+([a-zA-Z\d]{2,4})+$/);
			},

			image: function(v){
				return validator.regex(v, /\.(jpg|jpeg|png|gif|bmp)$/i);
			},

			url: function(v){
				return validator.regex(v, /^\b(https?|ftp):\/\/([-A-Z0-9.]+)(\/[-A-Z0-9+&@#\/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#\/%=~_|!:,.;]*)?$/i);
			},

			regex: function(v, regex, mod){
				if(typeof regex === "string")
					regex = new RegExp(regex, mod);
				return regex.test(v.value);
			},

			ip4: function(v){
				return validator.regex(v, /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/);
			},
			
			ip6: function(v){
				return validator.regex(v, /^(?:(?:(?:[A-F\d]{1,4}:){5}[A-F\d]{1,4}|(?:[A-F\d]{1,4}:){4}:[A-F\d]{1,4}|(?:[A-F\d]{1,4}:){3}(?::[A-F\d]{1,4}){1,2}|(?:[A-F\d]{1,4}:){2}(?::[A-F\d]{1,4}){1,3}|[A-F\d]{1,4}:(?::[A-F\d]{1,4}){1,4}|(?:[A-F\d]{1,4}:){1,5}|:(?::[A-F\d]{1,4}){1,5}|:):(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:[A-F\d]{1,4}:){7}[A-F\d]{1,4}|(?:[A-F\d]{1,4}:){6}:[A-F\d]{1,4}|(?:[A-F\d]{1,4}:){5}(?::[A-F\d]{1,4}){1,2}|(?:[A-F\d]{1,4}:){4}(?::[A-F\d]{1,4}){1,3}|(?:[A-F\d]{1,4}:){3}(?::[A-F\d]{1,4}){1,4}|(?:[A-F\d]{1,4}:){2}(?::[A-F\d]{1,4}){1,5}|[A-F\d]{1,4}:(?::[A-F\d]{1,4}){1,6}|(?:[A-F\d]{1,4}:){1,7}:|:(?::[A-F\d]{1,4}){1,7})$/i);
			},

			date: function(v, format){ // format can be any combination of mm,dd,yyyy with separator between. Example: 'mm.dd.yyyy' or 'yyyy-mm-dd'
				if(v.value.length == 10 && format.length == 10){
					var s = format.match(/[^mdy]+/g);
					if(s.length == 2 && s[0].length == 1 && s[0] == s[1]){

						var d = v.value.split(s[0]),
						 f = format.split(s[0]);

						for(var i=0; i<3; i++){
							if(f[i] == 'dd') var day = d[i];
							else if(f[i] == 'mm') var month = d[i];
							else if(f[i] == 'yyyy') var year = d[i];
						}

						var dobj = new Date(year, month-1, day)
						if ((dobj.getMonth()+1!=month) || (dobj.getDate()!=day) || (dobj.getFullYear()!=year))
							return false

						return true
					}
				}
				return false;
			},

			extension: function(){
				var v = arguments[0],
				 r = '';
				if(!arguments[1])
					return false
				for(var i=1; i<arguments.length; i++){
					r += arguments[i];
					if(i != arguments.length-1)
						r += '|';
				}
				return validator.regex(v, '\\.(' +  r  + ')$', 'i');
			},
			
			ajax: function(ajaxResponse){
				if(ajaxResponse == options.ajaxAnswerValid)
					return true;
				return false;
			}
		},
		
		// object with modifiers
		modifier = {
			
			trim: function(v){
				return $.trim(v);
			}
		},

		// validator instance, scroll position flag
		instance = this, scroll_to;
		

		// global options
		if(window['bValidatorOptions'])
			$.extend(true, options, window['bValidatorOptions']);

		// passed options
		if(overrideOptions)
			$.extend(true, options, overrideOptions);
	
	
		// object with all validator instances
		var allInstances = mainElement.data("bValidators");
		if(!allInstances){
			allInstances = {};
			mainElement.data("bValidators", allInstances);
		}
		
		// if there is already first instance
		if(mainElement.data("bValidator")){
			if(!instanceName)
				return mainElement.data("bValidator"); // return existing instance

			if(allInstances[instanceName])
				return allInstances[instanceName];
		}
		else{
			if(!instanceName)
				instanceName = 'first';
			mainElement.data("bValidator", this);
		}
		
		allInstances[instanceName] = this;
		
		
		// if bind to a form
		if(mainElement.is('form')){
			
			// bind validation on form submit
			if(options.validateOnSubmit){
				mainElement.bind("submit.bV" + instanceName, function(event){
					
					if(instance.validate(false, undefined, 1))
						return true;
					else if(options.stopSubmitPropagation){
						event.stopImmediatePropagation();
						return false;
					}
				});
			}

			// bind reset on form reset
			mainElement.bind("reset.bV" + instanceName, function(){
				instance.reset();			
			});
		}

		// bind validateOn event
		if(options.validateOn)
			_bindValidateOn(_getElementsForValidation(mainElement));


		// API functions:

		// validation function
		this.validate = function(doNotshowMessages, elementsOverride, forceAjaxSync, ajaxResponse){

			// return value, elements to validate
			var ret = true, 
			 elementsl = elementsOverride ? elementsOverride : _getElementsForValidation(mainElement);

			scroll_to = null;

			if(typeof ajaxResponse !== 'undefined' || _callBack('onBeforeAllValidations', elementsl) !== false){

				// validate each element
				elementsl.each(function(){

					var actions_exp = _parseAttr($(this).attr(options.validateActionsAttr)), // all validation actions
					 modifiers_exp = _parseAttr($(this).attr(options.modifyActionsAttr)), // all modifier actions
					 k = -1, action_data = [], action, is_valid = 0;
					
					// call modifiers
					if(modifiers_exp){
						for(var i=0; i<modifiers_exp.length; i++){
						
							action = _parseAction(modifiers_exp[i]);
							
							if(!action.name)
								continue;
							
							// call modifier
							_applyModifier(action, this);
						}
					}
					
					// call auto modifiers and prepare validation actions
					if(actions_exp){
						for(var i=0; i<actions_exp.length; i++){
						
							action = _parseAction(actions_exp[i]);
							
							if(!action.name)
								continue;
							
							// auto modifiers
							if(options.autoModifiers && options.autoModifiers[action.name]){
								for(var h=0; h<options.autoModifiers[action.name].length; h++)
									_applyModifier(_parseAction(options.autoModifiers[action.name][h]), this);
							}
							
							if(action.name == 'required')
								var flagRequired = 1;
							else if(action.name == 'ajax')
								var flagAjax = 1;
							
							if(action.name == 'valempty')
								var flagValempty = 1;
							else
								action_data[++k] = action; // action objects, with name and params
							
						}
					}
					else
						return true; // no actions for validation
						
					var inputValue = _getValue($(this)), // value of input field for validation;
					 errorMessages = [], validationResult;
					
					// call async ajax validation and skip element
					if(!forceAjaxSync && flagAjax && typeof ajaxResponse === 'undefined'){
						
						var skipAjaxAction = 0;
						
						// call all validators till ajax
						for(var i=0; i<action_data.length; i++){
							if(action_data[i].name == 'ajax')
								break;
							
							if(!_callValidator(action_data[i], this, inputValue)){
								skipAjaxAction = 1;
								break;
							}
						}
						
						if(!skipAjaxAction){
							ajaxResponse = _ajaxValidation($(this), instanceName, action.params[0]);
							if(typeof ajaxResponse === 'undefined'){
								return true;
							}
						}
					}
						
					// do not show message if exists for instance specified by noMsgIfExistsForInstance option
					if(options.noMsgIfExistsForInstance.length && _isMsgFromInstanceExists($(this), options.noMsgIfExistsForInstance))
						doNotshowMessages = 1;

					// if value is not required and is empty
					if((!flagRequired && !flagValempty && !validator.required(inputValue)) || $(this).attr(options.forceValidAttr) == 'true')
						is_valid = 1;

					if(!is_valid){

						// get message from attribute
						var errMsg = $(this).attr(options.errorMessageAttr),
						 skip_messages = 0;

						// mark field as validated
						$(this).data('checked.bV' + instanceName, 1);

						if(_callBack('onBeforeElementValidation', $(this)) !== false){
		
							// for each validation action
							for(var i=0; i<action_data.length; i++){
								
								if(action_data[i].name == 'valempty')
									continue;
								
								if(_callBack('onBeforeValidate', $(this), action_data[i].name) === false)
									continue;

								// if validator exists
								if(action_data[i].name == 'ajax'){
									
									if(skipAjaxAction)
										continue;
										
									if(forceAjaxSync || typeof ajaxResponse === 'undefined'){
										if(!errorMessages.length){
											validationResult = _ajaxValidation($(this), instanceName, action_data[i].params[0], 1);
										}
										else
											validationResult = true; // skip ajax validation if value is already invalid
									}
									else{
										validationResult = validator.ajax.apply(this, [ajaxResponse]);
									}
								}
								else{
									validationResult = _callValidator(action_data[i], this, inputValue);
								}
								
								if(_callBack('onAfterValidate', $(this), action_data[i].name, validationResult) === false)
									continue;
		
								// if validation failed
								if(!validationResult){
									if(!doNotshowMessages){
		
										if(!skip_messages){
											if(!errMsg){
												
												if (options.singleError && errorMessages.length){
													skip_messages = 1;
													errMsg = '';
												}
												else{
													// lang set
													if(options.errorMessages[options.lang] && options.errorMessages[options.lang][action_data[i].name])
														errMsg = options.errorMessages[options.lang][action_data[i].name];
													// lang en
													else if(options.errorMessages.en[action_data[i].name])
														errMsg = options.errorMessages.en[action_data[i].name];
													else{
														// action msg attribute
														var tt = $(this).attr(options.errorMessageAttr + '-' + action_data[i].name);
														if(tt)
															errMsg = tt;
														// lang set default msg
														else if(options.errorMessages[options.lang] && options.errorMessages[options.lang]['default'])
															errMsg = options.errorMessages[options.lang]['default'];
														// lang en default msg
														else
															errMsg = options.errorMessages.en['default'];
													}
												}
												
											}
											else{
												skip_messages = 1;
											}
		
											// replace values in braces
											if(errMsg.indexOf('{')){
												for(var j=0; j<action_data[i].params.length-1; j++)
													errMsg = errMsg.replace(new RegExp("\\{" + j + "\\}", "g"), action_data[i].params[j+1]);
											}
		
											if(!(errorMessages.length && action_data[i].name == 'required'))
												errorMessages[errorMessages.length] = errMsg;
		
											errMsg = null;
										}
									}
									else
										errorMessages[errorMessages.length] = '';
		
									ret = false;
		
									_callBack('onValidateFail', $(this), action_data[i].name, errorMessages);
								}
								else{
									_callBack('onValidateSuccess', $(this), action_data[i].name);
								}
							}
						}
						
						var onAfterElementValidation = _callBack('onAfterElementValidation', $(this), errorMessages);	
					}
						
					
					// show messages and bind events
					if(!doNotshowMessages && onAfterElementValidation !== false && $(this).data('checked.bV' + instanceName)){

						var chk_rad = $(this).is('input:checkbox,input:radio') ? 1 : 0;
	
						// if validation failed
						if(errorMessages.length){
							
							if(onAfterElementValidation !== 0)
								_showMsg($(this), errorMessages)
	
							if(!chk_rad){
								$(this).removeClass(options.classNamePrefix+options.validClass);
								if(options.errorClass)
									$(this).addClass(options.classNamePrefix+options.errorClass);
							}
			
							// input validation event
							if (options.errorValidateOn){
								if(options.validateOn)
									$(this).unbind(options.validateOn + '.bV' + instanceName);
	
								var evt = chk_rad || $(this).is('select,input:file') ? 'change' : options.errorValidateOn;
	
								if(chk_rad){
									var group = $(this).is('input:checkbox') ? _chkboxGroup($(this)) : $('input:radio[name="' + $(this).attr('name') + '"]');
									$(group).unbind('.bVerror' + instanceName);
									$(group).bind('change.bVerror' + instanceName, {'bVInstance': instance, 'groupLeader': $(this)}, function(event){
										event.data.bVInstance.validate(false, event.data.groupLeader);
									});
								}
								else{
									$(this).unbind('.bVerror' + instanceName);
									$(this).bind(evt + '.bVerror' + instanceName, {'bVInstance': instance}, function(event){
										event.data.bVInstance.validate(false, $(this));
									});
								}
							}
						}
						else{
							if(onAfterElementValidation !== 0)
								_removeMsg($(this));
	
							if(!chk_rad){
								$(this).removeClass(options.classNamePrefix+options.errorClass);
								if(options.validClass)
									$(this).addClass(options.classNamePrefix+options.validClass);
							}
	
							if (options.validateOn){
								$(this).unbind(options.validateOn + '.bV' + instanceName);
								_bindValidateOn($(this));
							}
							
							$(this).data('checked.bV' + instanceName, 0);
						}
					}
				});
			}

			_callBack('onAfterAllValidations', elementsl, ret);

			// scroll to message
			if(scroll_to && !elementsOverride && ($(window).scrollTop() > scroll_to || $(window).scrollTop()+$(window).height() < scroll_to)){
				var ua = navigator.userAgent.toLowerCase();			
				$(ua.indexOf('chrome')>-1 || ua.indexOf('safari')>-1 ? 'body' : 'html').animate({scrollTop: scroll_to - 10}, {duration: 'slow'});
			}

			return ret;
		}

		// returns options object
		this.getOptions = function(){
			return options;
		}
		
		// returns validator object
		this.getValidators = this.getActions = function(){
			return validator;
		}
		
		// returns modifier object
		this.getModifiers = function(){
			return modifier;
		}

		// checks validity
		this.isValid = function(elements){
			return this.validate(true, elements, 1);
		}

		// deletes message
		this.removeMsg = this.removeErrMsg = function(element){
			_removeMsg(element);
		}
		
		// shows message
		this.showMsg = function(element, message){
			if(element.length){
				if(typeof(message)=='string')
					message = [message];
				
				_showMsg(element, message);
			}
		}

		// returns all inputs
		this.getInputs = function(){
			return _getElementsForValidation(mainElement);
		}

		// binds validateOn event
		this.bindValidateOn = function(element){
			_bindValidateOn(element);
		}

		// resets validation
		this.reset = function(){
			elements = _getElementsForValidation(mainElement);
			if (options.validateOn)
				_bindValidateOn(elements);
			elements.each(function(){
				_removeMsg($(this));
				$(this).unbind('.bVerror' + instanceName);
				$(this).removeClass(options.classNamePrefix+options.errorClass);
				$(this).removeClass(options.classNamePrefix+options.validClass);
				$(this).removeData('ajaxData.bV' + instanceName);
				$(this).removeData('errMsg.bV' + instanceName);
				$(this).removeData('checked.bV' + instanceName);
			});
		}

		this.destroy = function(){
			if (mainElement.is('form'))
				mainElement.unbind('.bV' + instanceName);
			
			this.reset();
			
			mainElement.removeData("bValidator");
			mainElement.removeData("bValidators");
		}
	}

})(jQuery);
