jQuery.autocomplete = function(input, options){me=this;$input=$(input).attr("autocomplete","off");if(options.inputClass)$input.addClass(options.inputClass); results=document.createElement("div"); $results=$(results);$results.hide().addClass(options.resultsClass).css("position", "absolute");if(options.width>0)$results.css("width",options.width);$("body").append(results);input.autocompleter=me; timeout=null; prev=""; active=-1; cache={}; keyb=false; hasFocus=false; lastKeyPressCode=null;function flushCache(){cache={};cache.data={};cache.length=0;};flushCache();if(options.data!=null){ sFirstChar="",stMatchSets={},row=[];if(typeof options.url!="string")options.cacheLength = 1;for( i=0;i<options.data.length;i++){row=((typeof options.data[i]=="string")?[options.data[i]]:options.data[i]);if(row[0].length>0){sFirstChar=row[0].substring(0,1).toLowerCase();if(!stMatchSets[sFirstChar] ) stMatchSets[sFirstChar]=[];stMatchSets[sFirstChar].push(row);}}for( k in stMatchSets){options.cacheLength++;addToCache(k,stMatchSets[k]);}}$input.keydown(function(e){lastKeyPressCode=e.keyCode;switch(e.keyCode){case 38:e.preventDefault();moveSelect(-1);break;case 40:e.preventDefault();moveSelect(1);break;case 9:case 13:if(selectCurrent()){$input.get(0).blur();e.preventDefault();}break;default:active=-1;if(timeout)clearTimeout(timeout);timeout=setTimeout(function(){onChange();},options.delay);break;}}).focus(function(){hasFocus=true;}).blur(function(){hasFocus=false;hideResults();});hideResultsNow();function onChange(){if(lastKeyPressCode==46||(lastKeyPressCode>8&&lastKeyPressCode<32))return $results.hide(); v=$input.val();if(v==prev)return;prev=v;if(v.length>=options.minChars){$input.addClass(options.loadingClass);requestData(v);}else{$input.removeClass(options.loadingClass);$results.hide();}};function moveSelect(step){ lis=$("li",results);if(!lis)return;active+=step;if(active<0){active=0;}else if(active>=lis.size()){active=lis.size()-1;}lis.removeClass("ac_over");$(lis[active]).addClass("ac_over");autoFill($(lis[active]).text());};function selectCurrent(){ li=$("li.ac_over",results)[0];if(!li){ $li=$("li",results);if(options.selectOnly){if($li.length==1)li=$li[0];}else if(options.selectFirst){li=$li[0];}}if(li){selectItem(li);return true;}else{return false;}
};function selectItem(li){if(!li){li=document.createElement("li");li.extra= [];
li.selectValue = "";}
v = $.trim(li.selectValue ? li.selectValue : li.innerHTML);
input.lastSelected = v;
prev = v;
$results.html("");
$input.val(v);
hideResultsNow();
if (options.onItemSelect) setTimeout(function() { options.onItemSelect(li) }, 1);};

	function createSelection(start, end){
		 field = $input.get(0);
		if( field.createTextRange ){
			 selRange = field.createTextRange();
			selRange.collapse(true);
			selRange.moveStart("character", start);
			selRange.moveEnd("character", end);
			selRange.select();
		} else if( field.setSelectionRange ){
			field.setSelectionRange(start, end);
		} else {
			if( field.selectionStart ){
				field.selectionStart = start;
				field.selectionEnd = end;
			}
		}
		field.focus();
	};

	function autoFill(sValue){
		if( lastKeyPressCode != 8 ){
			$('#log').text('---' + prev.length);
			$input.val(sValue);
			createSelection(prev.length, sValue.length);
		}
	};

	function showResults() {
		 pos = findPos(input);
		 iWidth = (options.width > 0) ? options.width : $input.width();
		$results.css({
			width: parseInt(iWidth) + "px",
			top: (pos.y + input.offsetHeight) + "px",
			left: pos.x + "px"
		}).show();
	};

	function hideResults() {
		if (timeout) clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		if (timeout) clearTimeout(timeout);
		$input.removeClass(options.loadingClass);
		if ($results.is(":visible")) {
			$results.hide();
		}
		if (options.mustMatch) {
			 v = $input.val();
			if (v != input.lastSelected) {
				selectItem(null);
			}
		}
	};

	function receiveData(q, data) {
		if (data) {
			$input.removeClass(options.loadingClass);
			results.innerHTML = "";

			if( !hasFocus || data.length == 0 ) return hideResultsNow();

			if ($.browser.msie) {
				$results.append(document.createElement('iframe'));
			}
			results.appendChild(dataToDom(data));
			if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]);
			showResults();
		} else {
			hideResultsNow();
		}
	};

	function parseData(data) {
		if (!data) return null;
		 parsed = [];
		 rows = data.split(options.lineSeparator);
		for ( i=0; i < rows.length; i++) {
			 row = $.trim(rows[i]);
			if (row) {
				parsed[parsed.length] = row.split(options.cellSeparator);
			}
		}
		return parsed;
	};

	function dataToDom(data) {
		 ul = document.createElement("ul");
		 num = data.length;
		if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;

		for ( i=0; i < num; i++) {
			 row = data[i];
			if (!row) continue;
			 li = document.createElement("li");
			if (options.formatItem) {
				li.innerHTML = options.formatItem(row, i, num);
				li.selectValue = row[0];
			} else {
				li.innerHTML = row[0];
				li.selectValue = row[0];
			}
			 extra = null;
			if (row.length > 1) {
				extra = [];
				for ( j=1; j < row.length; j++) {
					extra[extra.length] = row[j];
				}
			}
			li.extra = extra;
			ul.appendChild(li);
			$(li).hover(
				function() { $("li", ul).removeClass("ac_over"); $(this).addClass("ac_over"); active = $("li", ul).indexOf($(this).get(0)); },
				function() { $(this).removeClass("ac_over"); }
			).click(function(e) { e.preventDefault(); e.stopPropagation(); selectItem(this) });
		}
		return ul;
	};

	function requestData(q) {
		if (!options.matchCase) q = q.toLowerCase();
		 data = options.cacheLength ? loadFromCache(q) : null;
		if (data) {
			receiveData(q, data);
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			$.get(makeUrl(q), function(data) {
				data = parseData(data);
				addToCache(q, data);
				receiveData(q, data);
			});
		} else {
			$input.removeClass(options.loadingClass);
		}
	};

	function makeUrl(q) {
		 url = options.url + "?q=" + encodeURI(q);
		for ( i in options.extraParams) {
			url += "&" + i + "=" + encodeURI(options.extraParams[i]);
		}
		return url;
	};

	function loadFromCache(q) {
		if (!q) return null;
		if (cache.data[q]) return cache.data[q];
		if (options.matchSubset) {
			for ( i = q.length - 1; i >= options.minChars; i--) {
				 qs = q.substr(0, i);
				 c = cache.data[qs];
				if (c) {
					 csub = [];
					for ( j = 0; j < c.length; j++) {
						 x = c[j];
						 x0 = x[0];
						if (matchSubset(x0, q)) {
							csub[csub.length] = x;
						}
					}
					return csub;
				}
			}
		}
		return null;
	};

	function matchSubset(s, sub) {
		if (!options.matchCase) s = s.toLowerCase();
		 i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};

	this.flushCache = function() {
		flushCache();
	};

	this.setExtraParams = function(p) {
		options.extraParams = p;
	};

	this.findValue = function(){
		 q = $input.val();

		if (!options.matchCase) q = q.toLowerCase();
		 data = options.cacheLength ? loadFromCache(q) : null;
		if (data) {
			findValueCallback(q, data);
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			$.get(makeUrl(q), function(data) {
				data = parseData(data)
				addToCache(q, data);
				findValueCallback(q, data);
			});
		} else {
			findValueCallback(q, null);
		}
	}

	function findValueCallback(q, data){
		if (data) $input.removeClass(options.loadingClass);

		 num = (data) ? data.length : 0;
		 li = null;

		for ( i=0; i < num; i++) {
			 row = data[i];

			if( row[0].toLowerCase() == q.toLowerCase() ){
				li = document.createElement("li");
				if (options.formatItem) {
					li.innerHTML = options.formatItem(row, i, num);
					li.selectValue = row[0];
				} else {
					li.innerHTML = row[0];
					li.selectValue = row[0];
				}
				 extra = null;
				if( row.length > 1 ){
					extra = [];
					for ( j=1; j < row.length; j++) {
						extra[extra.length] = row[j];
					}
				}
				li.extra = extra;
			}
		}

		if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1);
	}

	function addToCache(q, data) {
		if (!data || !q || !options.cacheLength) return;
		if (!cache.length || cache.length > options.cacheLength) {
			flushCache();
			cache.length++;
		} else if (!cache[q]) {
			cache.length++;
		}
		cache.data[q] = data;
	};

	function findPos(obj) {
		 curleft = obj.offsetLeft || 0;
		 curtop = obj.offsetTop || 0;
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
		return {x:curleft,y:curtop};
	}
}

jQuery.fn.autocomplete = function(url, options, data) {
	options = options || {};
	options.url = url;
	options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null;
	options.inputClass = options.inputClass || "ac_input";
	options.resultsClass = options.resultsClass || "ac_results";
	options.lineSeparator = options.lineSeparator || "\n";
	options.cellSeparator = options.cellSeparator || "|";
	options.minChars = options.minChars || 1;
	options.delay = options.delay || 400;
	options.matchCase = options.matchCase || 0;
	options.matchSubset = options.matchSubset || 1;
	options.matchContains = options.matchContains || 0;
	options.cacheLength = options.cacheLength || 1;
	options.mustMatch = options.mustMatch || 0;
	options.extraParams = options.extraParams || {};
	options.loadingClass = options.loadingClass || "ac_loading";
	options.selectFirst = options.selectFirst || false;
	options.selectOnly = options.selectOnly || false;
	options.maxItemsToShow = options.maxItemsToShow || -1;
	options.autoFill = options.autoFill || false;
	options.width = parseInt(options.width, 10) || 0;

	this.each(function() {
		 input = this;
		new jQuery.autocomplete(input, options);
	});

	return this;
}

jQuery.fn.autocompleteArray = function(data, options) {
	return this.autocomplete(null, options, data);
}

jQuery.fn.indexOf = function(e){
	for(  i=0; i<this.length; i++ ){
		if( this[i] == e ) return i;
	}
	return -1;
};