
// A few notes on implementation:
//
// 1.  We use a Lisp technique to avoid namespace pollution:  define, then
// immediately invoke anonymous functions in order to get a new scope for
// variables which you would otherwise make global.
//
// 2.  We avoid the newer addEventListener mechanism for browser compatibility
// reasons.  However, we still don't want our event handlers to suppress others
// the user may have defined.  As such, we manually save and invoke any
// existing handler before invoking our new functionality.
//
// 3.  The "cancel box" technique allows the user to click anywhere outside
// of the menu to cancel.  Because we're building our own pseudo-menu instead
// of using a real one, we have to simulate this behavior using a full-screen
// invisible div.
//
// 4.  Keyboard handling needs to be a bit complicated in order to suppress
// the browser's default behavior for certain keys.  It is not enough to trap
// "onkeydown" or "onkeyup"; you need to do both.
//
// 5.  Although AJAX would be cleaner, we use an alternative technique called
// JSONP (inserting "script" elements into the DOM) to retrieve data from the
// backend, because browser security restrictions require the backend to be on
// the same web server as the HTML page for AJAX.

var PicoSearchAutoCompleteRenderSuggestions = null;

window.onload = (function()
{
	var backend_server = "http://www.picosearch.com/autocomplete";

	var existing_window_onload_handler = window.onload;

	return function(event)
	{
		if (existing_window_onload_handler != null) existing_window_onload_handler(event);

		var min_prefix_length = 2;
		var typeahead_delay = 10; // milliseconds
		var head = getElementsByTagName("head", document)[0];
		var body = getElementsByTagName("body", document)[0];
		var query_boxes = getElementsByClassName("PicoSearchAutoComplete", document, "input");

		for (var i = 0; i < query_boxes.length; i++)
		{
			(function()
			{
				var query_box = query_boxes[i];
				var account = null;
				var form_encoding = null;
				var dictionary_encoding = null;
				var font = "menu";
				var border_color = "WindowFrame";
				var unselected_background_color = "Window";
				var unselected_text_color = "WindowText";
				var selected_background_color = "Highlight";
				var selected_text_color = "HighlightText";
				var dynamic_menu_width = 0;
				var timeout_id = null;
				var previous_prefix = null;
				var menu_cancel_box = null;
				var menu_box = null;
				var capture_keys = false;
				var highlit_row = null;

				function process_input_element(input_element)
				{
					var parameter_name = getAttribute(input_element, "name");

					if (parameter_name == "index")
					{
						account = getAttribute(input_element, "value");
					}
					else if (parameter_name == "formenc")
					{
						form_encoding = getAttribute(input_element, "value");
					}
					else if (parameter_name == "dictenc")
					{
						dictionary_encoding = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-font")
					{
						font = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-border-color")
					{
						border_color = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-unselected-background-color")
					{
						unselected_background_color = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-unselected-text-color")
					{
						unselected_text_color = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-selected-background-color")
					{
						selected_background_color = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-selected-text-color")
					{
						selected_text_color = getAttribute(input_element, "value");
					}
					else if (parameter_name == "autocomplete-dynamic-menu-width")
					{
						dynamic_menu_width = (getAttribute(input_element, "value") != "0");
					}
				}

				for (var node = query_box.parentNode; node != null; node = node.parentNode)
				{
					if (node.nodeName.toLowerCase() == "form")
					{
						var input_elements = getElementsByTagName("input", node);

						for (var j = 0; j < input_elements.length; j++)
						{
							process_input_element(input_elements[j]);
						}

						break;
					}
				}

				// It is invalid HTML to place a <form> immediately inside a <table>, but we have traditionally done so in our default template.  Behind the scenes, Firefox shuffles the DOM to make a valid document out of this, but it does so by reparenting the <form> and <input> elements in unintuitive places.  The following code searches those places if we couldn't find our data in its usual home.

				(function()
				{
					if (account == null)
					{
						var node = query_box.form;

						if (node != null)
						{
							for (node = node.nextSibling; (node != null) && (node.nodeName.toLowerCase() != "form"); node = node.nextSibling)
							{
								if (node.nodeName.toLowerCase() == "input")
								{
									process_input_element(node);
								}
							}
						}
					}
				})();

				(function()
				{
					if (account == null)
					{
						var node = query_box.form;

						if (node != null)
						{
							var input_elements = getElementsByTagName("input", node);

							for (var j = 0; j < input_elements.length; j++)
							{
								process_input_element(input_elements[j]);
							}
						}
					}
				})();

				query_box.setAttribute("autocomplete", "off");
				query_box.autocomplete = "off";  /* turn off firefox autocomplete, seems portable */

				query_box.onkeydown = function(event)
				{
					if (!event) event = window.event;

					if (capture_keys)
					{
						switch (event.keyCode)
						{
							case /* tab */ 9:
							{
								if (menu_cancel_box != null)
								{
									menu_cancel_box.parentNode.removeChild(menu_cancel_box);
									menu_cancel_box = null;
								}

								if (menu_box != null)
								{
									menu_box.parentNode.removeChild(menu_box);
									menu_box = null;
								}

								previous_prefix = null;
								capture_keys = false;
								highlit_row = null;
								return true;
							}
							case /* escape */ 27:
							{
								if (menu_cancel_box != null)
								{
									menu_cancel_box.parentNode.removeChild(menu_cancel_box);
									menu_cancel_box = null;
								}

								if (menu_box != null)
								{
									menu_box.parentNode.removeChild(menu_box);
									menu_box = null;
								}

								query_box.value = previous_prefix;
								previous_prefix = null;
								highlit_row = null;
								return false;
							}
							case /* up */ 38:
							{
								var rows = getElementsByTagName("tr", menu_box);

								if (highlit_row == null)
								{
									highlit_row = rows.length - 1;
								}
								else
								{
									rows[highlit_row].style.background = unselected_background_color;
									rows[highlit_row].style.color = unselected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", rows[highlit_row]);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = unselected_background_color;
											table_cells[table_cell].style.color = unselected_text_color;
										}
									})();

									highlit_row = highlit_row - 1;
								}

								if (highlit_row < 0)
								{
									query_box.value = previous_prefix;
									highlit_row = null;
								}
								else
								{
									query_box.value = getElementsByTagName("td", rows[highlit_row])[0].firstChild.nodeValue;
									rows[highlit_row].style.background = selected_background_color;
									rows[highlit_row].style.color = selected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", rows[highlit_row]);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = selected_background_color;
											table_cells[table_cell].style.color = selected_text_color;
										}
									})();
								}

								return false;
							}
							case /* down */ 40:
							{
								var rows = getElementsByTagName("tr", menu_box);

								if (highlit_row == null)
								{
									highlit_row = 0;
								}
								else
								{
									rows[highlit_row].style.background = unselected_background_color;
									rows[highlit_row].style.color = unselected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", rows[highlit_row]);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = unselected_background_color;
											table_cells[table_cell].style.color = unselected_text_color;
										}
									})();

									highlit_row = highlit_row + 1;
								}

								if (highlit_row >= rows.length)
								{
									query_box.value = previous_prefix;
									highlit_row = null;
								}
								else
								{
									query_box.value = getElementsByTagName("td", rows[highlit_row])[0].firstChild.nodeValue;
									rows[highlit_row].style.background = selected_background_color;
									rows[highlit_row].style.color = selected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", rows[highlit_row]);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = selected_background_color;
											table_cells[table_cell].style.color = selected_text_color;
										}
									})();
								}

								return false;
							}
							default:
							{
								break;
							}
						}
					}

					return true;
				};

				query_box.onkeyup = function(event)
				{
					if (!event) event = window.event;

					if (capture_keys)
					{
						switch (event.keyCode)
						{
							case /* escape */ 27:
							{
								capture_keys = false;
								return false;
							}
							case /* up */ 38:
							{
								return false;
							}
							case /* down */ 40:
							{
								return false;
							}
							default:
							{
								break;
							}
						}
					}

					if (timeout_id != null)
					{
						window.clearTimeout(timeout_id);
						timeout_id = null;
					}

					timeout_id = window.setTimeout(function()
					{
						getSuggestions(query_box.value);
					}, typeahead_delay);

					return true;
				};

				function getSuggestions(prefix)
				{
					if (prefix != previous_prefix)
					{
						previous_prefix = prefix;

						if (prefix.length >= min_prefix_length)
						{
							PicoSearchAutoCompleteRenderSuggestions = renderSuggestions;
							var script_inclusion = document.createElement("script");
							script_inclusion.src = backend_server + "?Callback=PicoSearchAutoCompleteRenderSuggestions&Query=" + encodeURIComponent(prefix) + ((account == null) ? "" : ("&Account=" + encodeURIComponent(account))) + ((form_encoding == null) ? "" : ("&FormEnc=" + encodeURIComponent(form_encoding))) + ((dictionary_encoding == null) ? "" : ("&DictEnc=" + encodeURIComponent(dictionary_encoding)));
							script_inclusion.type = "text/javascript";
							head.appendChild(script_inclusion);
						}
						else
						{
							renderSuggestions([]);
						}
					}
				}

				function renderSuggestions(suggestions)
				{
					if (menu_cancel_box != null)
					{
						menu_cancel_box.parentNode.removeChild(menu_cancel_box);
						menu_cancel_box = null;
					}

					if (menu_box != null)
					{
						menu_box.parentNode.removeChild(menu_box);
						menu_box = null;
					}

					capture_keys = false;
					highlit_row = null;

					if (suggestions.length > 0)
					{
						menu_cancel_box = document.createElement("div");
						body.appendChild(menu_cancel_box);
						menu_cancel_box.className = "PicoSearchAutoComplete";
						menu_cancel_box.style.position = "absolute";
						menu_cancel_box.style.zIndex = 1;
						menu_cancel_box.style.top = "0px";
						menu_cancel_box.style.left = "0px";
						menu_cancel_box.style.height = Math.max((window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight), body.scrollHeight) + "px";
						menu_cancel_box.style.width = Math.max((window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth), body.scrollWidth) + "px";
						menu_cancel_box.style.background = "url(clear-pixel.png)"; // workaround for IE7 not responding to clicks if there is nothing in the div and no background

						menu_cancel_box.onclick = function(event)
						{
							menu_cancel_box.parentNode.removeChild(menu_cancel_box);
							menu_cancel_box = null;

							menu_box.parentNode.removeChild(menu_box);
							menu_box = null;

							previous_prefix = null;
							capture_keys = false;
							highlit_row = null;
						};

						menu_box = document.createElement("table");
						body.appendChild(menu_box);
						menu_box.className = "PicoSearchAutoComplete";
						menu_box.style.position = "absolute";
						menu_box.style.zIndex = 2;

						(function()
						{
							var node = query_box;
							var top = 0;
							var left = 0;

							while (node != null)
							{
								top += node.offsetTop;
								left += node.offsetLeft;
								node = node.offsetParent;
							}

							menu_box.style.top = top + query_box.offsetHeight + "px";
							menu_box.style.left = left + "px";
						})();

						if (! dynamic_menu_width) menu_box.style.width = query_box.offsetWidth + "px";
						menu_box.style.borderCollapse = "collapse";
						menu_box.style.border = "solid 1px " + border_color;
						menu_box.style.background = unselected_background_color;
						menu_box.style.color = unselected_text_color;
						menu_box.style.font = font;

						var table_body = document.createElement("tbody");
						menu_box.appendChild(table_body);
						table_body.className = "PicoSearchAutoComplete";

						for (var i = 0; i < suggestions.length; i++)
						{
							var table_row = document.createElement("tr");
							table_body.appendChild(table_row);
							table_row.className = "PicoSearchAutoComplete";
							table_row.style.cursor = "default";

							table_row.onmouseover = (function()
							{
								var self = table_row;

								return function(event)
								{
									var rows = getElementsByTagName("tr", menu_box);

									if (highlit_row != null)
									{
										rows[highlit_row].style.background = unselected_background_color;
										rows[highlit_row].style.color = unselected_text_color;

										(function()
										{
											var table_cells = getElementsByTagName("td", rows[highlit_row]);

											for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
											{
												table_cells[table_cell].style.background = unselected_background_color;
												table_cells[table_cell].style.color = unselected_text_color;
											}
										})();
									}

									self.style.background = selected_background_color;
									self.style.color = selected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", self);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = selected_background_color;
											table_cells[table_cell].style.color = selected_text_color;
										}
									})();

									for (highlit_row = 0; highlit_row < rows.length; highlit_row++)
									{
										if (rows[highlit_row] == self) break;
									}
								};
							})();

							table_row.onmouseout = (function()
							{
								var self = table_row;

								return function(event)
								{
									self.style.background = unselected_background_color;
									self.style.color = unselected_text_color;

									(function()
									{
										var table_cells = getElementsByTagName("td", self);

										for (var table_cell = 0; table_cell < table_cells.length; table_cell++)
										{
											table_cells[table_cell].style.background = unselected_background_color;
											table_cells[table_cell].style.color = unselected_text_color;
										}
									})();
								};
							})();

							table_row.onclick = (function()
							{
								var suggestion = suggestions[i];

								return function(event)
								{
									query_box.value = suggestion;

									menu_cancel_box.parentNode.removeChild(menu_cancel_box);
									menu_cancel_box = null;

									menu_box.parentNode.removeChild(menu_box);
									menu_box = null;

									previous_prefix = null;
									capture_keys = false;
									highlit_row = null;

									for (var node = query_box; node != null; node = node.parentNode)
									{
										if (node.submit)
										{
											node.submit();
											break;
										}
									}
								};
							})();

							var table_cell = document.createElement("td");
							table_row.appendChild(table_cell);
							table_cell.className = "PicoSearchAutoComplete";
							table_cell.style.padding = "3px 5px 3px 5px";
							table_cell.style.background = unselected_background_color;
							table_cell.style.color = unselected_text_color;
							table_cell.style.font = font;
							table_cell.style.textAlign = "left";
							
							table_cell.appendChild(document.createTextNode(suggestions[i]));
						}

						capture_keys = true;
					}
				}
			})();
		}

		function getElementsByClassName(class_name, node, tag_name)
		{
			if (!node) node = document;
			if (!tag_name) tag_name = "*";
			var pattern = new RegExp("(^|\\s)" + class_name + "(\\s|$)");
			var candidate_elements = getElementsByTagName(tag_name, node);
			var matching_elements = new Array();

			for (var i = 0; i < candidate_elements.length; i++)
			{
				if (pattern.test(candidate_elements[i].className)) matching_elements.push(candidate_elements[i]);
			}

			return matching_elements;
		}

		function getElementsByTagName(tag_name, node)
		{
			if (!node) node = document;
			var candidate_elements = node.getElementsByTagName("*");
			var matching_elements = new Array();

			for (var i = 0; i < candidate_elements.length; i++)
			{
				if (candidate_elements[i].tagName.toLowerCase() == tag_name) matching_elements.push(candidate_elements[i]);
			}

			return matching_elements;
		}

		function getAttribute(element_node, attribute_name)
		{
			for (var i = 0; i < element_node.attributes.length; i++)
			{
				if (element_node.attributes[i].nodeName.toLowerCase() == attribute_name) return element_node.attributes[i].nodeValue;
			}

			return null;
		}
	};
})();


