﻿(function ($) {
	$.widget("ui.navlist", {
		options: {
			items: ">li",
			search_text: "Search",
			selected: "ui-navlist-selected",
			search_parent: false,
			class_filter: false
		},

		toggle: null,
		search_container: null,

		_create: function () {
			var self = this;
			var $ul = this.element;
			var $search_container = $("<div class='ui-navlist-search-container'><div class='ui-navlist-search-input'><input type='text' /><div class='ui-navlist-search-status empty'><a style='display:none' title='remove search filter' href='javascript:;'>x</a></div></div></div>");
			var $find = $search_container.find("input");
			var $status = $search_container.find(".ui-navlist-search-status");
			var $clear = $status.find("a");
			var $results = $("<ul class='ui-navlist-results' style='display:none'></ul>");
			var root = null;
			var indexed = null;
			var index_count = 0;
			var arrows = false;
			var class_filter = this.options.class_filter ? new RegExp(this.options.class_filter, "g") : false;

			var keys = {
				up: 38,
				left: 37,
				down: 40,
				right: 39,
				enter: 13,
				esc: 27,
				tab: 9
			};

			this.toggle = toggle;
			this.search_container = function () { return $search_container; };

			if (this.options.search_parent) {
				$(this.options.search_parent).append($search_container);
			} else {
				$ul.before($search_container);
			}

			$find.example(this.options.search_text);
			$ul.before($results);

			$find.keydown(function (e) {
				arrows = false;

				if (e.keyCode == keys.up || e.keyCode == keys.down) {
					arrows = true;

					var $nav = $results.children();
					var $selected = $nav.filter("li." + self.options.selected);
					var selected_index = $nav.index($selected);
					var new_index = selected_index;

					if (e.keyCode == keys.down) {
						new_index++;
					} else if (e.keyCode == keys.up) {
						new_index--;
					}

					if (new_index > $nav.length - 1) new_index = 0;
					if (new_index < 0) new_index = $nav.length - 1;

					$selected.removeClass(self.options.selected);
					$($nav[new_index]).addClass(self.options.selected).focus();
					$find.focus();

					return;
				}
			});

			$find.keyup(function (e) {
				if (arrows) {
					return;
				}

				if (e.keyCode == keys.enter) {
					window.location.href = $results.find("li." + self.options.selected + " a").attr("href");
					return;
				}

				if (e.keyCode == keys.esc) {
					clear();
					return;
				}

				var empty = $find.val().length == 0;
				$status.toggleClass("empty", empty);

				if (empty) {
					$ul.show();
					$clear.hide();
					$results.empty().hide();
				} else {
					$clear.show();
					$ul.hide();
					$results.show();

					if (!indexed) index();

					var words = get_words($find.val());
					var results = {};

					for (var word in indexed) {
						var nodes = indexed[word];

						for (var i in words) {
							var w = words[i];

							if (word.indexOf(w) == 0) {
								for (var j in nodes) {
									var n = nodes[j];
									var r = results[n.id];

									if (!r) {
										results[n.id] = r = {
											hits: [],
											node: n
										};
									}
									if (!contains(r.hits, w)) {
										r.hits.push(w);
									}
								}
							}
						}
					}

					var html = [];
					for (var id in results) {
						var result = results[id];
						if (result.hits.length != words.length) continue;

						html.push("<li class='");
						if (!result.node.className) html.push("no-class");
						else html.push(result.node.className + " has-class");
						html.push("'><a href='");
						html.push(result.node.href);
						html.push("'>");

						// Highlight matched terms
						var text = result.node.text.replace(/([\w']+)\b/g, function () {
							var w = arguments[1];
							for (var i in result.hits) {
								var h = result.hits[i];

								if (w.toLowerCase().indexOf(h) == 0) {
									return "<strong>" + w.substr(0, h.length) + "</strong>" + w.substr(h.length);
								}
							}
							return w;
						});
						html.push(text);

						html.push("</a></li>");
					}

					if (!html.length) {
						html.push("<li><a href='javascript:;'>No results</a></li>");
					}

					$results.html(html.join(''));
				}
			});

			$clear.click(function (e) {
				clear();
				$find.blur();
			});

			function clear() {
				$find.val("").keyup();
			}

			function index() {
				index_count = 0;
				indexed = {};
				root = index_node($ul);
			}

			function index_node($node) {
				var $a = $node.find(">a");

				var node = {
					id: index_count++,
					text: $a.attr("href") != "javascript:;" ? $a.text() : "",
					href: $a.attr("href"),
					className: class_filter ? ($node.attr("class") || "").replace(class_filter, "") : $node.attr("class")
				};

				var words = get_words(node.text);
				for (var i in words) {
					var w = words[i];

					var nodes = indexed[w];
					if (!nodes) {
						indexed[w] = nodes = [];
					}
					nodes.push(node);
				}

				var $children = $node.is("ul") ? $node.find(self.options.items) : $node.children("ul").find(self.options.items);
				$children.each(function () {
					var $child = $(this);
					var child = index_node($child);
				});

				return node;
			}

			function get_words(val) {
				var list = [];
				if (!val) return list;
				var words = $.trim(val).toLowerCase().split(" ");

				for (var i in words) {
					var w = words[i];
					if (!contains(list, w)) list.push(w);
				}

				return list;
			}

			function contains(arr, val) {
				for (var i in arr) {
					if (arr[i] == val) return true;
				}
				return false;
			}

			function toggle(show) {
				if (show === undefined) show = !$ul.is(":visible");
				$search_container.toggle(show);
				$ul.toggle(show);
			}
		}
	});
})(jQuery);
