(function($)
{
	var _request = null;

	$.fn.autocomplete = function(options)
	{
		var options = $.extend({}, $.fn.autocomplete.defaults, options);
		return this.each(function()
		{
			new $.Autocompleter(this, options);
		});
	}
	$.fn.autocomplete.defaults =
	{
		url: null,
		minLength: 2,
		resultsClass: "ac_results"
	};

	$.Autocompleter = function(input, options)
	{
		var $input = $(input).attr("autocomplete", "off");
		var $list = $("<ul/>")
			.addClass(options.resultsClass)
			.css("position", "absolute")
			.appendTo(document.body)
			.mouseover(function(event)
			{
				if (target(event).nodeName && target(event).nodeName.toUpperCase() == "LI")
				{
					active = $("li", $list).removeClass("ac_over").index(target(event));
					$(target(event)).addClass("ac_over");
				}
			})
			.click(function(event)
			{
				$list.hide();
				var value = input.value;
				var data = $.data(listItems[active], "ac_data");
				var unit = getPositionUnit(value, lastCaretPosition);
				var start = unit.start;
				var end = unit.end;
				if (start > 0)
					start++;
				var value = value.substring(0, start - 1) + (start == 0 ? "" : " ") + data + value.substring(end + 1);
				$input.val(value);
				start = end = start + data.length;
				if (input.createTextRange)
				{
					var range = input.createTextRange();
					range.collapse(true);
					range.moveStart("character", start);
					if (start != end)
						range.moveEnd("character", end);
					range.select();
				}
				else if (input.setSelectionRange)
					input.setSelectionRange(start, end);
				else if (input.selectionStart)
				{
					input.selectionStart = start;
					input.selectionEnd = end;
				}
				input.focus();
				return false;
			}).mousedown(function() {
				config.mouseDownOnSelect = true;
			}).mouseup(function() {
				config.mouseDownOnSelect = false;
			});
		var active = -1;
		var listItems;
		var previousValue = "";
		var lastCaretPosition;
		var hasFocus = 0;
		var config =
		{
			mouseDownOnSelect: false
		};
		$input.bind("keyup", function(event)
		{
			if (event.keyCode == 13)
				return false;
			lastCaretPosition = getCaretPosition(input);
			var value = input.value;
			var unit = getPositionUnit(value, lastCaretPosition);
			value = jQuery.trim(value.substring(unit.start, unit.end + 1));
			//status = lastCaretPosition + ", " + unit.start + ", " + unit.end + ", " + (unit.end - unit.start + 1) + ", " + value;
			if (value.length < options.minLength || value == previousValue)
				return;
			previousValue = value;
			if (value.length)
			{
				$input.addClass("ac_loading");
				if (_request)
					_request.abort();
				_request = $.ajax(
				{
					async: true,
					contentType: "application/json; charset=utf-8",
					dataType: "json",
					type: "POST",
					url: options.url,
					data: JSON.stringify({q: value}),
					success: function(data)
					{
						$list.empty();
						var values = data.d;
						if (!values.length || (values.length == 1 && values[0][0] == value))
							$list.hide();
						else
						{
							for (var i = 0; i < values.length; i++)
							{
								var v = values[i][0];
								var li = $("<li/>").html(v).appendTo($list)[0];
								$.data(li, "ac_data", v);
							}
							listItems = $list.find("li");
							listItems.slice(0, 1).addClass("ac_over");
							active = 0;
							var offset = $input.offset();
							$list.css({
								width: $input.width() + 3,
								top: offset.top + input.offsetHeight,
								left: offset.left
							}).show();
						}
						$input.removeClass("ac_loading");
					},
					error: function(XMLHttpRequest, textStatus, errorThrown)
					{
						alert(textStatus);
					}
				});
			}
			else
				$list.hide();
		})
		.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event)
		{
			switch (event.keyCode)
			{
				case 38:
					moveSelect(-1);
					break;
				case 40:
					moveSelect(1);
					break;
				case 13:
					if ($list.is(":visible"))
					{
						$list.click();
						event.preventDefault();
						return false;
					}
					break;
			}
		}).focus(function()
		{
			hasFocus++;
		}).blur(function()
		{
			hasFocus = 0;
			if (!config.mouseDownOnSelect)
				$list.hide();
		});

		function getCaretPosition(input)
		{
			if (input.createTextRange)
			{
				var range = document.selection.createRange();
				range.moveStart("textedit", -1);
				return range.text.length;
			}
			return input.selectionStart;
		}

		function getPositionUnit(value, caret)
		{
			var start = caret, end;
			if (value.charAt(caret) == ",")
				start--;
			for (start = start; start > 0; start--)
				if (value.charAt(start) == ",")
				{
					start++;
					break;
				}
			for (end = caret; end < value.length; end++)
				if (value.charAt(end) == ",")
				{
					end--;
					break;
				}
			return {start: start, end: end};
		}

		function target(event)
		{
			var element = event.target;
			while(element && element.tagName != "LI")
				element = element.parentNode;
			if (!element)
				return [];
			return element;
		}

		function moveSelect(step)
		{
			listItems.slice(active, active + 1).removeClass("ac_over");
			active += step;
			if (active < 0)
				active = 0;
			else if (active >= listItems.size())
				active = listItems.size() - 1;
			var activeItem = listItems.slice(active, active + 1).addClass("ac_over");
			var offset = 0;
			listItems.slice(0, active).each(function()
			{
				offset += this.offsetHeight;
			});
			if ((offset + activeItem[0].offsetHeight - $list.scrollTop()) > $list[0].clientHeight)
				$list.scrollTop(offset + activeItem[0].offsetHeight - $list.innerHeight());
			else if (offset < $list.scrollTop())
				$list.scrollTop(offset);
		};
	};
})(jQuery);
