﻿(function () {
    $(document).ready(function () {

        $.ui.autocomplete.prototype._create = function () {
            var self = this,
			doc = this.element[0].ownerDocument;
            this.element
			.addClass("ui-autocomplete-input")
			.attr("autocomplete", "off")
            // TODO verify these actually work as intended
			.attr({
			    role: "textbox",
			    "aria-autocomplete": "list",
			    "aria-haspopup": "true"
			})
			.bind("keydown.autocomplete", function (event) {
			    if (self.options.disabled) {
			        return;
			    }

			    var keyCode = $.ui.keyCode;
			    switch (event.keyCode) {
			        case keyCode.PAGE_UP:
			            self._move("previousPage", event);
			            break;
			        case keyCode.PAGE_DOWN:
			            self._move("nextPage", event);
			            break;
			        case keyCode.UP:
			            self._move("previous", event);
			            // prevent moving cursor to beginning of text field in some browsers
			            event.preventDefault();
			            break;
			        case keyCode.DOWN:
			            self._move("next", event);
			            // prevent moving cursor to end of text field in some browsers
			            event.preventDefault();
			            break;
			        case keyCode.ENTER:
			        case keyCode.NUMPAD_ENTER:
			            // when menu is open or has focus
			            if (self.menu.element.is(":visible")) {
			                event.preventDefault();
			            }
			            //passthrough - ENTER and TAB both select the current element
			        case keyCode.TAB:
			            if (!self.menu.active) {
			                return;
			            }
			            self.menu.select(event);
			            break;
			        case keyCode.ESCAPE:
			            self.element.val(self.term);
			            self.close(event);
			            break;
			        default:
			            // keypress is triggered before the input value is changed
			            clearTimeout(self.searching);
			            self.searching = setTimeout(function () {
			                // only search if the value has changed
			                if (self.term != self.element.val()) {
			                    self.selectedItem = null;
			                    self.search(null, event);
			                }
			            }, self.options.delay);
			            break;
			    }
			})
			.bind("focus.autocomplete", function () {
			    if (self.options.disabled) {
			        return;
			    }

			    self.selectedItem = null;
			    self.previous = self.element.val();
			})
			.bind("blur.autocomplete", function (event) {
			    if (self.options.disabled) {
			        return;
			    }

			    clearTimeout(self.searching);
			    // clicks on the menu (or a button to trigger a search) will cause a blur event
			    self.closing = setTimeout(function () {
			        self.close(event);
			        self._change(event);
			    }, 150);
			});
            this._initSource();
            this.response = function () {
                return self._response.apply(self, arguments);
            };
            this.menu = $("<ul></ul>")
			.addClass("ui-autocomplete")
			.appendTo($(this.options.appendTo || "body", doc)[0])
            // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
			.mousedown(function (event) {
			    // clicking on the scrollbar causes focus to shift to the body
			    // but we can't detect a mouseup or a click immediately afterward
			    // so we have to track the next mousedown and close the menu if
			    // the user clicks somewhere outside of the autocomplete
			    var menuElement = self.menu.element[0];
			    if (event.target === menuElement) {
			        setTimeout(function () {
			            $(document).one('mousedown', function (event) {
			                if (event.target !== self.element[0] &&
								event.target !== menuElement &&
								!$.ui.contains(menuElement, event.target)) {
			                    self.close();
			                }
			            });
			        }, 1);
			    }

			    // use another timeout to make sure the blur-event-handler on the input was already triggered
			    setTimeout(function () {
			        clearTimeout(self.closing);
			    }, 13);
			})
			.menu({
			    focus: function (event, ui) {
			        var item = ui.item.data("item.autocomplete");
			        if (false !== self._trigger("focus", null, { item: item })) {
			            // use value to match what will end up in the input, if it was a key event
			            if (/^key/.test(event.originalEvent.type)) {
			                self.element.val(item.value);
			            }
			        }
			    },
			    selected: function (event, ui) {
			        var item = ui.item.data("item.autocomplete"),
						previous = self.previous;

			        // only trigger when focus was lost (click on menu)
			        if (self.element[0] !== doc.activeElement) {
			            self.element.focus();
			            self.previous = previous;
			        }

			        if (false !== self._trigger("select", event, { item: item })) {
			            self.term = item.value;
			            self.element.val(item.value);
			        }

			        self.close(event);
			        self.selectedItem = item;
			    },
			    blur: function (event, ui) {
			        // don't set the value of the text field if it's already correct
			        // this prevents moving the cursor unnecessarily
			        if (self.menu.element.is(":visible") &&
						(self.element.val() !== self.term)) {
			            // self.element.val(self.term);
			        }
			    }
			})
			.zIndex(this.element.zIndex() + 1)
            // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
			.css({ top: 0, left: 0 })
			.hide()
			.data("menu");
            if ($.fn.bgiframe) {
                this.menu.element.bgiframe();
            }
        };

        $.ui.menu.prototype.refresh = function () {
            var self = this;
            // don't refresh list items that are already adapted
            // var items = this.element.children("li:not(.ui-menu-item):has(a)")
            var items = this.element.find("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role", "menuitem");
            this.element.find("li:not(.ui-menu-item)").addClass("ui-menu-title");

            items.children("a").addClass("ui-corner-all").attr("tabindex", -1)
            // mouseenter doesn't work with event delegation
        .mouseenter(function (event) {
            self.activate(event, $(this).parent());
        }).mouseleave(function () {
            self.deactivate();
        });
        };

        $.ui.menu.prototype.first = function () {
            // return this.active && !this.active.prevAll(".ui-menu-item").length;
            if (!this.active) return;

            return this.element.find(".ui-menu-item").index(this.active) == 0;
        };

        $.ui.menu.prototype.last = function () {
            // return this.active && !this.active.nextAll(".ui-menu-item").length;
            if (!this.active) return;

            var elms = this.element.find(".ui-menu-item");
            return elms.index(this.active) == elms.length - 1;
        };

        $.ui.menu.prototype.move = function (direction, edge, event) {

            if (!this.active) {
                this.activate(event, this.element.find(edge));
                return;
            }

            // var next = this.active[direction + "All"](".ui-menu-item").eq(0);
            var elms = this.element.find(".ui-menu-item");
            var next = $(elms.get(elms.index(this.active) + ((direction == "next") ? 1 : -1)));
            if (next.length) {
                this.activate(event, next);
            } else {
                this.activate(event, this.element.find(edge));
            }
        };

        // TODO merge with previousPage
        $.ui.menu.prototype.nextPage = function (event) {
            if (this.hasScroll()) {
                // TODO merge with no-scroll-else
                if (!this.active || this.last()) {
                    this.activate(event, this.element.children(":first"));
                    return;
                }
                var base = this.active.offset().top,
                height = this.element.height(),
                // result = this.element.children("li").filter(function () {
                result = this.element.find("li").filter(function () {
                    var close = $(this).offset().top - base - height + $(this).height();
                    // TODO improve approximation
                    return close < 10 && close > -10;
                });

                // TODO try to catch this earlier when scrollTop indicates the last page anyway
                if (!result.length) {
                    result = this.element.children(":last");
                }
                this.activate(event, result);
            } else {
                this.activate(event, this.element.children(!this.active || this.last() ? ":first" : ":last"));
            }
        };

        // TODO merge with nextPage
        $.ui.menu.prototype.previousPage = function (event) {
            if (this.hasScroll()) {
                // TODO merge with no-scroll-else
                if (!this.active || this.first()) {
                    this.activate(event, this.element.children(":last"));
                    return;
                }

                var base = this.active.offset().top,
                height = this.element.height();
                // result = this.element.children("li").filter(function () {
                result = this.element.find("li").filter(function () {
                    var close = $(this).offset().top - base + height - $(this).height();
                    // TODO improve approximation
                    return close < 10 && close > -10;
                });

                // TODO try to catch this earlier when scrollTop indicates the last page anyway
                if (!result.length) {
                    result = this.element.children(":first");
                }
                this.activate(event, result);
            } else {
                this.activate(event, this.element.children(!this.active || this.first() ? ":last" : ":first"));
            }
        };
    })
})();
