//lib
	var inRange = function(value, min, max){
		return ((value>=min)&&(value<=max));
	}
	var inArray = function(value, array){
		return (jQuery.inArray(value, array) != -1);
	}
	var trim = function(string){
		return jQuery.trim(string); 
	}
	var round = function(value){
		return Math.round(value); 
	}
	var floor = function(value){
		return Math.floor(value); 
	}
	var escapeForUrl = function(input){
		input = input
			.replace(RegExp('\\+', 'gi'), '%2B')
			.replace(RegExp('\\\\', 'gi'), '%5c')
			.replace(RegExp('\\?', 'gi'), '%3F')
			.replace(RegExp('/', 'gi'), '%2F');
		return input;
	}
	var time = function(){
		var date = new Date();
		return date.getTime(); 
	}
	var each = function(array, action){
		jQuery.each(array, action);
	}
	var countArray = function(array){
		var count = 0;
		jQuery.each(array, function(){
			count++
		});
		return count;
	}
	var isset = function(variable){
		return (typeof( variable ) != 'undefined');
	}
	var set = function(variable, defaultValue){
		(!isset(defaultValue))&&(defaultValue = false);
		if ( typeof( variable ) != 'undefined' ){
			return variable;
		}
		return defaultValue;
	}
	var merge = function(first, second){
		each(second, function(key, value){
			first[key] = value;
		});
		return first;
	}
	var serialize = function(mixedValue){
		var type = typeof(mixedValue)
		var val 	= ''
		switch (type) {
		case "function": 
				val = ""; 
				break;
		case "undefined":
				val = "";
				break;
		case "boolean":
				val = "b:" + (mixedValue ? "1" : "0");
				break;
		case "number":
				val = (Math.round(mixedValue) == mixedValue ? "i" : "d") + ":" + mixedValue;
				break;
		case "string":
				val = "s:" + mixedValue.length + ":\"" + mixedValue + "\"";
				break;
		case "array":
				val = "a";
				var count = 0;
				var vals = "";
				for (var key=0; key < mixedValue.length; key++){
					vals +=serialize(key)+serialize(mixedValue[key]);
					count++;
				};
				val += ":"+count+":{"+vals+"}";
				break;
		case "object":
				val = "a";
				var count = 0;
				var vals = "";
				each(mixedValue, function(key){
					vals +=serialize(key)+serialize(mixedValue[key]);
					count++;
				});
				val += ":"+count+":{"+vals+"}";
				break;
		}
		if (type != "object" && type != "array") val += ";";
		return val;
	}
	var htmlEntities =  function(text){
		var entities = {
				'&amp;' : '&',
				'&lt;'	: '<',
				'&gt;'	: '>'
		};
		each(entities, function(from, to){
			text = text.replace(new RegExp(from,'gi'), to);
		});
		return text;
	}
var Event = (function(){
	var binds = {}
	return {
		bind: function(type, handler){
			(!isset(binds[type])) && (binds[type] = []);
			binds[type].push(handler);
		},
		notify: function(type, obj){
			var result = false;
			isset(binds[type]) && each(binds[type], function(){
				result = this(obj);
			})
			return result;
		}
	}
})();
var ServerAction = (function(input){
	var getJSON = function(url, data, handler, postMethod, count){
		(!isset(count))&&(count=0);         
		jQuery[postMethod](url, data, function(result, status){
			if (count<10){
				result = trim(result+'');
				if (result.match(RegExp('^[\\{]{1,}', 'gi'))){
					handler(eval('('+result+')'));
				}else if (result.length == 0){
					setTimeout(function(){
						getJSON(url, data, handler, postMethod, ++count);
					}, 2000);
				}else{
					//alert(result);
					(typeof(console)!= 'undefined')&&(console.debug(result));
				}
			}else{
				//alert('ajax error '+url+' '+result);
			}
		})
	}
	return function(input){
		(!isset(input.postMethod))&&(input.postMethod = 'get');
		getJSON('/ajax/'+input.method+'?'+time(), input.data, function(data){
			if (isset(data.status)){
				if (isset(input.handler)){
					if (isset(input.handler[data.status])){
						input.handler[data.status](data);
					}else if (typeof(input.handler)=='function'){
						input.handler(data);
					}else{
						alert('No handler '+data.status);
					}
				}
			}else{
				alert('Unknown result for'+'/ajax/'+input.method);
			}
		}, input.postMethod);
	}
})();
var PageReload = function(url){
	if (isset(url)){
		if(url.match(new RegExp('^http'))){
			window.document.location.href = url;
		}else{
			window.document.location.href = 'http://'+window.document.location.host+url;
		}
	}else{
		window.location.reload()
	}
}
var PatternInit = function(url, handler){
	var patterns = {};
	var returnFunction =  function(path, values){
		var pattern = patterns;
		if (isset(path)){
			var pathAr = path.split('.');
			for (var key=0; key<pathAr.length; key++){
				isset(pattern[pathAr[key]]) ? (pattern = pattern[pathAr[key]]) : alert('Undefined pattern '+path);
			}
		}
		if((typeof(pattern)=='string')&&(isset(values))){
			for(var from in values){
				pattern = pattern.replace(RegExp('\\$'+from, 'gi'), values[from]);
			}
		}
		return pattern;
	}
	if (isset(window.PatternCache)&&isset(PatternCache[url])){
		patterns = PatternCache[url];
		Event.notify('pattern.init', returnFunction);
		isset(handler) && handler();
	}else{
		jQuery.get(url+'?'+time(), function(data){
			if (jQuery.trim(data).search(RegExp('^{', 'gi')) != -1){
				patterns = eval('('+data.replace(RegExp('\n', 'gi'), '').replace(RegExp('\r', 'gi'), '')+')');
			} else {
				patterns = data+'';
			}
			Event.notify('pattern.init', returnFunction);
			isset(handler) && handler();
		});
	}
	return returnFunction;
}
var Buttons = function(target){
	var initButton = function(button){
		var self 		= this;
		self.button = button;
		self.initText = self.button.text();
		self.button.html(Pattern('button.inner'));
		self.setTitle(self.initText);
		self.disabled = false;
		self.button.click(function(){
			if (self.disabled){
				return false;
			} else {
				return self.click();
			}
		})
	}
	initButton.prototype = {
		wait: function(){
			var self = this;
			self.setTitle(Pattern('button.wait'));
			self.disable();
		},
		reset: function(){
			var self = this;
			self.setTitle(self.initText);
			self.enable();
		},
		click: function(){
		},
		enable: function(title){
			var self = this;
			isset(title) && self.setTitle(title);
			self.button.removeClass('disable');
			self.disabled = false;
		},
		disable: function(title){
			var self = this;
			isset(title) && self.setTitle(title);
			self.button.addClass('disable');
			self.disabled = true;
		},
		setTitle: function(title){
			var self = this;
			var getDeepestChild = function(obj){
				if (obj.children().length == 0) {
					return obj;
				} else {
					return getDeepestChild(obj.children().eq(0));
				}
			}
			//alert(title.replace(RegExp('\\s', 'gi'), '&nbsp;'));
			getDeepestChild(self.button).html(title.replace(RegExp('\\s', 'gi'), '&nbsp;'));
		}
	}
	var buttons = {};
	target.find('button').each(function(){
		button 												= jQuery(this);
		buttons[button.attr('name')] 	= new initButton(button);
	});
	return buttons;
}
var Inputs = function(target){
	var initInput = function(input){
		var self 						= this;
		self.input 					= input;
		self.initLableText 	= false;
		if (self.input.next().hasClass('subtitle')){
			self.initLableText = self.input.next().text();
		}
	}
	initInput.prototype = {
		value:function(value){
			var self = this;
			isset(value) && self.input.val(value);
			return jQuery.trim(self.input.val());
		},
		error: function(text){
			var self = this;
			self.reset();
			self.input.addClass('error');
			var subtitle = self.showSubtitle().addClass('error');
			if (isset(text)){
				subtitle.text(text)
			} else if(subtitle.text().length == 0) {
				self.hideSubtitle();
			}
		},
		hideSubtitle: function(){
			var self = this;
			(self.input.next().length > 0) &&
			(self.input.next().hasClass('subtitle')) &&
			(self.input.next().hide());
		},
		showSubtitle: function(){
			var self = this;
			if (
				(self.input.next().length > 0) 
				//&& (self.input.next().get(0).tagName.toLowerCase() == 'label')
				&& (self.input.next().hasClass('subtitle'))
			){
				return self.input.next().attr('class', 'subtitle').show();
			} else {
				return jQuery(Pattern('input.subtitle')).insertAfter(self.input);
			}
		},
		success: function(text){
			var self = this;
			self.reset();
			self.input.addClass('success');
			if (isset(text)){
				self.showSubtitle().addClass('success').text(text);
			} else {
				self.hideSubtitle();
			}
		},
		reset: function(){
			var self = this;
			self.input.removeClass('success').removeClass('error');
			(self.initLableText) 
				?(self.showSubtitle().text(self.initLableText))
				:(self.hideSubtitle());
		},
		bind: function(type, handler){
			var self = this;
			self[type+'Handler'] = handler;
			self.input.bind(type, function(){
				return self[type+'Handler']();
			})
		}
	}
	var inputs = {};
	target.find('input').each(function(){
		var input 									= jQuery(this);
		if (jQuery.trim(input.attr('name')).length = 0){
			alert('Empty name in input');
		}else{
			inputs[input.attr('name')] 	= new initInput(input);
		}
	});
	return inputs;
}
var Env = (function(){
	var env = {};
	return {
		get: function(path){
			if (isset(path)){
				var pathAr = path.split('.');
				var cursor = env;
				each(pathAr, function(){
					if (isset(cursor[this+''])){
						cursor = cursor[this+''];
					}else{
						cursor = false;
					}
				});
				return cursor;
			}else{
				return env;
			}
		},
		set: function(inputArray){
			if (isset(inputArray)){
				env = merge(env, inputArray);
			}
		}
	}
})();
var AjaxHistory = function(){
	var init = function(){
		var self = this;
		jQuery(function(){
			jQuery.browser.msie && self.addIframe();
		});
	}
	init.prototype = {
		
		globalSetHash: function(hash){
			var location = window.document.location.href;
			window.document.location.href = location.replace(new RegExp('#[^\\s]*'), '#'+hash);
		},
		
		addIframe: function(){
			var self = this;
			self.iframe = jQuery('<iframe></iframe>').hide();
			jQuery('body').append(self.iframe);
			self.setHash(self.getHash());
		},
		
		getHash: function(){
			var match = (window.document.location.href.match(new RegExp('#[^\\s]*')));
			if (match) {
				return match[0].replace(new RegExp('#'), '');
			} else {
				return '';
			}
		},
		setHash: function(hash){
			var self = this;
			var location = window.document.location.href;
			if (location.match(new RegExp('#[^\\s]*'))){
				window.document.location.href = location.replace(new RegExp('#[^\\s]*'), '#'+hash);
			} else {        
				window.document.location.href = location+'#'+hash;
			}
			//for ie
			if (jQuery.browser.msie){
				var frame = self.iframe.get(0);
				var output = frame.contentDocument || frame.contentWindow.document;
				output.open();
				output.write('<script type="text/javascript">window.onload = function(){parent.AjaxHistory.globalSetHash("'+hash+'")}</script>');
				output.close();
			}
		}
	}
	return new init();
}();
var Hash = (function(){
	var initHash = function(){
		var self = this;
		self.hash = '';
		self.hashObj = {};
		self.binds = [];
		self.waiter();
		setInterval(function(){
			self.waiter();
		}, 300);
	}
	initHash.prototype = {
		set: function(hashObj){
			var self = this;
			self.hashObj = hashObj;
			var hashAr = [];
			each(hashObj, function(key, value){
				hashAr.push(key+':'+value);
			});
			self.hash = hashAr.join('/');
			if (AjaxHistory.getHash() != self.hash){
				AjaxHistory.setHash(self.hash);
			}
		},
		get: function(name){
			var self = this;
			self.setHash();
			if (isset(name)){
				return (isset(self.hashObj[name]))&&(self.hashObj[name]);
			}else{
				return self.hashObj;
			}
		},
		setHash: function(){
			var self = this;
			var hash = AjaxHistory.getHash();
			if (hash != self.hash){
				self.hash 		= hash;
				self.hashObj 	= {};
				each(self.hash.split('/'), function(){
					var itemAr = (this+'').split(':');
					if (itemAr.length == 2){
						self.hashObj[itemAr[0]] = itemAr[1];
					}
				});
			}
		},
		waiter: function(){
			var self = this;
			var hash = AjaxHistory.getHash();
			if (hash != self.hash){
				self.setHash();
				for (var key=0; key<self.binds.length; key++){
					self.binds[key](self.hashObj);
				}
				//if(self.hash.length > 0){
				//	each(self.hashObj, function(key, value){
				//		if (isset(self.binds[key])){
				//			each(self.binds[key], function(){
				//				this(value);
				//			});
				//		}
				//	});
				//}
			}
			//setTimeout(function(){
			//	self.waiter();
			//}, 300);
		},
		bind: function(handler){
			var self = this;
			self.binds.push(handler);
		}
	}
	return new initHash();
})()
var AddMethods = function(obj, prototype){
	each(prototype, function(key, value){
		obj.prototype[key] = value;
	})
}
var Mailto = (function(){
	var domReady = false;
	jQuery(function(){
		domReady = true;
	})
	var addMails = function(mails){
		jQuery('a.mailto').each(function(){
			var link = jQuery(this);
			var className = link.attr('class').replace(RegExp('(mailto)|[\\s]{1,}', 'gi'), '');
			if (className.length > 0 && isset(mails[className])){
				var url = mails[className].login+'@'+mails[className].host;
				link.attr('href', 'mailto:'+url);
				if (trim(link.html()).length == 0){
					link.html(url);
				}
			}
		})
	}
	return function(mails){
		if (domReady){
			addMails(mails);
		}else{
			jQuery(function(){
				addMails(mails);
			})
		}
	}
})()
var SelectSimple = (function(){
	Pattern = false;
	Event.bind('pattern.init', function(getPattern){
		Pattern = getPattern;
	})
	var initSelect = function(element){
		var self = this;
		if (Pattern){
			self.element = element;
			self.element.hide();
			self.optionElements = {};
			self.value = function(value){
				if (isset(value)){
					self.element.val(value);
					self.highlightByValue();
				}else{
					return trim(self.element.val());
				}
			}
			self.highlightByValue = function(){
				each(self.optionElements, function(){
					this.removeClass('select');
				});
				isset(self.optionElements[element.val()])&&(self.optionElements[element.val()].addClass('select'));
			}
			self.main = jQuery(Pattern('simpleSelect.select'));
			//init options
			self.element.find('option').each(function(){
				var option = jQuery(this);
				var optionElement = jQuery(Pattern('simpleSelect.option', {
					title: option.text()
				}));
				var value = option.attr('value');
				self.optionElements[value] = optionElement;
				optionElement.click(function(){
					self.element.val(value);
					self.highlightByValue();
				})
				if (isset(option.attr('title'))){
					optionElement.attr('title', option.attr('title'));
				}
				self.main.append(optionElement);
			});
			self.highlightByValue();
			self.element.after(self.main);
		}else{
			alert('No pattern for SelectSimple');
		}
	}
	return function(selects){
		var returnSelects = {};
		selects.each(function(){
			returnSelects[jQuery(this).attr('name')] = new initSelect(jQuery(this));
		})
		return returnSelects;
	}
})();
var Popup = (function(){
	var popupPattern 	= false;
	var popupInstance = false;
	Event.bind('pattern.init', function(Pattern){
		popupPattern = Pattern('popup');
	})
	var initPopup = function(){
		var self = this;
		if (!popupPattern){
			alert('No pattern for popup');
		}else{
			self.main = jQuery(popupPattern);
			self.main.hide();
			jQuery('body').append(self.main);
			self.offset 				= self.main.find('.popup-relative');
			self.windowHolder		= self.main.find('.popup-window-background');
			self.contentHolder 	= self.main.find('.popup-content-holder');
			self.closeButton 		= self.main.find('.popup-close');
			self.closeButton.click(function(){
				self.close();
			});
			self.bgCloseBlock = true;
			self.contentHolder.hover(function(){
				self.bgCloseBlock = true;
			}, function(){
				self.bgCloseBlock = false;
			})
			self.closeButton.click(function(){
				self.close();
			});
			self.main.click(function(){
				(!self.bgCloseBlock)&&self.close();
			});
		}
	}
	initPopup.prototype = {
		setSize: function(content){
			var self = this;
			self.windowHolder.width(
				content.width()
				+parseInt(self.contentHolder.css('paddingLeft'))
				+parseInt(self.contentHolder.css('paddingRight'))
			);
			var offset = round(((content.height()
				+parseInt(self.contentHolder.css('paddingTop'))
				+parseInt(self.contentHolder.css('paddingBottom'))))/2+50);
			self.offset.css('top',
				'-'+offset+'px'
			);
		},
		close: function(){
			var self = this;
			self.main.hide();
			Event.notify('popup.close');
		},
		open: function(content){
			var self = this;
			if (isset(content)){
				self.contentHolder.html('');
				self.contentHolder.append(content);
			}
			self.main.show();
			isset(content)&&self.setSize(content);
			Event.notify('popup.show');
		}
	}
	return {
		open: function(content){
			(!popupInstance)&&(popupInstance = new initPopup);
			popupInstance.open(content);
			return popupInstance;
		}
	}
})()
