jQuery.autocomplete = function( input, options ) {
	
	var me = this;
	var options = options;
	
	var CLASSES = {
		OVER: 'ac_over',
		RESULTS: 'ac_results',
		INPUT: 'ac_input',
		LOADING: 'ac_loading'			
	};
	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create jQuery object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(CLASSES.INPUT);
	var inputWidth = $input.width() || 100; 
	inputWidth += parseInt( $input.css("padding-left"),10) + parseInt( $input.css("padding-right"),10);
	
	input.autocompleter = me;
	
	var timeout = null;
	var ex_timeout = null;
	var prev = "";
	var active = -1;
	var keyb = false;
	var hasFocus = false;
	var lastKeyPressCode = null;
	var $results = null;
		
	
	$input.bind(($.browser.opera ? "keypress" : "keydown"),function(e) {
		// track last key pressed
		lastKeyPressCode = e.keyCode;
		switch(e.keyCode) {
			case KEY.UP: // up
				e.preventDefault();
				moveSelect(-1);
				break;
			case KEY.DOWN: // down
				e.preventDefault();
				moveSelect(1);
				break;
			case KEY.TAB:  // tab
			case KEY.RETURN: // return
				if( selectCurrent() ){
					// make sure to blur off the current field
					$input.get(0).blur();
					e.preventDefault();
				}
				break;
			case KEY.ESC:
				hideResultsNow();
				break;
			default:
				active = -1;
				if (timeout) clearTimeout(timeout);
				timeout = setTimeout( function() { onChange();}, options.delay);
				break;
		}
	})
	.focus(function(){
		// track whether the field has focus, we shouldn't process any results if the field no longer has focus
		hasFocus = true;
	})
	.blur(function() {
		// track whether the field has focus
		var timeout_blur = null;
		setTimeout( function() { 
			hideResultsNow();
		}, 100);
		hasFocus = false;		
	});
	
	/**
	 * hides the shown results with some delay
	 */
	function hideResults() {
		if (timeout) clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, options.delay);
		
	};
	
	/**
 	 * hides the shown results immediatly
	 */
	function hideResultsNow() {		
		if (timeout) clearTimeout(timeout);
		$input.removeClass(CLASSES.LOADING);
		
		$('div.'+CLASSES.RESULTS).hide(200, function(e) {
			$(this).remove();
		});		
	};
	
	/**
	 * is called on text-input into the search-box
	 * calls the request for data
	 */
	function onChange() {
		// ignore if the following keys are pressed: [del] [shift] [capslock]
		if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide();
		var v = $input.val();
		v = trim(v);
		v = str_replace('*','',v);
		// checking if the search-string has changed
		if (v == prev) return;	
		prev = v;
		// cutting symbols
		var chars = new Array('{', '}', '[', ']', '~', ':', '\\');			
		v = cutSymbols( v, chars );
		
		if (v.length >= options.minChars) {			
		
			if( options.excludeStrings ) {
			//	if (ex_timeout) clearTimeout(ex_timeout);
				var isPartExcluded = false;
				var isExcluded = false;
				$.each(	options.excludeStrings , function(intIndex, value) {
					var search = value.indexOf( v );					
					var rsearch = v.indexOf(value);					
					if (rsearch == 0 ) {
						isExcluded = true;						
					}					
					if( search == 0) {
						isPartExcluded = true;
					}
				});			
				if( isExcluded ) {
					hideResultsNow();
					return;
				}
				if( isPartExcluded ) {
					setTimeout(function() {	getResults(v); }, options.excludeDelay);
					return;
				} else {
					getResults( v );	
					return;				
				}
			}
			getResults( v );		
			return;
		} else {
			$input.removeClass(CLASSES.LOADING);
			hideResultsNow();
		}
	};
	
	function getResults( v ) {
		$input.addClass(CLASSES.LOADING);
		v = URLEncode(v);			
		sajax_do_call(options.BE_func, [v, options.resultNum], receiveData);
	}
		
 	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(CLASSES.OVER);		
		
		$li_active = $(lis[active]); 
		$li_active.addClass(CLASSES.OVER);
		
		// Weird behaviour in IE
		 if (lis[active] && lis[active].scrollIntoView) {
		 	lis[active].scrollIntoView(false);
		 }

	};

	function selectCurrent() {	
			
		if( options.addResultForm) {
			//the object of the html-object which the newResultForm contains
			var form = options.addResultForm;
			var id = $("li."+CLASSES.OVER+" input.result_id", results).val();			
			var uri = $("li."+CLASSES.OVER+" input.result_uri", results).val();
			var title = $("li."+CLASSES.OVER+" input.result_title", results).val();
			
			$('textarea.description' , form ).attr('disabled' , 'disabled').val('');
			$('input.title', form).attr('disabled' , 'disabled').val( title );
			$('input.result-id', form).val( id );
			
			$changeUrl = $('input.change_url', form);			
			$changeUrl.width(0);			
			
			
			$input.attr('disabled' , 'disabled').val(uri).animate({
				width: '-=30',
			}, 500, function() {
				$changeUrl.animate({
					width: '25px'
				}, 500);
			});			
			hideResultsNow();
			
			
			// options.addResultForm.hide();
			return true;			
		} else {
			var link = $("li."+CLASSES.OVER+" a", results);
			var query = link.attr('href');			
			if( typeof(query) == 'undefined') return;
			// cutFromQuery at normal Autocomplete = 7 , because of cutting "Woogle:"
			query = query.substring(7);		
			if ( query ) {
				// query = URLEncode(query);
				$input.val( URLDecode(query) );
				sajax_do_call("woogleAjaxAutocompleteRedirect", [query], function( response){
					window.location.replace(response.responseText);
				});				
				return true;
			} else {
				return false;
			}
		}
	};

	/**
	 * receive data from server and put it into the result-box
	 * @param request 		the result deliviered from the ajax-request
	 * @return
	 */
	function receiveData(request) {
				
		var data = request.responseText;
		hideResultsNow();
		
		if (data) {						
			$input.removeClass(CLASSES.LOADING);
			createResultDiv(data);
			
			if( data.indexOf('li') > -1) {
				var hasResults = true;
			} else {
				var hasResults = false;
			}			
//			// if the field no longer has focus or if there are no matches, do not display the drop down
			if( !hasFocus || !hasResults ) return hideResultsNow();
//
//			if ($.browser.msie) {
//				// we put a styled iframe behind the calendar so HTML SELECT elements don't show through
//				$results.append(document.createElement('iframe'));
//			}
			
			// $results.html(data);
			// either use the specified width, or autocalculate based on form element
			$results.resize(inputWidth, 400);
			
			showResults();				
		} else {			
			hideResultsNow();
		}		
	}

	function createResultDiv(data) {
		
		results = document.createElement("div");						  	
	
		// Create jQuery object for results
		$results = $(results);
		$results.hide()
				.addClass(CLASSES.RESULTS)
				.css("position", "absolute")
				.html(data);

		// Add to body element
		$("body").append(results);
	}
	
	/**
	 *	shows the results of the autocomplete request  
	 */	
	function showResults() {
		// get the position of the input field right now (in case the DOM is shifted)
		var pos = getAbsPosition(input);
		
		// reposition				
		$results
		.css({
			top: (pos.y + input.offsetHeight) + "px",
			left: pos.x + "px"
		})
		.show(500, initMouse() );		
	};
	
	function initMouse() {		
		
		var lis = $results.find('li');	
		
		lis
		.mouseover( function(){
			lis.removeClass(CLASSES.OVER);			
			$(this).addClass(CLASSES.OVER);			
			active = lis.index($(this));
//			$(this).click( function(){
//				alert('Click');
//			});
		})		
		
		.click( function(e){
			e.preventDefault();

			selectCurrent();			
		});		
	}
}

/**
 * inputClass: the class-name added to the input-field
 */
jQuery.fn.autocomplete = function( options ) {
	
	// Make sure options exist
	options = options || {};
	
	options.BE_func = options.BE_func || 'woogleAjaxAutocomplete';

	// minChars to start Search
	options.minChars = options.minChars || 3;
	// number of shown results 
	options.resultNum = options.resultNum || 5;
	// strings, with no autocomplete or longer delay
	options.excludeStrings = options.excludeStrings || false;
	// delay for excludeStrings , false if not set
	options.excludeDelay = options.exlcudeDelay || 1000;
	// timeout delay
	options.delay = options.delay || 300;
	// call other function, if got to handle a new added result
	options.addResultForm = options.addResultForm|| false;

	this.each(function() {
		var input = this;
		new jQuery.autocomplete(input, options);
	});

	// Don't break the chain
	return this;
}
