(function($) {
	
	var defaults = {
		lastEvent: 0,
		delay: 400,
		callbackError: function(msg) {alert(msg)},
		validChars: "abcdefghijklmnñopqrstuvwxyzáéíóú' ,",
		fontSize: "14px",
		fontFamily: "Arial, Helvetica, sans-serif",
		background: "#FFFFFF",
		selectedBackground: "#8CC43D",
		selectedForeground: "#FFFFFF",
		borderColor: "#5AA01B",
		padding: "3px",
		imageLoading: "img/loading.gif"
	}

	$.fn.autocomplete = function(options) {

		/**
		 * set options function
		 */
    function _setOptions(element) {
      options = $.extend({}, defaults, element.data("autocomplete:options"), options);
      element.data("autocomplete:options", options);
    };
    
    function _createSuggestion(element) {
    	var img = new Image(15,15); 
    	img.src = options.imageLoading; 
    	var width = element.width();
    	if (options.width) {
    		width = options.width;
    	}
    	var div = $("<div>").width(width);
    	div.css({"position": "absolute",
    		"background": options.background,
    		"font-size": options.fontSize,
    		"font-family": options.fontFamily,
    		"border": "1px solid "+ options.borderColor,
    		"overflow": "visible",
    		"cursor": "pointer"
    	});
    	_locateSuggestion(element, div);
    	$("body").append(div.hide());
    	$(window).resize(function() {
    		_locateSuggestion(element, div);
    	});
    	options.suggestion = div;
    	element.attr("autocomplete", "off");
    	//options.debug = $("<div>").css("position", "absolute").css("top", "0px").css("left", "0px")
  		//.css("background", "#FFFFFF").width(200).height(200).attr("id","autocomplete-debug").appendTo($("body"));
    }
    
    function _locateSuggestion(element, div) {
    	var offset = element.offset();
    	var height = element.height();
    	div.css("left", offset.left);
    	div.css("top", offset.top + height + 10);
    }

    function _log(msg) {
    	//options.debug.append($("<div>").html(msg));
    }
    
    function _registerEvents(element) {
    	element.keydown(function(e) {
    		return _processInput(e, element);
    	})
    	.blur(function() {
    		setTimeout(_hideSuggestions, 300);
    	});
    }
    
    function _processInput(e, element) {
    	var code = e.which;
    	if (code == 0) {
    		code = e.keyCode;
    	}
    	//_log(code);
    	switch(code) {
    	case 13:
    	case 9: // tab
    		return _performSelection(element, e);
    	case 27: // escape
    		_hideSuggestions();
    		return true;
    	case 38: // up
    		_selectUp(element);
    		return true;
    	case 40: // down
    		_selectDown(element);
    		return true;
    	case 0:
    	case 27:
    	case 35:
    	case 36:
    	case 37:
    	case 39:
    		return true;
	    }
    	
    	var input = String.fromCharCode(code).toLowerCase();
    	if (options.validChars.indexOf(input) < 0 && code != 8) return false;
			if (options.timeout) {
				clearTimeout(options.timeout);
			}
			options.timeout =	setTimeout(function() {
    		_sendRequest(element);
    	}, options.delay);
			return true;
    }
    
    function _sendRequest(element) {
    	if (element.val() == "") {
    		_hideSuggestions();
    		return;
    	}
    	element.css("background", "url("+options.imageLoading+") right center no-repeat");
    	
    	$.request({
    		url: options.url,
    		callback: function(data) {_sendRequestCallback(data, element)},
    		callbackError: options.callbackError,
    		data: {
    			q: element.val()
    		}
    	});
    }
    
    function _sendRequestCallback(data, element) {
    	var list = data.getDataList();
    	if (list.size() > 0) {
    		_showSuggestions(list, element);
    	} else {
    		_hideSuggestions();
    	}
    	element.css("background", "");
    }
    
    function _showSuggestions(list, element) {
    	options.suggestion.html("");
    	var suggestion = options.suggestion;
    	var index = 1;
    	$.each(list.values, function() {
    		var div = $("<div>").html(this+"").css("padding", options.padding).attr("index", index);
    		div.mouseover(function() {
    			_selectSuggestion(div);
    		});
    		div.mouseout(function() {
    			_unselectSuggestion(div);
    		});
    		div.click(function() {
    			_performClickSelection(element);
    		});
    		suggestion.append(div);
    		index++;
    	});
    	if (options.showListener) {
    		options.showListener();
    	}

    	options.suggestion.show();
    	options.size = list.size();
    }
    
    function _hideSuggestions() {
    	options.suggestion.hide();
    	options.suggestion.html("");
    	options.selected = null;
    	if (options.hideListener) {
    		options.hideListener();
    	}
    }
    
    function _selectUp() {
    	if (!options.selected) return;
    	var index = parseInt(options.selected.attr("index"));
    	_unselectSuggestion();
    	if (index == 1) {
    		options.selected = null;
    	} else {
    		index--;
    		_selectSuggestion($("div[index='"+index+"']", options.suggestion));
    	}
    }
    
    function _selectDown() {
    	var selected;
    	if (!options.selected) {
    		if (options.suggestion.html() == "") return;
    		selected = $(":first", options.suggestion);
    	} else {
    		var index = parseInt(options.selected.attr("index"));
    		if (index == options.size) return;
    		index++;
    		_unselectSuggestion();
    		selected = $("div[index='"+index+"']", options.suggestion);
    	}
    	_selectSuggestion(selected);
    }
    
    function _selectSuggestion(selected) {
    	if (options.selected) {
    		_unselectSuggestion();
    	}
    	options.selected = selected;
    	selected.css("background", options.selectedBackground);
    	selected.css("color", options.selectedForeground);
    }
    
    function _unselectSuggestion() {
    	options.selected.css("background", options.background);
    	options.selected.css("color", "");
    	options.selected = null;
    }
    
    function _performSelection(element, e) {
    	var returnValue = true;
    	if (options.selected) {
    		element.val(options.selected.html());
    		if (e) {
    			if ($.isIE()) {
    				window.event.cancelBubble = true;
    				returnValue = false;
    				_log("cancelled!!!");
    			}    				
    			e.preventDefault();
    			e.stopPropagation();
    		}
    	}
    	element.focus();
    	_hideSuggestions();
    	return returnValue;
    }
    
    function _performClickSelection(element) {
    	_log("here");
    	element.val(options.selected.html());
    	element.focus();
    	_hideSuggestions();
    } 

		element = $(this);
		_setOptions(element);

		_createSuggestion(element);
		_registerEvents(element);
		
	};
})(jQuery);