(function($) {

$.fn.suggestions = function(callbackUrl, settings) {
    function escapeHTML(s) {
        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
    }
    
    var conf = $.extend({
        autoSize: true,
        autoSizeMinWidth: 150,
        fadeSpeed: 0,
        updateInterval: 500,
        selectedItemClass: "selected",
        keywordHighlightClass: "keyword",
        submitOnCommit: false,
        submitOnEnter: false,
        submitButton: "",
        scrollIntoView: false,
        prompt: ""
    }, settings);
    
    $.each(this, function() {
        var root = $(this);
        
        var input;
        if (root.is("input"))
            input = root;
        else
            input = $("input[type=text]", root);
        
        var container, list = $(".Suggestions", root);
        if (list.length == 0) {
            container = list = $("<ul class=\"Suggestions\" style=\"display: none; position: absolute;\"></ul>");
            input.after(list);
        } else {
            container = $(".SuggestionContainer", root);
            if (container.length == 0)
                container = list;
        }
        
        if (conf.autoSize)
            container.width(Math.max(conf.autoSizeMinWidth, input.outerWidth()));
        
        var lastValue = "", selected = null, timer = null, req = null;
        
        function submit() {
            if (conf.submitButton)
                $(conf.submitButton)[0].click();
            else
                input.parent("form").submit();
        }
        
        function commitText(text) {
            input.val(text);
            hideSuggestions();
            
            if (conf.submitOnCommit)
                submit();
        }
        
        function checkControl(e) {
            var key = e ? (e.which || e.keyCode) : null;
            if (key == 38 || key == 40) {
                if (selected) {
                    var n = selected[key == 38 ? "prev" : "next"]();
                    if (n.length) {
                        selected.removeClass("selected");
                        n.addClass("selected");
                        selected = n;
                    }
                } else {
                    selected = $("li:" + (key == 38 ? "last" : "first"), list).addClass("selected");
                    if (selected.length == 0)
                        selected = null;
                }
                
                if (selected && conf.scrollIntoView)
                    container.scrollTo(selected);
                
                e.preventDefault();
            } else if (key == 13) {
                if (selected) {
                    commitText(selected.text());
                
                    e.preventDefault();
                }
                
                if (conf.submitOnEnter) {
                    submit();
                    e.preventDefault();
                }
            }
        }
        
        function itemClick() {
            commitText($(this).text());
        }
        
        function check(e) {
            var key = e ? (e.which || e.keyCode) : null;
            
            if (key == 27) {
                hideSuggestions();
                
                e.preventDefault();
            } else if (key != 13) {
                if (!timer) {
                    if (!req)
                        request();
                    else
                        timer = setTimeout(request, conf.updateInterval);
                }
            }
        }
        
        function requestComplete(data) {
            req = null;
            
            if (!data || data.error)
                return;
            
            list.empty();
            container.fadeIn(conf.fadeSpeed);
            
            $.each(data.suggestions, function() {
                var s = escapeHTML(this).replace(new RegExp("(" + escapeHTML(data.search) + ")", "i"), "<span class=\"" + conf.keywordHighlightClass + "\">$1</span>");
                var item = $("<li>" + s + "</li>");
                list.append(item);
                item.click(itemClick).hover(function() { $(this).addClass("hover"); }, function() { $(this).removeClass("hover"); });
            });
            
            if (selected) {
                selected = list.find("li:first");
                selected.addClass("selected");
            }
            
            list.scrollTop(0);
            check();
        }
        
        function request() {
            if (timer)
                clearTimeout(timer);
            timer = null;
            
            if (input.val() != lastValue) {
                lastValue = input.val();
                
                if (req)
                    req.abort();
                
                if (lastValue == "")
                    hideSuggestions();
                else
                    req = $.get(callbackUrl, {search: input.val()}, requestComplete, "json");
            }
        }
        
        var inhibitBlur = false;

        function blur(e) {
            if (inhibitBlur) {
                inhibitBlur = false;
                e.preventDefault();
                return false;
            }
            
            hideSuggestions();
        }

        function hideSuggestions() {
            if (req)
                req.abort();
            req = null;
            
            if (timer)
                clearTimeout(timer);
            timer = null;
            
            container.fadeOut(conf.fadeSpeed);
            selected = null;
        }

        container.click(function(e) {
            inhibitBlur = true;
            setTimeout(function() { inhibitBlur = false; }, 10)
            e.stopPropagation();
        });
        $(document).click(hideSuggestions);
        input.keypress(checkControl).keyup(check)/*@cc_on.keydown(checkControl)@*/;
        // .blur(hideSuggestions)
        /*function l(e) {
            window.status += e.type.charAt(3);
        }
        input.keypress(l).keyup(l).keydown(l);*/
    });
};

$.fn.prompt = function(prompt, promptClass) {
    $.each(this, function() {
        var input = $(this);
        var pos = input.position();
        var h = input.outerHeight() + "px";
        
        var el = $("<div></div>");
        el.addClass(promptClass || "prompt");
        el.css({position: "absolute", overflow: "hidden", zIndex: 10, left: pos.left + 1 + "px",
            top: pos.top + ($.browser.msie ? -3 : 0) + "px",
            width: input.outerWidth() + "px", height: h, lineHeight: h, cursor: "text", display: "none",
            paddingLeft: input.css("paddingLeft")});
        el.text(prompt);
        
        function focus() {
            el.hide();
        }
        
        function blur() {
            if (!input.val())
                el.show();
        }
        
        function click() {
            el.hide();
            input.focus();
        }
        
        el.click(click);
        input.focus(focus).blur(blur).after(el);
        
        if (!input.val())
            el.show();
    });
};

$.fn.scrollTo = function(selector) {
    $.each(this, function() {
        var container = $(this);
        
        var elem = $(selector, container);
        
        if (elem.length == 0)
            return;

        function clamp(pos, elemSize, containerSize, scroll) {
            //var s = "";
            //for (var i = 0; i < arguments.length; i++)
            //    s += arguments[i] + " ";
            //console.log(s);
            //window.status = s;
            pos += scroll;
            
            if (pos < scroll) {
                return pos;
            } else if (pos > scroll + containerSize - elemSize) {
                return pos - containerSize + elemSize;
            } else {
                return scroll;
            }
        }
        
        var elemPos = elem.position();
        container.scrollLeft(clamp(elemPos.left, elem.outerWidth(), container.innerWidth(), container.scrollLeft()));
        container.scrollTop(clamp(elemPos.top, elem.outerHeight(), container.innerHeight(), container.scrollTop()));
    });
};

})(jQuery);