﻿(function($) {

    var Keys = {
        LeftArrow: 37,
        UpArrow: 38,
        RightArrow: 39,
        DownArrow: 40,
        Return: 13,
        Escape: 27,
        Tab: 9,
        BackSpace: 8
    };
    var SpecialKeys = [Keys.LeftArrow, Keys.UpArrow, Keys.RightArrow, Keys.DownArrow, Keys.Return, Keys.Escape, Keys.Tab, Keys.BackSpace];

    var optionsId = function(node) {
        return $(node).attr('id') + '___options';
    };

    var regexEscape = function(str) {
        return str.replace(/^([\*\^\$\-\.\?\{\}\(\)\[\]])$/g, "\\$1");
    };

    var startsWith = function(str, searchStr) {
        return new RegExp("^" + regexEscape(searchStr), "i").test(str);
    };

    var contains = function(str, searchStr) {
        return new RegExp(regexEscape(searchStr), "i").test(str);
    };

    var search = function(data, searchStr, options) {
        var i, len = data.length, matches = [];
        var searchMethod = options.searchMethod === 'contains' ? contains : startsWith;
        if (typeof options.getValueOf !== 'function') {
            return;
        }
        for (i = 0; i < len; i += 1) {
            if (searchStr === "*" || searchMethod(options.getValueOf(data[i]), searchStr)) {
                matches.push(data[i]);
            }
        }
        return matches;
    };

    var dataBoundItem = function(node) {
        if (arguments.length === 2) {
            node.data("dataBoundItem", arguments[1]);
        } else {
            return node.data("dataBoundItem");
        }
    };

    var clearData = function(node) {
        $("#" + optionsId(node) + " ul li").removeData("dataBoundItem");
    };

    var setSelected = function(node, options) {
        var opts = $.extend({ selected: $("#" + optionsId(node) + " ul li.bricks-selected") }, options);
        var data = null;
        if (typeof opts.getValueOf !== "function") return;
        if (opts.selected.size() === 1) {
            data = dataBoundItem(opts.selected);
            if (data) {
                opts.onSelect(data);
                node.val(opts.getValueOf(dataBoundItem(opts.selected)));
            }
        }
    };

    var hideOptions = function(node) {
        var list = $("#" + optionsId(node));
        list.find("ul li.bricks-selected").removeClass("bricks-selected");
        clearData(node);
        list.find("ul").empty();
        list.hide();
    };

    var showOptions = function(node, matches, options) {
        var i, len = matches.length, list = $("#" + optionsId(node));
        if (len !== 0) {
            if (typeof options.formatter !== "function") return;
            list.show();
            list.css("position", "absolute");
            list.css("top", $(node).offset().top + $(node).outerHeight());
            list.css("left", $(node).offset().left);
            if (len > 15) {
                list.css("height", "200px").css("overflow-y", "scroll");
                list.css("min-width", $(node).width() + 16 + 4);
            } else {
                list.css("height", "auto").css("overflow-y", "auto");
                list.css("min-width", $(node).width() + 4);
            }
            clearData(node);
            list.find("ul").empty();
            for (i = 0; i < len; i += 1) {
                list.find("ul").append("<li>" + options.formatter(matches[i]) + "</li>");
                dataBoundItem(list.find("ul li:last"), matches[i]);
            }
            list.find("ul li").click(function(e) {
                setSelected(node, { selected: $(this) });
                hideOptions(node);
            });
            list.find("ul li").mouseover(function(e) {
                $(this).parent().find("li.bricks-selected").removeClass("bricks-selected");
                $(this).addClass("bricks-selected");
            });
        } else {
            if (options.noResultsText) {
                list.find("ul").empty();
                list.find("ul").append("<li>" + options.noResultsText + "</li>");
            } else {
                hideOptions(node);
            }
        }
    };

    var elementHeight = function(node) {
        if (node.size() == 1) {
            return pixelToNumber(node.css("padding-top")) +
                   pixelToNumber(node.css("padding-bottom")) +
                   pixelToNumber(node.css("border-top-width")) +
                   pixelToNumber(node.css("border-bottom-width")) +
                   pixelToNumber(node.css("margin-top")) +
                   pixelToNumber(node.css("margin-bottom")) +
                   node.height();
        }
        return 0;
    };

    var pixelToNumber = function(str) {
        if (/^\d+px$/.test(str)) {
            return parseInt(str.replace(/px$/, ""));
        }
        return 0;
    };

    $.fn.autoCompleteWith = function(data, options) {
        var opts = {};
        $.extend(opts, $.fn.autoCompleteWith.defaults);
        $.extend(opts, options);
        return this.each(function() {
            var query;
            var allowQuery = true;
            var $this = $(this);
            $("body").append('<div class="bricks-autocomplete-options" id="' + optionsId($this) + '" style="display: none;"><ul></ul></div>');
            $this.keyup(function(event) {
                var query;
                var option = $("#" + optionsId($this) + " ul li.bricks-selected");
                var allowQuery = true;
                if (event.which === Keys.Tab) {
                    hideOptions($this);
                } else if (event.which === Keys.BackSpace) {
                    query = $.trim($this.val());
                    if (options.ignorePattern) {
                        allowQuery = !options.ignorePattern.test(query);
                    } else {
                        allowQuery = true;
                    }
                    if (query.length !== 0 && allowQuery) {
                        showOptions($this, search(data, query, opts), opts);
                    } else {
                        hideOptions($this);
                    }
                } else if ($.inArray(event.which, SpecialKeys) === -1) {
                    query = $.trim($this.val());
                    if (options.ignorePattern) {
                        allowQuery = !options.ignorePattern.test(query);
                    } else {
                        allowQuery = true;
                    }
                    if (query.length !== 0 && allowQuery) {
                        showOptions($this, search(data, query, opts), opts);
                    } else {
                        hideOptions($this);
                    }
                }
            });
            $this.keydown(function(event) {
                var option = $("#" + optionsId($this) + " ul li.bricks-selected");
                var list = $("#" + optionsId($this));
                var optionHeight = elementHeight(list.find("ul li.bricks-selected"));
                if (event.which === Keys.DownArrow) {
                    if (option.size() !== 0) {
                        if (option.next("li").size() !== 0) {
                            option.removeClass("bricks-selected");
                            option.next("li").addClass("bricks-selected");
                            if (list.find("ul li.bricks-selected").position().top >= (10 * optionHeight)) {
                                list.scrollTop(list.scrollTop() + optionHeight);
                            }
                        }
                    } else {
                        list.find("ul li:first").addClass("bricks-selected");
                    }
                } else if (event.which === Keys.UpArrow) {
                    if (option.size() !== 0) {
                        if (option.prev("li").size() !== 0) {
                            option.removeClass("bricks-selected");
                            option.prev("li").addClass("bricks-selected");
                            if (list.find("ul li.bricks-selected").position().top <= optionHeight) {
                                list.scrollTop(Math.max(0, list.scrollTop() - optionHeight));
                            }
                        }
                    }
                } else if (event.which == Keys.Escape) {
                    if (list.is(":visible")) {
                        event.stopPropagation();
                        hideOptions($this);
                    }
                } else if (event.which == Keys.Return) {
                    if (option.size() !== 0) {
                        setSelected($this, opts);
                        hideOptions($this);
                        event.stopPropagation();
                        return event.preventDefault();
                    }
                }
            });
            $this.blur(function(event) {
                if ($("#" + optionsId($this)).is(":visible")) {
                    setSelected($this, opts);
                }
                hideOptions($this);
            });
            query = $.trim($this.val());
            if (options.ignorePattern) {
                allowQuery = !options.ignorePattern.test(query);
            } else {
                allowQuery = true;
            }
            if (query.length !== 0 && allowQuery) {
                showOptions($this, search(data, query, opts), opts);
            } else {
                hideOptions($this);
            }
        });
    };

    $.fn.autoCompleteWith.defaults = {
        onSelect: function(item) { },
        getValueOf: function(item) { return item.toString(); },
        formatter: function(item) { return item.toString(); },
        searchMethod: 'startsWith',
        noResultsText: '',
        ignorePattern: null // Set to a regex
    };
})(jQuery);
