jQuery.fn.liveSearch = function (conf) {
	var config = jQuery.extend({
		url:		'/',
		id:			'jquery-live-search',
		hoverClass:	'hover',
		searchBtn:  {
			id: 'submit',
			enabled_class: '',
			disabled_class: ''
		},
		searchPage:	'/search/',
		minChars:	2,
		duration:	400,
		typeDelay:	400,
		loadingClass: 'loading',
		onSlideUp:	function () {}
	}, conf);
	
	var liveSearch	= jQuery('#' + config.id);
	var searchBtn = jQuery('#' + config.searchBtn.id);
	var currentSelection = null;
	var searchValue = null;
	
	var enterAvaliable = false;
	
	var KEY = {
		LEFT: 37,
		UP: 38,
		RIGHT: 39,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8,
		END: 35,
		HOME: 36
	};

	if (!liveSearch.length) {
		liveSearch = jQuery('<div id="' + config.id + '"></div>').appendTo(document.body).hide().slideUp(0);
		
		if (config.listClass) {
			liveSearch.addClass(config.listClass);
		}
	}
		
	var resultsShit	= parseInt(liveSearch.css('paddingLeft'), 10) + parseInt(liveSearch.css('paddingRight'), 10) + parseInt(liveSearch.css('borderLeftWidth'), 10) + parseInt(liveSearch.css('borderRightWidth'), 10);

	jQuery(document.body).click(function(event) {
		var clicked = jQuery(event.target);

		if (!(clicked.is('#' + config.id) || clicked.parents('#' + config.id).length || clicked.is('input'))) {
			liveSearch.slideUp(config.duration, function () {
				config.onSlideUp();
			});
		}
	});
	
	function updown(input, dir){
		if ((cnt = $('dd', liveSearch).size()) == 0) {
			return false;
		}
		var idx = getSelectedIndex() + dir;
		$('dd', liveSearch).removeClass(config.hoverClass);
		if (idx == -1 || idx == cnt) {
			input.val(searchValue);
			currentSelection = null;
			return;
		}
		idx = idx < 0 ? cnt - 1 : (idx > cnt ? 0 : idx);
		$('dd', liveSearch).eq(idx).addClass(config.hoverClass);
		input.val($('a', $('dd', liveSearch).eq(idx)).html());
		currentSelection = {
			index: idx,
			link: $('a', $('dd', liveSearch).eq(idx)).attr('href')
		}
	}
	
	function getSelectedIndex() {
		return $('dd').index($('dd.hover'));
	}
	
	function enter(input){		
		var cnt = $('dd', liveSearch).size();
		if (currentSelection || (!currentSelection && cnt == 1)){
			if (cnt == 1) {
				currentSelection = {
					index: 0,
					link: $('a', $('dd', liveSearch).eq(0)).attr('href')
				}
			}
			document.location.href = currentSelection.link;
		} else if ($.trim(input.val()).length >= config.minChars) {
			document.location.href = config.searchPage + '?' + $.param({'query': $.trim(input.val())});
		}
	}
	
	function reset(){
		searchValue = null;
		currentSelection = null;
		$('dd', liveSearch).removeClass('hover');
		hide();
	}
	
	function hide(){
		liveSearch.slideUp(config.duration, function () {
			config.onSlideUp();
		});
	}
	
	function checkBtn(input, disable){
		var btnClass = config.searchBtn.disabled_class;
		if ($.trim(input.value).length >= config.minChars && !disable) {
			btnClass = config.searchBtn.enabled_class;
		}
		if (btnClass != ''){
			searchBtn.attr('class', btnClass);
		}
	}
	
	function onChange(input){
		if (input.value == input.lastValue) {
			return;
		}
		checkBtn(input, true);
		if ($.trim(input.value).length >= config.minChars) {
			$(input).addClass(config.loadingClass);
			//enterAvaliable = true;
			var q = $.trim(input.value);

			var requestUrl = config.url + '?' + $.param({'q': q});
			if ($.param(config.params).length > 0) {
				requestUrl += '&' + $.param(config.params);
			}
			jQuery.get(requestUrl, function (data) {
				$(input).removeClass(config.loadingClass);

				if ($.trim(data).length && q.length) {
					
					checkBtn(input);
					enterAvaliable = true;
					
					searchValue = q;
					var tmpOffset	= $(input).offset();
					var inputDim	= {
						left:		tmpOffset.left,
						top:		tmpOffset.top,
						width:		$(input).outerWidth(),
						height:		$(input).outerHeight()
					};

					inputDim.topNHeight	= inputDim.top + inputDim.height;
					inputDim.widthNShit	= inputDim.width - resultsShit;
					liveSearch.html(data).slideDown(config.duration);
				} else {
					enterAvaliable = false;
					reset();
				}
			});
			input.lastValue = input.value;
		} else {
			input.lastValue = null;
			reset();
		}
	}

	return this.each(function () {
		var input = jQuery(this).attr('autocomplete', 'off');
		
		checkBtn(this);
		
		searchBtn.bind('click', function(){
			if (config.searchBtn.disabled_class != '' && $(this).hasClass(config.searchBtn.disabled_class)){
				return;
			}
			var query_str = input.val();
			var search_url = config.searchPage + '?' + $.param({'query': $.trim(query_str)});
			if (config.params && config.params['search_types[]']){
				for (var type in config.params['search_types[]']){
					search_url += '&search_types[]=' + config.params['search_types[]'][type];
				}
			}
			document.location.href = search_url;
		});
		
		input.bind($.browser.opera ? 'keypress' : 'keydown', function(event) {
			switch(event.keyCode) {
				case KEY.UP:
					event.preventDefault();
					if (liveSearch.css('display') == 'block'){
						updown(input, -1);
					}
					break;
				case KEY.DOWN:
					event.preventDefault();
					if (liveSearch.css('display') == 'block'){
						updown(input, 1);
					}
					break
				case KEY.RETURN:
					event.preventDefault();
					if ($.trim(this.value) != '' && enterAvaliable){
						enter(input);
					}
					break;
				case KEY.ESC:
					reset();
					break;
				default:
					if (this.timer) {
						clearTimeout(this.timer);
					}
					this.timer = setTimeout(function(p){
						return function(){onChange(p);}
					}(this), config.typeDelay);
			}
		});
	});
};