! function(e) {
    "use strict";
    "function" == typeof define && define.amd ? define(["jquery"], e) : e(jQuery)
}(function(e) {
    "use strict";

    function t(n, s) {
        var i = function() {},
            o = this,
            a = {
                autoSelectFirst: !1,
                appendTo: "body",
                serviceUrl: null,
                lookup: null,
                onSelect: null,
                width: "auto",
                minChars: 1,
                maxHeight: 300,
                deferRequestBy: 0,
                params: {},
                formatResult: t.formatResult,
                delimiter: null,
                zIndex: 9999,
                type: "GET",
                noCache: !1,
                onSearchStart: i,
                onSearchComplete: i,
                containerClass: "autocomplete-suggestions",
                tabDisabled: !1,
                dataType: "text",
                lookupFilter: function(e, t, n) {
                    return -1 !== e.value.toLowerCase().indexOf(n)
                },
                paramName: "query",
                transformResult: function(t, n) {
                    var s = "string" == typeof t ? e.parseJSON(t) : t;
                    return s.query = n, s
                }
            };
        o.element = n, o.el = e(n), o.suggestions = [], o.badQueries = [], o.selectedIndex = -1, o.currentValue = o.element.value, o.intervalId = 0, o.cachedResponse = [], o.onChangeInterval = null, o.onChange = null, o.ignoreValueChange = !1, o.isLocal = !1, o.suggestionsContainer = null, o.options = e.extend({}, a, s), o.classes = {
            selected: "autocomplete-selected",
            suggestion: "autocomplete-suggestion"
        }, o.initialize(), o.setOptions(s)
    }
    var n = function() {
            return {
                extend: function(t, n) {
                    return e.extend(t, n)
                },
                addEvent: function(e, t, n) {
                    if (e.addEventListener) e.addEventListener(t, n, !1);
                    else {
                        if (!e.attachEvent) throw new Error("Browser doesn't support addEventListener or attachEvent");
                        e.attachEvent("on" + t, n)
                    }
                },
                removeEvent: function(e, t, n) {
                    e.removeEventListener ? e.removeEventListener(t, n, !1) : e.detachEvent && e.detachEvent("on" + t, n)
                },
                createNode: function(e) {
                    var t = document.createElement("div");
                    return t.innerHTML = e, t.firstChild
                }
            }
        }(),
        s = {
            ESC: 27,
            TAB: 9,
            RETURN: 13,
            UP: 38,
            DOWN: 40
        };
    t.utils = n, e.Autocomplete = t, t.formatResult = function(e, t) {
        var n = new RegExp("(\\" + ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"].join("|\\") + ")", "g"),
            s = "(" + t.replace(n, "\\$1") + ")";
        return e.value.replace(new RegExp(s, "gi"), "<strong>$1</strong>")
    }, t.prototype = {
        killerFn: null,
        initialize: function() {
            var n, s = this,
                i = "." + s.classes.suggestion,
                o = s.classes.selected,
                a = s.options;
            s.element.setAttribute("autocomplete", "off"), s.killerFn = function(t) {
                0 === e(t.target).closest("." + s.options.containerClass).length && (s.killSuggestions(), s.disableKillerFn())
            }, a.width && "auto" !== a.width || (a.width = s.el.outerWidth()), s.suggestionsContainer = t.utils.createNode('<div class="' + a.containerClass + '" style="position: absolute; display: none;"></div>'), n = e(s.suggestionsContainer), n.appendTo(a.appendTo).width(a.width), n.on("mouseover", i, function() {
                s.activate(e(this).data("index"))
            }), n.on("mouseout", function() {
                s.selectedIndex = -1, n.children("." + o).removeClass(o)
            }), n.on("click", i, function() {
                s.select(e(this).data("index"), !1)
            }), s.fixPosition(), window.opera ? s.el.on("keypress", function(e) {
                s.onKeyPress(e)
            }) : s.el.on("keydown", function(e) {
                s.onKeyPress(e)
            }), s.el.on("keyup", function(e) {
                s.onKeyUp(e)
            }), s.el.on("blur", function() {
                s.onBlur()
            }), s.el.on("focus", function() {
                s.fixPosition()
            })
        },
        onBlur: function() {
            this.enableKillerFn()
        },
        setOptions: function(t) {
            var s = this,
                i = s.options;
            n.extend(i, t), s.isLocal = e.isArray(i.lookup), s.isLocal && (i.lookup = s.verifySuggestionsFormat(i.lookup)), e(s.suggestionsContainer).css({
                "max-height": i.maxHeight + "px",
                width: i.width + "px",
                "z-index": i.zIndex
            })
        },
        clearCache: function() {
            this.cachedResponse = [], this.badQueries = []
        },
        disable: function() {
            this.disabled = !0
        },
        enable: function() {
            this.disabled = !1
        },
        fixPosition: function() {
            var t, n = this;
            "body" === n.options.appendTo && (t = n.el.offset(), e(n.suggestionsContainer).css({
                top: t.top + n.el.outerHeight() + "px",
                left: t.left + "px"
            }))
        },
        enableKillerFn: function() {
            var t = this;
            e(document).on("click", t.killerFn)
        },
        disableKillerFn: function() {
            var t = this;
            e(document).off("click", t.killerFn)
        },
        killSuggestions: function() {
            var e = this;
            e.stopKillSuggestions(), e.intervalId = window.setInterval(function() {
                e.hide(), e.stopKillSuggestions()
            }, 300)
        },
        stopKillSuggestions: function() {
            window.clearInterval(this.intervalId)
        },
        onKeyPress: function(e) {
            var t = this;
            if (!t.disabled && !t.visible && e.keyCode === s.DOWN && t.currentValue) return void t.suggest();
            if (!t.disabled && t.visible) {
                switch (e.keyCode) {
                    case s.ESC:
                        t.el.val(t.currentValue), t.hide();
                        break;
                    case s.TAB:
                    case s.RETURN:
                        if (-1 === t.selectedIndex) return void t.hide();
                        if (t.select(t.selectedIndex, e.keyCode === s.RETURN), e.keyCode === s.TAB && this.options.tabDisabled === !1) return;
                        break;
                    case s.UP:
                        t.moveUp();
                        break;
                    case s.DOWN:
                        t.moveDown();
                        break;
                    default:
                        return
                }
                e.stopImmediatePropagation(), e.preventDefault()
            }
        },
        onKeyUp: function(e) {
            var t = this;
            if (!t.disabled) {
                switch (e.keyCode) {
                    case s.UP:
                    case s.DOWN:
                        return
                }
                clearInterval(t.onChangeInterval), t.currentValue !== t.el.val() && (t.options.deferRequestBy > 0 ? t.onChangeInterval = setInterval(function() {
                    t.onValueChange()
                }, t.options.deferRequestBy) : t.onValueChange())
            }
        },
        onValueChange: function() {
            var e, t = this;
            return clearInterval(t.onChangeInterval), t.currentValue = t.element.value, e = t.getQuery(t.currentValue), t.selectedIndex = -1, t.ignoreValueChange ? void(t.ignoreValueChange = !1) : void(e.length < t.options.minChars ? t.hide() : t.getSuggestions(e))
        },
        getQuery: function(t) {
            var n, s = this.options.delimiter;
            return s ? (n = t.split(s), e.trim(n[n.length - 1])) : e.trim(t)
        },
        getSuggestionsLocal: function(t) {
            var n = this,
                s = t.toLowerCase(),
                i = n.options.lookupFilter;
            return {
                suggestions: e.grep(n.options.lookup, function(e) {
                    return i(e, t, s)
                })
            }
        },
        getSuggestions: function(t) {
            var n, s = this,
                i = s.options;
            n = s.isLocal ? s.getSuggestionsLocal(t) : s.cachedResponse[t], n && e.isArray(n.suggestions) ? (s.suggestions = n.suggestions, s.suggest()) : s.isBadQuery(t) || (i.onSearchStart.call(s.element, i.params), e.ajax({
                url: i.serviceUrl + "/" + t,
                data: i.params,
                type: i.type,
                dataType: i.dataType
            }).done(function(e) {
                s.processResponse(e, t), i.onSearchComplete.call(s.element, t)
            }))
        },
        isBadQuery: function(e) {
            for (var t = this.badQueries, n = t.length; n--;)
                if (0 === e.indexOf(t[n])) return !0;
            return !1
        },
        hide: function() {
            var t = this;
            t.visible = !1, t.selectedIndex = -1, e(t.suggestionsContainer).hide()
        },
        suggest: function() {
            if (0 === this.suggestions.length) return void this.hide();
            var t = this,
                n = t.options.formatResult,
                s = t.getQuery(t.currentValue),
                i = t.classes.suggestion,
                o = t.classes.selected,
                a = e(t.suggestionsContainer),
                l = "";
            e.each(t.suggestions, function(e, t) {
                l += '<div class="' + i + '" data-index="' + e + '">' + n(t, s) + "</div>"
            }), a.html(l).show(), t.visible = !0, t.options.autoSelectFirst && (t.selectedIndex = 0, a.children().first().addClass(o))
        },
        verifySuggestionsFormat: function(t) {
            return t.length && "string" == typeof t[0] ? e.map(t, function(e) {
                return {
                    value: e,
                    data: null
                }
            }) : t
        },
        processResponse: function(e, t) {
            var n = this,
                s = n.options,
                i = n.options.transformResult(e, t);
            i.suggestions = n.verifySuggestionsFormat(i.suggestions), s.noCache || (n.cachedResponse[i[s.paramName]] = i, 0 === i.suggestions.length && n.badQueries.push(i[s.paramName])), i.query === n.getQuery(n.currentValue) && (n.suggestions = i.suggestions, n.suggest())
        },
        activate: function(t) {
            var n, s = this,
                i = s.classes.selected,
                o = e(s.suggestionsContainer),
                a = o.children();
            return o.children("." + i).removeClass(i), s.selectedIndex = t, -1 !== s.selectedIndex && a.length > s.selectedIndex ? (n = a.get(s.selectedIndex), e(n).addClass(i), n) : null
        },
        select: function(e, t) {
            var n = this,
                s = n.suggestions[e];
            s && (n.el.val(s), n.ignoreValueChange = t, n.hide(), n.onSelect(e))
        },
        moveUp: function() {
            var t = this;
            if (-1 !== t.selectedIndex) return 0 === t.selectedIndex ? (e(t.suggestionsContainer).children().first().removeClass(t.classes.selected), t.selectedIndex = -1, void t.el.val(t.currentValue)) : void t.adjustScroll(t.selectedIndex - 1)
        },
        moveDown: function() {
            var e = this;
            e.selectedIndex !== e.suggestions.length - 1 && e.adjustScroll(e.selectedIndex + 1)
        },
        adjustScroll: function(t) {
            var n, s, i, o = this,
                a = o.activate(t),
                l = 25;
            a && (n = a.offsetTop, s = e(o.suggestionsContainer).scrollTop(), i = s + o.options.maxHeight - l, s > n ? e(o.suggestionsContainer).scrollTop(n) : n > i && e(o.suggestionsContainer).scrollTop(n - o.options.maxHeight + l), o.el.val(o.getValue(o.suggestions[t].value)))
        },
        onSelect: function(t) {
            var n = this,
                s = n.options.onSelect,
                i = n.suggestions[t];
            n.el.val(n.getValue(i.value)), e.isFunction(s) && s.call(n.element, i)
        },
        getValue: function(e) {
            var t, n, s = this,
                i = s.options.delimiter;
            return i ? (t = s.currentValue, n = t.split(i), 1 === n.length ? e : t.substr(0, t.length - n[n.length - 1].length) + e) : e
        }
    }, e.fn.autocomplete = function(n, s) {
        return this.each(function() {
            var i, o = "autocomplete",
                a = e(this);
            "string" == typeof n ? (i = a.data(o), "function" == typeof i[n] && i[n](s)) : (i = new t(this, n), a.data(o, i))
        })
    }
});