// ==UserScript==
// @name          Javadoc Instant Search
// @namespace     http://jo.zerezo.com/projects/
// @description   Instant search for Javadoc class names.
// @include       */allclasses-frame.html
// @include       */allclasses-noframe.html
// @include       */package-frame.html
// @include       */overview-frame.html
// ==/UserScript==
//
// version 0.3 (2012/04/10)
// author Joël THIEFFRY
// http://jo.zerezo.com/projects/javadocInstantSearch.html
// 
// This script is distributed under the MIT licence.
// http://www.opensource.org/licenses/mit-license.php
//

(function() {
	// ID of the container for Javadoc Instant Search
	var ELEMENT_ID = "javadocInstantSearchElement";

	// IDs for regex type selector
	var ECLIPSE_REGEX_ID = "Eclipse";
	var SIMPLIFIED_REGEX_ID = "Simplified";
	var PLAIN_REGEX_ID = "Plain";

	// Compatibility layer for adding events
	addEvent = function(element, eventname, funcname) {
		if (element.addEventListener) {
			element.addEventListener(eventname, funcname, false);
		} else if (element.attachEvent) {
			element.attachEvent("on" + eventname, funcname);
		}
	}

	// Create and insert the Javadoc Instant Search container
	install = function() {
		// Detection of javadoc format
		var indexContainer = document.getElementsByClassName("indexContainer");
		var table = document.getElementsByTagName("table");
		if (!indexContainer && !table) {
			return;
		}
		var isModernFormat = indexContainer.length == 1;

		// Creation of search elements
		var searchContainer = document.createElement("p")
		searchContainer.id = ELEMENT_ID;

		var textInput = document.createElement("input");
		textInput.type = "text";
		textInput.style.backgroundColor = "White";

		var eraseIcon = document.createElement("input");
		eraseIcon.type = "image";
		// Origin: http://www.teria.com/~koseki/tools/gm/javadoc_isearch/index.html
		eraseIcon.src = "data:image/gif;base64,R0lGODlhDQANAJEDAM%2FPz%2F%2F%2F%2F93d3UpihSH5BAEAAAMALAAAAAANAA0AAAIwnCegcpcg4nIw2sRGDZYnBAWiIHJQRZbec5XXEqnrmXIupMWdZGCXlAGhJg0h7lAAADs%3D";
		eraseIcon.alt = "erase search pattern"
		eraseIcon.style.marginLeft = "3px";
		eraseIcon.style.marginRight = "3px";

		var isOverview = document.URL.match("/overview-frame\.html$", "g") != null;
		var typeRegex = document.createElement("select");
		typeRegex.size = 1;
		typeRegex.multiple = false;
		if (!isOverview) {
			var typeRegexOptionEclipse = document.createElement("option");
			typeRegexOptionEclipse.value = ECLIPSE_REGEX_ID;
			typeRegexOptionEclipse.appendChild(document.createTextNode("Eclipse"));
			typeRegexOptionEclipse.selected = true;
			typeRegex.add(typeRegexOptionEclipse);
		}
		var typeRegexOptionSimplifiedRegex = document.createElement("option");
		typeRegexOptionSimplifiedRegex.value = SIMPLIFIED_REGEX_ID;
		typeRegexOptionSimplifiedRegex.appendChild(document.createTextNode("Simplified"));
		typeRegexOptionSimplifiedRegex.selected = isOverview;
		typeRegex.add(typeRegexOptionSimplifiedRegex);
		var typeRegexOptionPlainRegex = document.createElement("option");
		typeRegexOptionPlainRegex.value = PLAIN_REGEX_ID;
		typeRegexOptionPlainRegex.appendChild(document.createTextNode("Regex"));
		typeRegex.add(typeRegexOptionPlainRegex);

		// Insertion of search elements
		searchContainer.appendChild(textInput);
		searchContainer.appendChild(eraseIcon);
		searchContainer.appendChild(typeRegex);
		if (isModernFormat) {
			indexContainer = indexContainer[0];
			indexContainer.insertBefore(searchContainer, indexContainer.firstChild);
		} else {
			table = table[0];
			table.parentNode.insertBefore(searchContainer, table);
		}

		// Handle the timeout for text input
		var searchTimeoutHandle = null;
		setSearchTimeout = function() {
			if (searchTimeoutHandle) {
				clearTimeout(searchTimeoutHandle);
			}
			searchTimeoutHandle = setTimeout(runSearch, 200); // in milliseconds
		}

		// Run the search
		runSearch = function() {
			if (searchTimeoutHandle) {
				clearTimeout(searchTimeoutHandle);
				searchTimeoutHandle = null;
			}
			var acceptFunction = function() {
				return true;
			};
			if (textInput.value) {
				var regex = textInput.value;
				//console.log("input  = " + regex);
				var selectedTypeRegex = typeRegex.options[typeRegex.selectedIndex].value;
				switch (selectedTypeRegex) {
					case ECLIPSE_REGEX_ID:
						regex = "^.*" + regex.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1") +  ".*$";
						regex = regex.replace(/([A-Z])/g, "\.\*$1");
						break;
					case SIMPLIFIED_REGEX_ID:
						// Escape all except ? and *
						regex = regex.replace(/([.+^$[\]\\(){}|-])/g, "\\$1");
						regex = "^.*" + regex.replace(/(?:\*)+/g, ".*").replace(/(?:\?)+/g, ".") + ".*$";
						break;
				}
				//console.log("before = " + regex);
				regex = regex.replace(/((.[\*\?])+)(?=\2)/g, "")  // Replace successive x* and x? to only one
					         .replace(/((.)\*\2\?)+/g, ".*")      // Replace x*x? with x*
					         .replace(/((.)\?\2\*)+/g, ".*")      // Replace x?x* with x*
					         .replace(/((.)\+\2\*)+/g, ".+")      // Replace x+x* with x+
					         .replace(/((.)\*\2\+)+/g, ".+")      // Replace x*x+ with x+
					         .replace(/((.[\*\?])+)(?=\2)/g, ""); // Replace successive x* and x? to only one
				//console.log("after  = " + regex);
				try {
					var searchRegexp = new RegExp(regex, 'g');
				} catch (err) {
					textInput.style.backgroundColor = "Tomato";
					return;
				}
				textInput.style.backgroundColor = "Lavender";
				acceptFunction = function(name) {
					return searchRegexp.test(name);
				}
			} else {
				textInput.style.backgroundColor = "White";
			}

			if (isModernFormat) {
				var items = indexContainer.getElementsByTagName("li");
				for (var iItem = 0; iItem < items.length; iItem++) {
					var liItem = items[iItem];
					var itemText = liItem.firstChild.firstChild;
					if (itemText.tagName == "I") {
						itemText = itemText.firstChild;
					}
					liItem.style.display = acceptFunction(itemText.nodeValue) ? "" : "none";
				}
			} else {
				var items = table.getElementsByTagName("a");
				for (var iItem = 0; iItem < items.length; iItem++) {
					var aItem = items[iItem];
					var itemText = aItem.firstChild;
					if (itemText.tagName == "I") {
						itemText = itemText.firstChild;
					}
					var displayStyle = acceptFunction(itemText.nodeValue) ? "" : "none";
					aItem.style.display = displayStyle;
					var nextBr = aItem.nextSibling;
					while (nextBr.nodeType != 1) {
          				nextBr = nextBr.nextSibling;
					}
					if (nextBr.tagName == "BR") {
						nextBr.style.display = displayStyle;
					}
				}
			}
			
			textInput.focus();
		}

		// Change the type of regex
		changeRegexType = function() {
			if (textInput.value != "") {
				runSearch(); // will clear the timeout
			}
		}

		// Erase the current input pattern
		eraseSearch = function() {
			if (textInput.value != "") {
				textInput.value = "";
				runSearch(); // will clear the timeout
			}
		}

		addEvent(textInput, "input", setSearchTimeout);
		addEvent(typeRegex, "change", changeRegexType);
		addEvent(eraseIcon, "click", eraseSearch);

		textInput.focus();
	}

	// Install the component when window has done loading, and the component is not already here
	if (!document.getElementById(ELEMENT_ID)) {
		install();
	}
})();