/* new MenuSearch(top_of_link_tree_id, section_class_name)
 * new MenuSearch({topElementId: top_of_link_tree_id, sectionClassName: section_class_name, sectionDetect: function(e) {return(e.className==section_class_name)}})
 *
 * top_of_link_tree_id is the id of an element to limit the scope of this
 * function; section_class_name is the class name of every block you want to
 * be treated as a section, ie that for which every child node has exactly one
 * link as a descendant.
 */
function MenuSearch(top_of_link_tree_id, section_class_name) { // Start of wrapper function.
if(top_of_link_tree_id.sectionClassName || top_of_link_tree_id.sectionDetect) {
	var options = top_of_link_tree_id;
	top_of_link_tree_id = options.topElementId;
	section_class_name = options.sectionClassName;
	this.sectionDetect=options.sectionDetect;
	this.gatheredResultsElementId = options.gatheredResultsElementId;
	this.replacementNodes = options.replacementNodes;
	this.sectionLimits = options.sectionLimits;
	this.overflowNotify = options.overflowNotify;
} else {
	this.gatheredResultsElementId = "autocomplete-results";
}
top_of_link_tree_id = top_of_link_tree_id || document.body;
var top_of_link_tree;
if(top_of_link_tree_id.tagName) {
	// It's really an element.
	top_of_link_tree = top_of_link_tree_id;	
	top_of_link_tree.id=top_of_link_tree.id|| Math.random();
	top_of_link_tree_id=top_of_link_tree.id;
}

this.topOfLinkTree = function() {
	if(!top_of_link_tree) {
		top_of_link_tree=document.getElementById(top_of_link_tree_id);
	}
	return top_of_link_tree;
}

this.sectionDetect=this.sectionDetect||function(e) {
	return(e.className==section_class_name);
};

var links = new Array(); // Set up below.

var cache_l_search = [];
var last_l_search;

function _keyword_match(k_list, n) {
	for(var i=0; i<k_list.length; i++) {
		if(k_list[i].match(n)) return true;
	}
	return false;
}

function _find_matching_elements(e, f) {
	if(f(e)) {
		return [e];
	} else {
		var cn = e.childNodes;
		var r = [];
		for(var i=0; i<cn.length; i++) {
			if(!cn.item(i).tagName) continue;
			r=r.concat(_find_matching_elements(cn.item(i), f));
		}
		return r;
	}
}

/* menu_link_search(query_string)
 *
 * Strips the menu down to just those entries which match the search, either
 * by href, title, or content. This is a "wildcard either side" search.
 *
 * At the same time, if the query seems long enough to product useful results
 * (>=4 characters in the general case, >=2 for intranet) it will also fire off
 * a left-to-right search for customers/domains, users/servers and tickets.
 */
this.menuLinkSearch = function(n) {
	var l;
	last_l_search = n;
	if(n.length == 0) {
		// Return to normal
		l = links;
	} else if(n.length > 0) {
		if(!cache_l_search[n]) {
			cache_l_search[n] = {l: []};
			for(var i=0;i<links.length; i++) {
				if(links[i].keywords.length>0) {
					if(_keyword_match(links[i].keywords, n))
						cache_l_search[n].l.push(links[i]);
				} else {
					// Just add it anyway - it's not searchable
					cache_l_search[n].l.push(links[i]);
				}
			}
		}
		l = cache_l_search[n].l;
	}
	if(this.gatheredResultsElementId) autocomplete_search.call(this, n);
	add_links.call(this, l);
}


/* autocomplete_search(query_string, opts={})
 *
 * A subset of menu_search() above, does the external searches.
 *
 * Currently supported in opts:
 * (nothing)
 */
this.autocompleteSearchInProgress=0;
var autocomplete_retest;
this.autocompleteRestestIfNeeded = function() {
	if(autocomplete_retest) autocomplete_search.call(this, autocomplete_retest);
}
this.bind = function(f) {
	return _bind_to_any(this, f);
}
this.bindName = function(n) {
	return this.bind(this[n]);
}
this.autocompleteTypes = [];
function autocomplete_search(n, opts) {
	if(this.autocompleteSearchInProgress) {
		autocomplete_retest=n;
		return;
	}
	opts=opts||{};
	autocomplete_retest="";
	this.autocompleteSearchInProgress=1;
	var default_filler = "...";
	
	var tried=0;
	var autocomplete_types = this.autocompleteTypes;
	// Wipe all result sets
	var progress=0;
	for(var t=0;t<autocomplete_types.length; t++) {
		progress++; // Even bad ones are added here.
		var conf = autocomplete_types[t];
		var wrapper = document.getElementById(conf.topElementId||conf.id);
		if(!wrapper) continue;
		var w = wrapper.cloneNode(false);
		w.appendChild(document.createTextNode(default_filler));
		wrapper.parentNode.replaceChild(w, wrapper);
	}
	var this_o=this;
	function onCompleteIsh() {
		if(--progress) return;
		this_o.autocompleteSearchInProgress=0;
		// Strip the filler.
		for(var t=0;t<autocomplete_types.length; t++) {
			var conf = autocomplete_types[t];
			var wrapper = document.getElementById(conf.topElementId||conf.id);
			if(!wrapper) continue;
			if(!wrapper.lastChild) continue;
			if(wrapper.lastChild.data==default_filler) 
				wrapper.removeChild(wrapper.lastChild);
		}
	}
	progress++; // It'll be decremented below
	function onSuccessFromConf(conf) {
		return function() {conf.setResults.apply(conf, arguments); onCompleteIsh()};
	}
	function onErrorFromConf(conf) {
		return function() {(conf.onError||conf.setResults).apply(conf, arguments); onCompleteIsh()};
	}
	// Set each result set to be filled
	for(var t=0;t<autocomplete_types.length; t++) {
		var conf = autocomplete_types[t];
		if(! conf.prerequisite(n)) {
			conf.lastSearch = "";
			progress--;
		} else {
			// Looks valid
			tried++;
			conf.lastSearch = n;
			xmlhttp_call_with_args(
				conf.id,
				build_simple_xmlr_statechange(
					conf.id, 
					onSuccessFromConf(conf), 
					onErrorFromConf(conf), 
					autocomplete_results_check
				),
				conf.script, 
				{q: n}
			);
		}
	}
	onCompleteIsh();
}

/* autocomplete_results_check(xml_el)
 *
 * Callback. Returns true unless the "too-many" attribute is set on the root
 * element.
 */
function autocomplete_results_check(xml_el) {
	return(0 == xml_el.getAttribute("too-many"));
}

/* add_links(link_set)
 *
 * Sets the links block to only contain the links in the set.
 */
function add_links(l) {
	var by_section = [];
	var sections = _find_matching_elements(this.topOfLinkTree(), this.sectionDetect);

	var found_by_section = [];
	for(var i=0; i<sections.length; i++) {
		by_section[sections[i].id] = [];
		found_by_section[sections[i].id] = 0;
	}

	for(var i=0;i<l.length;i++) {
		if(l[i].keywords.length>0) {
			found_by_section[l[i].section]++;
		}
		by_section[l[i].section].push(l[i]);
	}

	var top_element = this.gatheredResultsElementId ? 
		document.getElementById(this.gatheredResultsElementId) :
		undefined;
	for(var i=0; i<sections.length; i++) {
		el = sections[i];
		if(!el.id) continue;
		// Remove everything
		// ... and add it back.
		new_el = el.cloneNode(false);
		var in_this_section = by_section[el.id];

		var limit = in_this_section.length;
		if(this.sectionLimits && this.sectionLimits[el.id])
			if(this.sectionLimits[el.id] < limit)
				limit = this.sectionLimits[el.id]

		if(this.overflowNotify && this.overflowNotify[el.id])
			if(limit < in_this_section.length) {
				var missing_nodes=[];
				for(var j=limit; j<in_this_section.length; j++)
					missing_nodes.push(in_this_section[j].el);
				this.overflowNotify[el.id](
					in_this_section.length - limit, 
					missing_nodes,
					new_el
				);
			} else {
				this.overflowNotify[el.id](0, [], new_el);
			}

		for(var j=0; j<limit; j++) {
			new_el.appendChild(in_this_section[j].el);
			// Also add to the top list.
			if(top_element && links.length > l.length) {
				if(top_element.firstChild) {
					top_element.insertBefore(in_this_section[j].el.cloneNode(true), top_element.lastChild);
				} else {
					top_element.appendChild(in_this_section[j].el.cloneNode(true));
				}
			}
		}
		el.parentNode.replaceChild(new_el, el);
	}
	for(var section_id in by_section) {
		var el = document.getElementById(section_id);
		if(el && this.onSectionEmpty && ! found_by_section[section_id]) {
			this.onSectionEmpty.call(el);
		} else if(el && this.onSectionNotEmpty && found_by_section[section_id]) {
			this.onSectionNotEmpty.call(el, found_by_section[section_id]);
		}
	}
}

/* sloppyMenuLinkSearch()
 *
 * A simple wrapper for calling menu_link_search() out of context.
 */
this.sloppyMenuLinkSearch = function(i) {
	return this.menuLinkSearch(document.getElementById(i).value);
}
// This helps ensure that all local links do not get indexed the same way
var this_dir;
var this_host;
{
	var here = location.href;
	this_dir=here.replace(/[^\/]*$/,"");
	var split_href=here.split("//", 2);
	split_href[1] = split_href[1].replace(/\/.*/,"");
	this_host = split_href.join("//");
}
// -
this.anchorToKeywords = function(a) {
	var keywords = [];
	var target_href=(a.href||"").toLowerCase();
	target_href = _u_prototype.call(target_href, "stripSuffix", this_dir) || _u_prototype.call(target_href, "stripSuffix", this_host) || target_href;

	if(target_href) keywords.push(target_href.replace(/\.[a-z0-9]*(\?|#|$)/, ""));
	if(a.title) keywords.push(a.title.toLowerCase());
	for(var i=0; i<a.childNodes.length; i++) {
		var child = a.childNodes.item(i);
		if(child.data && ! child.tagName)
			keywords.push(child.data.toLowerCase());
	}
	return keywords;
}

this.scanSubsection = function(subsection_top, suggested_id) {
	if(!subsection_top.id) subsection_top.id = suggested_id;
	var keywords=[];
	var uid_found = subsection_top.id;
	if(subsection_top.getElementsByTagName) {
		var links_in_section = subsection_top.getElementsByTagName("A");
		for(var j=0; j<links_in_section.length; j++) {
			keywords = keywords.concat(this.anchorToKeywords(links_in_section.item(j)));
		}
	}
	if(this.moreKeywords) keywords = keywords.concat(this.moreKeywords(subsection_top));
	return {el: subsection_top.cloneNode(true), keywords: keywords, uid: uid_found};
}

/* cacheLinks()
 *
 * To be called after all your links appear. Scans down from the link top
 * element to find all links and put them in the array.
 */
this.cacheLinks = function() { // Cache links
	links = new Array();
	var sections = _find_matching_elements(this.topOfLinkTree(), this.sectionDetect);
	var counts_by_keyword={};
	var link_with_keyword_count=0;
	for(var i=0; i<sections.length; i++) {
		el = sections[i];
		if(!el.id) el.id = "menu-search-"+top_of_link_tree_id+"-"+i;

		var subsections = [];
		if(this.replacementNodes && this.replacementNodes[el.id]) {
			subsections=this.replacementNodes[el.id];
		} else {	
			for(var j=0; j<el.childNodes.length; j++)
				subsections.push(el.childNodes.item(j));
		}

		for(var m=0;m<subsections.length; m++) {
			var section_data = this.scanSubsection(subsections[m], el.id+"-"+m);
			for(var j=0; j<section_data.keywords.length; j++) {
				var keyword = section_data.keywords[j];
				counts_by_keyword[keyword]=(counts_by_keyword[keyword]||0)+1;
			}
			section_data.section = el.id;
			if(section_data.keywords.length>0) link_with_keyword_count++;
			links.push(section_data);
		}
	}
	var overused_keywords={};
	for(var keyword in counts_by_keyword)
		if(counts_by_keyword[keyword]==link_with_keyword_count)
			overused_keywords[keyword]=1;
	for(var i=0; i<links.length; i++) {
		var keywords = links[i].keywords;
		var new_keywords = [];
		for(var j=0; j<keywords.length; j++)
			if(!overused_keywords[keywords[j]]) new_keywords.push(keywords[j]);
		links[i].keywords=new_keywords;
	}
}
/*
 * prepare(search_box_id)
 * prepare(search_box)
 *
 * Caches links and prepares your search box
 */
this.prepare = function(search_box_id) {
	var existing_search_box = search_box_id.tagName ?
		search_box_id : 
		document.getElementById(search_box_id);
	if(!existing_search_box) return;
	var i = existing_search_box.cloneNode(true);
	i.value="";
	var t = this;
	i.onkeyup = function() {
		run_late_but_not_simultaneously(function() {t.menuLinkSearch(i.value)}, 400);
	};
	i.onmouseup = function() {
		run_late_but_not_simultaneously(function() {t.sloppyMenuLinkSearch(i.id)}, 200);
	};
	var wipe_button = 
		_mkel("a", {
				onclick: function() {
					i.value="";
					t.menuLinkSearch("");
				}
			}, ["X"], function(e) {e.style.marginLeft = "0.5em"}
		);
	// Because Internet Explorer 7 doesn't support CSS, it'll ignore
	// display: inline-block, so give them a span (instead of the more correct
	// div) to at least keep the block layout fairly sane
	var wrapper = _mkel("span", "", [i, wipe_button], function(e) {e.style.whiteSpace = "nowrap"; e.style.display="inline-block"; e.style.verticalAlign="baseline";});
	existing_search_box.parentNode.replaceChild(wrapper, existing_search_box);
		
	this.cacheLinks();
	if(links.length==0) alert("No links found");
}
} // End of wrapper function
