function autoSuggest(target, options) {
	var _this = this;
	var _id;
	var _key = { BACKSPACE: 8, TAB: 9, RETURN: 13, ESC: 27, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, DELETE: 46, HOME: 36, END: 35, PAGEUP: 33, PAGEDOWN: 34, INSERT: 45 };

	this.input = $('#' + target);
	this.input.attr('autocomplete', 'off');
	this.id = target;
	this.data = null;
	this.selectedIndex = -1;
	this.currentValue = this.input.val();
	this.ignoreValueChange = false;
	this.enabled = false;
	this.options = {
		autoSubmit: false,
		minChars: 1,
		maxHeight: 300,
		width: -1,
		serviceUrl: '',
		serviceObj: null,
		servicePrpIn: '',
		servicePrpOut: '',
		serviceName: '',
		maxResults: 10,
		forceUTF8: false,
		onSelect: null,
		onSuggest: null,
		onSubmit: null
	};
	$.extend(this.options, options);

	this.holder = $();
	this.container = $();

	this._fixPosition = function() {
		var offset = this.input.offset();
		var height = this.input.outerHeight() + 1;
		this.holder.css({ top: offset.top + height + 'px', left: offset.left + 'px' });
	};

	this._keyDown = function(e) {
		if (!this.enabled)
			return;
		switch (e.keyCode) {
			case _key.ESC:
				this.input.val(this.currentValue);
				this._hide();
				break;
			case _key.TAB:
			case _key.RETURN:
				if (this.selectedIndex == -1) {
					this._hide();
					this._onSubmit(this.input.val());
				} else
					this._select(this.selectedIndex);
				break;
			case _key.UP:
				this._moveUp();
				break;
			case _key.DOWN:
				this._moveDown();
				break;
			default:
				return;
		}
	};

	this._keyUp = function(e) {
		switch (e.keyCode) {
			case _key.UP:
			case _key.DOWN:
				return;
		}
		if (this.currentValue != this.input.val())
			this._onValueChange();
	};

	this._blur = function() {
		$(document.body).one('click', function() {
			_this._hide();
		});
	};

	this._onValueChange = function() {
		this.currentValue = this.input.val();
		this.selectedIndex = -1;
		if (this.ignoreValueChange) {
			this.ignoreValueChange = false;
			return;
		}
		if (this.currentValue == '' || this.currentValue.length < this.options.minChars)
			this._hide();
		else
			this._getSuggestions();
	};

	this._getSuggestions = function() {
		if (this.options.serviceObj.hasOwnProperty(this.options.servicePrpIn))
			this.options.serviceObj[this.options.servicePrpIn] = this.currentValue;
		$.ajax({
			url: this.options.serviceUrl,
			type: 'POST',
			data: Object.toJSON({ objSearch: this.options.serviceObj }),
			processData: false,
			contentType: 'application/json; charset=utf-8',
			dataType: 'json',
			success: function(data) {
				_this.data = data;
				_this._suggest();
			}
		});
	}

	this._suggest = function() {
		if (this.data.length == 0) {
			this._hide();
			return;
		}

		this.container.empty();
		this.container.height('auto');

		var content = [];
		if ($.isFunction(this.options.onSuggest))
			(this.options.onSuggest)(this);
		else {
			//content.push((this.selectedIndex === i ? '<div class="selected"' : '<div'), ' title="', this.data[i][this.options.servicePrp], '" onclick="GBAutoSuggest.instances[', this.instanceId, '].select(', i, ');" onmouseover="GBAutoSuggest.instances[', this.instanceId, '].activate(', i, ');">', this.highlight(this.data[i][this.options.servicePrp], re), '<\/div>');
			var re = new RegExp(this.currentValue.match(/\w+/g), 'gi');
			$.each(this.data, function(item, value) {
				var div = $('<div />');
				if (_this.selectedIndex == item)
					div.addClass('selected');
				div.attr('title', value[_this.options.servicePrpOut]);
				div.html(_this._highlight(value[_this.options.servicePrpOut], re));

				div.mouseover(function() {
					_this._activate(item);
				});

				div.click(function() {
					_this._select(item);
				});

				div.appendTo(_this.container);
			});
		}

		this.enabled = true;
		this.holder.show();

		if (this.container.height() > this.options.maxHeight) {
			this.container.height(this.options.maxHeight)
		}
	};

	this._hide = function() {
		this.holder.slideUp(100, function() {
			_this.enabled = false;
			_this.selectedIndex = -1;
		});
	};

	this._moveUp = function() {
		if (this.selectedIndex == -1)
			return;
		if (this.selectedIndex == 0) {
			var divs = this.container.contents();
			divs.eq(0).removeClass();
			this.selectedIndex = -1;
			this.input.val(this.currentValue);
			return;
		}
		this._adjustScroll(this.selectedIndex - 1);
	}

	this._moveDown = function() {
		if (this.selectedIndex == (this.data.length - 1))
			return;
		this._adjustScroll(this.selectedIndex + 1);
	}

	this._adjustScroll = function(index) {
		var activeItem = this._activate(index);
		var offsetTop = activeItem.offset().top;
		var upperBound = this.container.scrollTop();
		var lowerBound = upperBound + this.options.maxHeight - 25;
		if (offsetTop < upperBound)
			this.container.scrollTop(offsetTop);
		else if (offsetTop > lowerBound)
			this.container.scrollTop(offsetTop - this.options.maxHeight + 25);
		//var scrollHeight = this.container[0].scrollHeight;
	};

	this._activate = function(index) {
		var divs = this.container.contents();
		if (this.selectedIndex != -1 && divs.length > this.selectedIndex)
			divs.eq(this.selectedIndex).removeClass();
		this.selectedIndex = index;
		var activeItem;
		if (this.selectedIndex != -1 && divs.length > this.selectedIndex) {
			activeItem = divs.eq(this.selectedIndex);
			activeItem.addClass('selected');
		}
		return activeItem;
	};

	this._select = function(index) {
		var selectedValue = this.data[index];
		if (selectedValue) {
			this.input.val(selectedValue[this.options.servicePrpIn]);
			//if (this.options.autoSubmit && this.input.form)
			//	this.input.form.submit();
			this.ignoreValueChange = true;
			this._hide();
			this._onSelect(index);
		}
	};

	this._genId = function(number) {
		var chrs = ['A', 'B', 'C', 'D', 'E', 'F',
					'G', 'H', 'I', 'J', 'K', 'L',
					'M', 'N', 'O', 'P', 'Q', 'R',
					'S', 'T', 'U', 'V', 'W', 'X',
					'Y', 'Z', '1', '2', '3', '4',
					'5', '6', '7', '8', '9', '0',
					'a', 'b', 'c', 'd', 'e', 'f',
					'g', 'h', 'i', 'j', 'k', 'l',
					'm', 'n', 'o', 'p', 'q', 'r',
					's', 't', 'u', 'v', 'w', 'x',
					'y', 'z'];
		var results = [];
		for (var i = 0; i < number; i++) {
			var seed = Math.round((chrs.length - 2) * Math.random()) + 1;
			results.push(chrs[seed]);
		}
		return results.join('');
	};

	this._highlight = function(value, re) {
		return value.replace(re, function(match) {
			return '<b>' + match + '<\/b>';
		});
	};

	this._onSelect = function(index) {
		if ($.isFunction(this.options.onSelect))
			(this.options.onSelect)(this.data[index]);
	};

	this._onSubmit = function(value) {
		if ($.isFunction(this.options.onSubmit))
			(this.options.onSubmit)(value);
	};

	this._initialize = function() {
		_id = this._genId(9);

		this.holder = $('<div />');
		this.holder.attr('id', 'autoSuggest_Holder_' + _id);
		this.holder.css({ position: 'absolute', display: 'none' });
		this.holder.appendTo('body');

		var divW1 = $('<div />');
		divW1.addClass('autoSuggest-w1');
		divW1.appendTo(this.holder);

		var divW2 = $('<div />');
		divW2.addClass('autoSuggest-w2');
		divW2.appendTo(divW1);

		this.container = $('<div />');
		this.container.attr('id', 'autoSuggest_Container_' + _id);
		this.container.addClass('autoSuggest');
		this.container.width(this.input.innerWidth());
		this.container.appendTo(divW2);

		this.input.keydown(function(e) {
			_this._keyDown(e);
		});

		this.input.keyup(function(e) {
			_this._keyUp(e);
		});

		this.input.blur(function() {
			_this._blur();
		});

		this.input.focus(function() {
			_this._fixPosition();
		});

		this._fixPosition();
	};

	$(function() {
		_this._initialize();
	});
}
