(function($){
	$.extend({
		validGroupResult : {},
		validGroupCallback : {},
		isString: function(txt){
			return !!((typeof txt).toLowerCase() == 'string' && $.trim(txt));
		},
		isLikeRule:function(rule,value){
			/*
				type - type of rule
				val - value with out rule, but not formated
			*/
			if (!rule.type || !rule.val || !value) return false;
			var getNumber = function(v){return v.replace(/[^0-9-.]+/g,'')*1};
			if (rule.type == 'n' || rule.type == 'eq') {
				return rule.val == value;
			} else if (rule.type == 'ne'){
				return value != rule.val;
			} else if (rule.type == 's') {
				return value.indexOf(rule.val) > -1;
			} else if (rule.type == 'ls') {
				var result = 0;
				$.each(rule.val.split(';'),function(a,b){ if (b.indexOf(value) > -1) result++});
				return !!result;
			} else if (rule.type == 'ln') {
				var result = 0;
				$.each(rule.val.split(';'),function(a,b){ if (value == b) result++});
				return !!result;
			} else if (rule.type == 'gt'){
				return getNumber(value) > getNumber(rule.val);
			} else if (rule.type == 'lt'){
				return getNumber(value) < getNumber(rule.val);
			} else if (rule.type == 'ge'){
				return getNumber(value) >= getNumber(rule.val);
			} else if (rule.type == 'le'){
				return getNumber(value) <= getNumber(rule.val);
			} else if (rule.type == 'dn'){
				return getNumber(value) >= getNumber(rule.val.split(';')[0]) && getNumber(value) <= getNumber(rule.val.split(';')[1]);
			}
		},
		ruleObject:function(rule){
			var type;
			rule = unescape($.trim(rule)).slice(5);
			if (!isNaN(rule)){
				type = 'n';
			} else if (rule.slice(0,1) == '=') {
				type = 'eq';
			} else if (rule.slice(0,2) == '!=') {
				type = 'ne';
			} else if (rule.slice(0,2) == '>=') {
				type = 'ge';
			} else if (rule.slice(0,2) == '<=') {
				type = 'le';
			} else if (rule.slice(0,1) == '>') {
				type = 'gt';
			} else if (rule.slice(0,1) == '<') {
				type = 'lt';
			} else if (/^(\(|\[)[0-9-.]+[^\d]*;[0-9-.]+[^\d]*(\)|\])$/.test(rule) || /^[0-9-.]+[^\d]*-[0-9-.]+[^\d]*$/.test(rule)) {
				type = 'dn';
			} else if (/[0-9-.]+(.)*;[0-9-.]+(.)*$/.test(rule) || /{[0-9-.;]+}/.test(rule)){
				type = 'ln';
			} else if (/[^;]+;[^;]+$/i.test(value)){
				type = 'ls';
			} else {
				type ='s';
			}
			return rule = {
						type:type,
						val:rule.replace(/^([<>!=]|\(|\[|\{)*/,'').replace(/(\)|\]|\})?$/,'')
					};
		}
	});
	$.fn.extend({
		validOffsetPosition: function(key){
			if (key && (key == 'top' || key == 'left')) {
				return this.each(function(a,b){
					var el = $(b);
					el.css(key,el.offsetPosition()[key]+'px');
				});
			} else {
				var el = this.eq(0);
				var offParent = el.offsetParent(),
					parentOffset = offParent.offset(),
					offset = el.offset();
				return {top:offset.top - parentOffset.top,left:offset.left - parentOffset.left,offsetParent:offParent};
			}
		},
		getValidElements : function(){
			return this.find('.need-validation');
		},
		getValidAttr : function(name){
			return this[0].getAttribute(name);
		},
		isForm : function(){
			var el = this;
			return el.getValidElements()[0];
		},
		isValid : function(missGroup){
			var self = this;
			if (!missGroup && self.hasValidGroup()){
				return self.isGroupValid();
			} else if (self.isForm()) {
				return self.getValidElements().isValid();
			} else {
				var r = 1;
				self.not(':hidden').each(function(a,b){ 
					var el = $(b);
					if (el.notEmpty()) {
						r = r*el.getValid()
					} else  { 
						r = 0
						return r
					}
				});
				self = null;
				return r;
			}
		},
		isGroupValid : function(name){
			return $.validGroupResult[name?name:this.getValidGroup()];
		},
		notEmpty: function(){
			var r = 1;
			this.each(function(a,b){if (b.value == '' && b['_validLast'] === undefined) return r = 0 });
			return r;
		},
		groupNotEmpty : function(){
			return this.getValidGroupElements().notEmpty();
		},
		hasValid : function(){
			var self = this;
			if (self.isForm()) {
				return self.getValidElements().hasValid();
			} else {
				var r = 0;
				self.not(':hidden').each(function(a,b){if ($(b).getValid() > 0) {r = 1;return false}});
				self = null;
				return r;
			}
		},
		setValid : function(cat){
			return this.each(function(a,b){ if (cat+'') b['_isValid'] = cat});
		},
		getValid : function() {
			return this[0]['_isValid'];
		},
		validDrawPosition : function(pos){
			var pos = pos || false;
			return this.each(function(a,b){ b['_iOuter'] = pos });
		},
		addValidTimer: function(timer){
			return this.each(function(a,b){
				if (isNaN(timer)) return;
				b['_valid_timer'] = timer*1;
			});
		},
		removeValidTimer:function(){
			return this.each(function(a,b){
				b['_valid_timer'] = null;
			});
		},
		getValidTimer:function(){
			return this[0]['_valid_timer'];
		},
		hasValidTimer:function(){
			return !!this[0]['_valid_timer'];
		},
		addValidRule : function(rule){
			if (!rule) return this;
			var isArr = $.isArray(rule),
				isMyRule = function(r) { return $.isString(r) && r.indexOf('rule:') == 0}
			if (isMyRule(rule)) {
				rule = $.ruleObject(rule); 
			} else if(isArr) {
				$.each(rule,function(a,b){ if (isMyRule(rule[a])) rule[a] = $.ruleObject(b) });
			}
			return this.each(function(a,b){
				var field = '_valid_rule';
				if (isArr){
					b[field] = rule;
				} else {
					b[field] = b[field] || [];
					if(rule && $.inArray(rule,b[field]) < 0) b[field].push(rule);
				}
			}).validCheck();
		},
		removeValidRule:function(rule){
			if (!rule) return this;
			return this.each(function(a,b){
				var field = '_valid_rule';
				if ($.isArray(b[field])){
					var ind = $.inArray(rule,b[field]);
					if (ind > -1) b[field].splice(ind,1);
				} else {
					b[field] = null;
				}
			}).validCheck();
		},
		addNotValidAnswer : function(ans){
			return this.each(function(a,b){b['_not_valid_answer'] = ans});
		},
		removeNotValidAnswer : function (){
			return this.each(function(a,b){b['_not_valid_answer'] = null});
		},
		addGoodValidCallback : function(callback){
			return this.each(function(a,b){
				var el = $(b),
					group = el.getValidGroup();
				if (group && !$.validGroupCallback[group]) $.validGroupCallback[group] = callback; else b['_valid_callback'] = callback;
			});
		},
		removeGoodValidCallback : function(){
			return this.each(function(a,b){
				var el = $(b),
					group = el.getValidGroup();
				if (group) $.validGroupCallback[group] = null; else b['_valid_callback'] = null;
			});
		},
		getGoodValidCallback : function(){
			var el = this,
				group = el.getValidGroup();
			if (group && el.isLastGroupElement()) return $.validGroupCallback[group]; else return el[0]['_valid_callback'];
		},
		addValidGroup : function(group){
			return this.each(function(a,b){b.setAttribute('valid_group',group)});
		},
		getValidGroup : function(){
			return this.getValidAttr('valid_group');
		},
		hasValidGroup : function(){
			return !!this.getValidGroup();
		},
		removeValidGroup : function(){
			return this.removeAttr('valid_group');
		},
		getValidGroupElements : function(){
			return this.closest('form').find('[valid_group="'+this.getValidGroup()+'"]');
		},
		getLastGroupElement: function(){
			return this.getValidGroupElements().last();
		},
		isLastGroupElement: function(){
			return this[0] === this.getLastGroupElement()[0];
		},
		addValidOptions : function(options,callback,group){
			if ((typeof callback).toLowerCase() == 'string') {
					group = callback;
					callback = null;
			}
			return this.each(function(a,b){
				var $b = $(b);
				if (options && options['valid_rule']) $b.addValidRule(options['valid_rule']);
				if (options && options['not_valid_answer']) $b.addNotValidAnswer(options['not_valid_answer']);
				if (options && options['template']) $b.addTemplate(options['template']);
				if (group) $b.addValidGroup(group);
				if (callback) $b.addGoodValidCallback(callback);
				$b = b = null;
			});
		},
		removeValidOptions : function(options,callback,group){
			if ((typeof callback).toLowerCase() == 'string') {
					group = callback;
					callback = null;
			}
			return this.each(function(a,b){
				var $b = $(b);
				if (options && options['valid_rule']) $b.removeValidRule(options['valid_rule']);
				if (options && options['not_valid_answer']) $b.removeNotValidAnswer(options['not_valid_answer']);
				if (options && options['template']) $b.removeTemplate(options['template']);
				if (callback) $b.removeGoodValidCallback(callback);
				if (group) $b.removeValidGroup(group);
				$b = b = null;
			});
		},
		validConformity : function(rules) {
			var b = this[0],
				$b = this.eq(0),
				ctg = 1,
				rls = rules || b['_valid_rule'] || valid_reg_exp['email'];
			if (!$.isArray(rls)) rls = $.makeArray(rls);
			if (b.tagName.toLowerCase() == 'select') {
				ctg = (b.options.selectedIndex != 0)*1;
			} else if (b.type == 'radio'){
				if ($("input[name='"+b.name+"']").is(':checked')) ctg = 1;
				else ctg = 0;
			} else if (b.type == 'checkbox'){
				var form = $b.closest('form'),
					grp = form[0]? form.find('[name='+b.name+']') : $('[name='+b.name+']');
				ctg = grp.not(b)[0]? !!grp.filter(':checked')[0] : $b.is(':checked');
				grp = form = null;
			} else {
				$.each(rls,function(i,r){
					if (isNaN(r)){
						if (r.type && r.val){
							ctg = ctg*$.isLikeRule(r,b.value);
						} else {
							if($.isString(r)) rls[i] = new RegExp(r.replace(/^\//,'').replace(/\/$/,''));
							var a = new RegExp(rls[i]);
							ctg = ctg*a.test(b.value);
							a = null;
						}
					} else {
						ctg = ctg*r;
					}
				});
			}
			$b = b = null;
			return ctg;
		},
		validDraw : function(cat,message,need){
			var el = this;
			if (el.hasValidGroup() && !el.isLastGroupElement()) return el.getLastGroupElement().validDraw(cat,message,need); else return el.each(function(a,b){
				var $b = $(b),
					cssClass = 'warning-valid',
					txt = message || b['_not_valid_answer'],
					grp_ = $b.getValidGroup();
				if(grp_) $.validGroupResult[grp_] = cat; else $b.setValid(cat);
				switch(cat*1){
					case 0: cssClass='bad-valid';break;
					case 1: cssClass='good-valid';break;
					case 2: cssClass='warning-valid';break;
				}
				var old_res = $b.nextAll('.valid-helper').first();
				$('.valid-helper').hide();
				var sameRedsult = old_res.hasClass(cssClass);
				if (need && sameRedsult){
					old_res.show().html(txt+'<i></i>');
				} else if(!sameRedsult) {
					var insertEL = $b;
					if (b.type == 'radio'){
						if (b.name) insertEL = $("input[name='"+b.name+"']").last().next('label,span');
						else insertEL = $b.next('label,span');
					} else if (b.type == 'checkbox') {
						var form = $b.closest('form'),
							grp = b.name && form[0]? form.find('[name='+b.name+']') : $b;
						insertEL = grp.not(b)[0]? grp.last().next('label,span') : $b.next('label,span');
						grp = form = null;
					}
					insertEL.validDrawPosition(valid_config['dir'] !== 'rtl' && (b['_iOuter'] || b.type == 'radio' || b.type == 'checkbox' || valid_config['iOuter'])).nextAll('.valid-js-div').remove();
					var oWidth = insertEL.outerWidth(),
						oHeight = insertEL.outerHeight(),
						hBorder = (oWidth - insertEL.innerWidth())/2,
						vBorder = (oHeight - insertEL.innerHeight())/2,
						elOffP = insertEL.validOffsetPosition(),
						elLeft = valid_config['dir'] == 'rtl' ? elOffP.left : (elOffP.left + oWidth),
						elTop = elOffP.top + oHeight/2 - vBorder - valid_config['iHeight']/2;
					if (b['_iOuter'] !== false || valid_config['dir'] === 'rtl') elLeft = elLeft + hBorder + 1;
					else elLeft = elLeft - hBorder - valid_config['iWidth'] - 1;
					$('<div/>').addClass('valid-js-div valid-result '+cssClass).insertAfter(insertEL).css({
						left:elLeft+'px',
						top:elTop+'px'
					});
					if (cssClass != 'good-valid'){
						var helper = $('<div/>').addClass('valid-js-div valid-helper '+cssClass+'-helper').html(txt);
						$('<i/>').appendTo(helper);
						helper.insertAfter(insertEL).css({
							left:elLeft + valid_config['iWidth']/2 + 'px',
							top:elTop - valid_config['iHeight']/2 - helper.height() + 'px'
						}).delay(valid_config['hhDelay']).fadeOut(valid_config['fDelay']);
						helper = null;
					}
				}
				insertEL = $b = b = null;
			});
		},
		validate : function(options,callback,group){
			var options = options || {},
				self = this;
			return self.isForm()? self.getValidElements().validate(options,callback,group) : self.each(function(a,b){
				var $b = $(b);
				if ((b.type != 'radio' && b.type != 'checkbox' && b['_validLast'] !== undefined && b['_validLast'] == b.value) || $b.is(':hidden')) return;
				if ((typeof callback).toLowerCase() == 'string') {
						group = callback;
						callback = null;
				}
				var opt = {}, clb;
				if (b['_validateOnce']){
					opt['valid_rule'] = $.makeArray(options['valid_rule']) || [];
					clb = callback;
				} else {
					opt['valid_rule'] = b['_valid_rule'] || valid_reg_exp['email'];
					clb = callback == 'void' ? null : $b.getGoodValidCallback();
				}
				opt['not_valid_answer'] = options['not_valid_answer'] || b['_not_valid_answer'] || valid_txt_loc['notValid'];
				if (!b['_validateOnce']) $b.addValidOptions(opt,clb,group);
				var grp = group || $b.getValidGroup(),
					grp_els = $b.getValidGroupElements(),
					grp_last_el = grp_els.last(),
					is_grp_last_el = b === grp_last_el[0],
					category = 1,
					answer_text = opt['not_valid_answer'];
				if (grp) clb = $.validGroupCallback[grp];
				b['_validLast'] = b.value;
				if ($b.hasClass('gray-text') && !$b.hasClass('no-template-check')){
					category = 0;
				} else {
					category = $b.validConformity(opt['valid_rule']);
					if (clb && ((!grp && category) || (grp && grp_els.isValid(true)))) {
						try{
							var results = clb(grp?grp_els:b,answer_text);
							if (results && !isNaN(results[0])) {
								if (grp) var grp_clb_category = results[0];
								else category = category*results[0];
							}
							if (results && results[1]) answer_text = results[1];
						} catch(e){
							$b.validDraw(0,valid_txt_loc['tryError'],true);
						}
					}
				}
				$b.setValid(category);
				b['_validateOnce'] = 0;
				if (grp){
					var grp_valid_result = grp_clb_category !== undefined ? grp_clb_category : grp_els.isValid(true);
					if (grp_els.notEmpty()) grp_last_el.validDraw(grp_valid_result,answer_text);
					$.validGroupResult[grp] = grp_valid_result;
				} else {
					$b.validDraw(category,answer_text);
				}
				grp_els = grp_last_el = $b = b = null;
			});
		},
		validateOnce : function(options,callback,group){
			return this.each(function(a,b){
				b['_validLast'] = '';
				b['_validateOnce'] = 1;
			}).validate(options,callback,group);
		},
		validCheck : function (){
			var self = this;
			return self.isForm() ? self.getValidElements().validCheck() : self.each(function(a,b){var $b = $(b); $b.setValid($b.validConformity()); $b = b = null});
		},
		validInit : function (){
			var self = this;
			return self.isForm() ? self.getValidElements().validInit() : self.each(function(a,b){
				var c = $(b), 
					aRule = (c.getValidAttr('valid_rule')|| '').replace(/^\//,'').replace(/\/$/,''), 
					aNwa = c.getValidAttr('not_valid_answer'), 
					cls = c.attr('class'),
					iou = c.getValidAttr('iouter'),
					reg = new RegExp('valid-css-[^\\s]+','ig'),
					classes = cls.match(reg) || [];
				if (aRule) c.addValidRule(aRule).removeAttr('valid_rule');
				if (aNwa) c.addNotValidAnswer(aNwa).removeAttr('not_valid_answer');
				if (iou) c.validDrawPosition(iou).removeAttr('iouter');
				$.each(classes,function(i,s){
					var rr = valid_reg_exp[s.replace('valid-css-','')];
					if (rr) c.addValidRule(rr);
				});
				c = null;
			}).validCheck();
		},
		resetValidation: function(){
			return this.each(function(a,b){
				b['_validLast'] = '';
				b['_isValid'] = undefined;
				$(b).nextAll('.valid-js-div').remove();
			});
		},
		resetToTemplate : function(){
			return this.each(function(a,b){
				if ((b.type == 'text' || b.type == 'textarea') && b['_template']){
					b.value=b['_template'];
					$(b).addClass('gray-text');
				}
			});
		},
		resetElement : function(){
			return this.each(function(a,b){
				$(b).val('').removeAttr('checked').removeAttr('selected').removeAttr('disabled').resetToTemplate().filter('select').each(function(a,b){
					b.options.selectedIndex = 0
				})
			})
		},
		resetForm : function (){
			return this.each(function(a,b){$(':input',b).not(':button, :submit, :reset, :hidden').resetElement().filter('.need-validation').resetValidation()});
		},
		templateInit : function(){
			return this.each(function(a,b){
				var c = $(b), 
					tpl = c.getValidAttr('valid_template');
				if (tpl) {
					if (!b.value) c.val(tpl);
					c.addTemplate(tpl).removeAttr('valid_template');
				} else {
					c.addTemplate(b.value);
				}
				c = null;
			});
		},
		addTemplateText : function(template){
			return this.each(function(a,b){b['_template'] = template});
		},
		removeTemplateText : function(){
			return this.each(function(a,b){b['_template'] = null});
		},
		addTemplate : function(template){
			return this.each(function(a,b){
				var c = $(b);
				c.addTemplateText(template).addClass('valid-template');
				if (b.value == template) c.addClass('gray-text');
				c = null;
			});
		},
		removeTemplate : function(){
			return this.each(function(a,b){$(b).removeTemplateText().removeClass('gray-text valid-template')});
		},
		setMaxLengthHelper : function(){
			return this.each(function(a,b){
				var el = $(b);
				if (el.nextAll('.textarea-maxlength-helper')[0]) return;
				var max = el.getValidAttr('maxlength');
				if (!max) return false;
				var offsetPosition = el.validOffsetPosition();
				 $('<span>').addClass('textarea-maxlength-helper').text(max).css({
						left:(offsetPosition.left + el.outerWidth()- 7*max.length - 1) + 'px',
						top:(offsetPosition.top + el.outerHeight() - 12) + 'px'
				}).width(7*max.length).insertAfter(el);
				el = b = null;
			});
		},
		changeMaxLengthHelper : function(e){
			return this.each(function(a,b){
				var el = $(b),
					max = el.getValidAttr('maxlength'),
					val = b.value.substr(0,max)
					len = val.length,
					ost = max-len,
					c = e.keyCode;
				el.val(val).nextAll('.textarea-maxlength-helper').toggleClass('warning',ost < Math.ceil(max/3)).text(ost);
				el = b = null;
				return c == 8 || c == 9 || c == 37 || c == 38 || c == 39 || c == 40 || c == 46 || len < max;
			});
		},
		upper : function (options){
			return this.each(function(a,b){
				var min = -1e+12, max = 1e+12;
					options = {
						position:options.position || 'right',
						range:options.range || [min,max],
						def:!isNaN(options.def)? options.def:null || '0',
						zIndex:options.zIndex || 999999
					},
					el = $(b).wrap('<span/>'),
					wrapper = el.parent().addClass('upper-wrapper'),
					css = {zIndex:options.zIndex},
					posClass = options.position.toLowerCase() == 'right'?' upper-onright':' upper-onleft';
				el.attr('maxlength',Math.max(options.range[0].toString().length,options.range[1].toString().length)).val(options.def);
				$('<input type="button" value=""/>').addClass('upper-more-click'+posClass).css(css).css('top',b.style.borderTopWidth?b.style.borderTopWidth:'2px').click(function(){
					var t = $(this).parent().find(':text'), v = t.val();
					if (!isNaN(v)){
						v<options.range[1] && v>=options.range[0]? t.val(++v):t.val(options.range[1]);
					} else {
						t.val(options.def);
					}
					return false;
				}).appendTo(wrapper);
				$('<input type="button" value=""/>').addClass('upper-less-click'+posClass).css(css).css('bottom',b.style.borderBottomWidth?b.style.borderBottomWidth:'2px').click(function(){
					var t = $(this).parent().find(':text'), v = t.val();
					if (!isNaN(v)){
						v<=options.range[1] && v>options.range[0]? t.val(--v):t.val(options.range[0]);
					} else {
						t.val(options.def);
					}
					return false;
				}).appendTo(wrapper);
			});
		}
	});
})(jQuery);
$(function(){
	$('.need-validation').validInit().filter(':text,textarea').add('.valid-template').not('.no-valid-template').templateInit();
	$('body').delegate(".need-validation", "change keyup", function(e){
		var el = this, $el = $(el);
		if (e.type == 'change'){
			$el.validate();
			$el = el = null;
		} else if (e.type == 'keyup'){
			var keyCodes = [9,13,16,17,18,20,27,33,34,35,36,37,39,93,112,113,114,115,116,117,118,119,120,121,122,123,144];
			if (el.tagName.toLowerCase() != 'select') keyCodes = keyCodes.concat([38,40]);
			if ($.inArray(e.keyCode,keyCodes) > -1) return;
			if (valid_config['ticker']) clearTimeout(valid_config['ticker']);
			valid_config['ticker'] = setTimeout(function(){
				$el.validate();
				$el = el = null;
			},$el.getValidTimer() || valid_config['kDelay']);
		}
	}).delegate(".need-validation.gray-text,.valid-template.gray-text", "click change keyup", function(){
		var el = this, $el = $(el);
		if (el.value == el['_template']) $el.removeClass('gray-text').val('');
		$el = el = null;
	}).delegate(".need-validation,.valid-template", "focusout", function(){
		var el = this, $el = $(el);
		if (el['_template'] && !$.trim(el.value)) $el.addClass('gray-text').val(el['_template']);
		$el = el = null;
	}).delegate(".valid-result","mouseenter",function(e){
		e.stopPropagation();
		$('.valid-helper').hide();
		$(this).prev('.valid-helper').show();
	}).delegate(".valid-result","mouseleave",function(e){
		e.stopPropagation();
		$('.valid-helper').hide();
	}).delegate('textarea[maxlength]','keyup change',function(e){
		return $(this).setMaxLengthHelper().changeMaxLengthHelper(e);
	});
	$('.need-validation[maxlength]').addGoodValidCallback(function(el){
		return [(el.value && el.value.length == el.maxlength)+1,valid_txt_loc['hasMaxLength']];
	});
	$('textarea[maxlength]').setMaxLengthHelper();
});
