$.fn.autoComplete = function(url, options, enterCallback) {
    var currentJsonRequest = null;
    var seqId = 0;
    var selectedIndex = -1;
    var clearTimer = null;
    var cancelClear = false;
    var previousSearch = "";
    var forceUpdate = false;
    var ignoreUpdate = false;

    return this.each(function() {
        var obj = $(this);

        obj.keyup(process);
        obj.focus(process);

        obj.keydown(keyboard);
        obj.blur(startClearTimer);

        resultsContainer();

        function startClearTimer(event) {
            if (clearTimer != null) {
                clearTimeout(clearTimer);
                clearTimer = null;
            }
            clearTimer = setTimeout(function() {
                if (!cancelClear)
                    empty();
                else
                    cancelClear = false;
            }, 200);
        }

        function keyboard(event) {
            switch (event.keyCode) {
                case 40:
                    if (selectedIndex + 1 >= count()) break;
                    var results = resultsContainer().children();
                    results.removeClass("selected");
                    results.eq(++selectedIndex).addClass("selected");
                    break;
                case 38:
                    if (selectedIndex - 1 < 0) break;
                    var results = resultsContainer().children();
                    results.removeClass("selected");
                    results.eq(--selectedIndex).addClass("selected");
                    break;
                case 13: // enter
                    if (selectedIndex > -1 && selectedIndex < count()) {
                        selectResult(resultsContainer().children().eq(selectedIndex));
                        ignoreUpdate = true;
                        empty();
                        if ($.isFunction(enterCallback)) {
                            enterCallback(simplifySearchTerm(obj.val()));
                        }
                    } else if (simplifySearchTerm(obj.val()).length < 3) {
                        // Force an update
                        //forceUpdate = true;
                        //process(event);
                    } else {
                        if ($.isFunction(enterCallback)) {
                            enterCallback(simplifySearchTerm(obj.val()));
                        }
                        return false;
                    }
                    return false;
                    break;
            }
        }

        function process(event) {
            // Up or down arrow ignore
            if (event.keyCode == 40 || event.keyCode == 38)
                return;

            if (ignoreUpdate) {
                ignoreUpdate = false;
                return;
            }
            var keywords = simplifySearchTerm(obj.val());

            if (!forceUpdate && (keywords.length < 3 || keywords == previousSearch))
                return;
            else
                previousSearch = keywords;

            // Reset variable that forces an update
            forceUpdate = false;


            options.s = keywords;
            options.seqId = seqId++;

            if (currentJsonRequest != null) {
                currentJsonRequest.abort();
                currentJsonRequest = null;
            }

            currentJsonRequest = $.getJSON(url, options, function(response) {
                var resultsContainer = empty();

                if (response.seqId < seqId)
                    return;

                $.each(response.results, function(index, item) {
                    addResult(item.label);

                });
                $("div:first-child", resultsContainer).addClass("first");
                currentJsonRequest = null;
            });
        }

        function resultsContainer() {
            if (!obj.next().hasClass("results")) {
                var css = { position: 'absolute', top: obj.offset().top + obj.outerHeight() + 3, left: obj.offset().left };
                $("<div>").addClass("results").css(css).insertAfter(obj);
            }
            return obj.next();
        }

        function addResult(label) {
            var row = $("<div>").addClass("result").html(highlightText(label, simplifySearchTerm(obj.val())));
            resultsContainer().append(row);
            row.click(function() {
                selectResult($(this));
                if ($.isFunction(enterCallback)) {
                    enterCallback(simplifySearchTerm(obj.val()));
                }
            });
        }

        function selectResult(resultEl) {
            obj.val(resultEl.text());
        }

        function highlightText(haystack, needle) {
            if (needle.length < 3) return haystack;
            return haystack.replace(RegExp(needle, "gi"), "<strong>" + needle + "</strong>");
        }

        function simplifySearchTerm(term) {
            return term.replace(".", "");
        }

        function empty(event) {
            selectedIndex = -1;
            //				resultsContainer().fadeOut(200);
            return resultsContainer().empty();
        }

        function count() {
            return resultsContainer().children().length;
        }
    });
}	