/*
 * jQuery UI Autocomplete @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Autocomplete
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 */
(function($) {

$.widget("ui.autocomplete", {
	options: {
		minLength: 1,
		delay: 300
	},
	_init: function() {
		var self = this;
		this.element
			.addClass("ui-autocomplete ui-widget ui-widget-content ui-corner-all")
			.attr("autocomplete", "off")
			// TODO verify these actually work as intended
			.attr({
				role: "textbox",
				"aria-autocomplete": "list",
				"aria-haspopup": "true"
			})
			.bind("keydown.autocomplete", function(event) {
				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:
					// when menu is open or has focus
					if (self.menu && self.menu.active) {
						event.preventDefault();
					}
				case keyCode.TAB:
					if (!self.menu || !self.menu.active) {
						return;
					}
					self.menu.select();
					break;
				case keyCode.ESCAPE:
					self.element.val(self.term);
					self.close(event);
					break;
				case 16:
				case 17:
				case 18:
					// ignore metakeys (shift, ctrl, alt)
					break;
				default:
					// keypress is triggered before the input value is changed
					clearTimeout(self.searching);
					self.searching = setTimeout(function() {
						self.search(null, event);
					}, self.options.delay);
					break;
				}
			})
			.bind("focus.autocomplete", function() {
				self.previous = self.element.val();
			})
			.bind("blur.autocomplete", function(event) {
				clearTimeout(self.searching);
				// clicks on the menu (or a button to trigger a search) will cause a blur event
				// TODO try to implement this without a timeout, see clearTimeout in search()
				self.closing = setTimeout(function() {
					self.close(event);
				}, 150);
			});
		this._initSource();
		this.response = function() {
			return self._response.apply(self, arguments);
		};
	},

	destroy: function() {
		this.element
			.removeClass("ui-autocomplete ui-widget ui-widget-content ui-corner-all")
			.removeAttr("autocomplete")
			.removeAttr("role")
			.removeAttr("aria-autocomplete")
			.removeAttr("aria-haspopup");
		if (this.menu) {
			this.menu.element.remove();
		}
		$.Widget.prototype.destroy.call(this);
	},

	_setOption: function(key) {
		$.Widget.prototype._setOption.apply(this, arguments);
		if (key == "source") {
			this._initSource();
		}
	},

	_initSource: function() {
		if ($.isArray(this.options.source)) {
			var array = this.options.source;
			this.source = function(request, response) {
				// escape regex characters
				var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
				response($.grep(array, function(value) {
    				return matcher.test(value.value || value.label || value);
				}));
			};
		} else if (typeof this.options.source == "string") {
			var url = this.options.source;
			this.source = function(request, response) {
				$.getJSON(url, request, response);
			};
		} else {
			this.source = this.options.source;
		}
	},

	search: function(value, event) {
		value = value != null ? value : this.element.val();
		if (value.length < this.options.minLength) {
			return this.close(event);
		}

		clearTimeout(this.closing);
		if (this._trigger("search") === false) {
			return;
		}

		return this._search(value);
	},

	_search: function(value) {
		this.term = this.element
			.addClass("ui-autocomplete-loading")
			// always save the actual value, not the one passed as an argument
			.val();

		this.source({ term: value }, this.response);
	},

	_response: function(content) {
		if (content.length) {
			content = this._normalize(content);
			this._trigger("open");
			this._suggest(content);
		} else {
			this.close();
		}
		this.element.removeClass("ui-autocomplete-loading");
	},

	close: function(event) {
		clearTimeout(this.closing);
		if (this.menu) {
			this._trigger("close", event);
			this.menu.element.remove();
			this.menu = null;
		}
		if (this.previous != this.element.val()) {
			this._trigger("change", event);
		}
	},

	_normalize: function(items) {
		// assume all items have the right format when the first item is complete
		if (items.length && items[0].label && items[0].value) {
			return items;
		}
		return $.map(items, function(item) {
			if (typeof item == "string") {
				return {
					label: item,
					value: item
				};
			}
			return $.extend({
				label: item.label || item.value,
				value: item.value || item.label
			}, item);
		});
	},

	_suggest: function(items) {
		(this.menu && this.menu.element.remove());
		var self = this,
			ul = $("<ul></ul>"),
			parent = this.element.parent();

		$.each(items, function(index, item) {
			$("<li></li>")
				.data("item.autocomplete", item)
				.append("<a>" + item.label + "</a>")
				.appendTo(ul);
		});
		this.menu = ul
			.addClass("ui-autocomplete-menu")
			.appendTo(parent)
			.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
						self.element.val(item.value);
					}
				},
				selected: function(event, ui) {
					var item = ui.item.data("item.autocomplete");
					if (false !== self._trigger('select', event, { item: item })) {
						self.element.val( item.value );
					}
					self.close(event);
					self.previous = self.element.val();
					// only trigger when focus was lost (click on menu)
					if (self.element[0] != document.activeElement) {
						self.element.focus();
					}
				}
			})
			// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
			.css({ top: 0, left: 0 })
			.position({
				my: "left top",
				at: "left bottom",
				of: this.element
			})
			.data("menu");
		if (ul.width() <= this.element.width()) {
			ul.width(this.element.width());
		}
	},

	_move: function(direction, event) {
		if (!this.menu) {
			this.search(null, event);
			return;
		}
		if (this.menu.first() && /^previous/.test(direction)
				|| this.menu.last() && /^next/.test(direction)) {
			this.element.val(this.term);
			this.menu.deactivate();
			return;
		}
		this.menu[direction]();
	},

	widget: function() {
		// return empty jQuery object when menu isn't initialized yet
		return this.menu && this.menu.element || $([]);
	}
});

$.extend($.ui.autocomplete, {
	escapeRegex: function(value) {
		return value.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1");
	}
});

})(jQuery);

/*
 * jQuery UI Menu (not officially released)
 * 
 * This widget isn't yet finished and the API is subject to change. We plan to finish
 * it for the next release. You're welcome to give it a try anyway and give us feedback,
 * as long as you're okay with migrating your code later on. We can help with that, too.
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Menu
 *
 * Depends:
 *	jquery.ui.core.js
 *  jquery.ui.widget.js
 */
(function($) {

$.widget("ui.menu", {
	_init: function() {
		var self = this;
		this.element
			.addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
			.attr({
				role: "menu",
				"aria-activedescendant": "ui-active-menuitem"
			})
			.click(function(e) {
				// temporary
				e.preventDefault();
				self.select();
			});
		var items = this.element.children("li")
			.addClass("ui-menu-item")
			.attr("role", "menuitem");
		
		items.children("a")
			.addClass("ui-corner-all")
			.attr("tabindex", -1)
			// mouseenter doesn't work with event delegation
			.mouseenter(function() {
				self.activate($(this).parent());
			});
	},

	activate: function(item) {
		this.deactivate();
		this.active = item.eq(0)
			.children("a")
				.addClass("ui-state-hover")
				.attr("id", "ui-active-menuitem")
			.end();
		this._trigger("focus", null, { item: item });
		if (this.hasScroll()) {
			var offset = item.offset().top - this.element.offset().top,
				scroll = this.element.attr("scrollTop"),
				elementHeight = this.element.height();
			if (offset < 0) {
				this.element.attr("scrollTop", scroll + offset);
			} else if (offset > elementHeight) {
				this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
			}
		}
	},

	deactivate: function() {
		if (!this.active) { return; }

		this.active.children("a")
			.removeClass("ui-state-hover")
			.removeAttr("id");
		this.active = null;
	},

	next: function() {
		this.move("next", "li:first");
	},

	previous: function() {
		this.move("prev", "li:last");
	},

	first: function() {
		return this.active && !this.active.prev().length;
	},

	last: function() {
		return this.active && !this.active.next().length;
	},

	move: function(direction, edge) {
		if (!this.active) {
			this.activate(this.element.children(edge));
			return;
		}
		var next = this.active[direction]();
		if (next.length) {
			this.activate(next);
		} else {
			this.activate(this.element.children(edge));
		}
	},

	// TODO merge with previousPage
	nextPage: function() {
		if (this.hasScroll()) {
			// TODO merge with no-scroll-else
			if (!this.active || this.last()) {
				this.activate(this.element.children(":first"));
				return;
			}
			var base = this.active.offset().top,
				height = this.element.height(),
				result = this.element.children("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(result);
		} else {
			this.activate(this.element.children(!this.active || this.last() ? ":first" : ":last"));
		}
	},

	// TODO merge with nextPage
	previousPage: function() {
		if (this.hasScroll()) {
			// TODO merge with no-scroll-else
			if (!this.active || this.first()) {
				this.activate(this.element.children(":last"));
				return;
			}

			var base = this.active.offset().top,
				height = this.element.height();
				result = this.element.children("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(result);
		} else {
			this.activate(this.element.children(!this.active || this.first() ? ":last" : ":first"));
		}
	},

	hasScroll: function() {
		return this.element.height() < this.element.attr("scrollHeight");
	},

	select: function() {
		this._trigger("selected", null, { item: this.active });
	}
});

})(jQuery);
