


































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;
    this.noLinksExpected = options.noLinksExpected;
    this.dontAddLinks = options.dontAddLinks;
    this.dontSearchInternal = options.dontSearchInternal;
    this.clearLinkCaption = options.clearLinkCaption || "X";
    this.clearAfterLinks = options.clearAfterLinks;
    this.existingClearButtonID = options.existingClearButtonID;
    this.noLinkCopy = options.noLinkCopy;
  } 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;
    }
  }

  








  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.dontSearchInternal && this.gatheredResultsElementId) autocomplete_search.call(this, n);
    if(!this.dontAddLinks) add_links.call(this, l);
  }


  






  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) {
    n=n.replace(/^[\r\n\s]+/, "").replace(/[\r\n\s]+$/, "");
    if(this.autocompleteSearchInProgress) {
      autocomplete_retest=n;
      return;
    }
    opts=opts||{};
    autocomplete_retest="";
    this.autocompleteSearchInProgress=1;
    var default_filler = options.defaultFiller || "...";
    
    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);
      if(options.onSearch) options.onSearch.call(w);
      if(!this.dontAddLinks) {
        this.filler = _mkel("div", {className: "autocomplete-footer"}, [default_filler]);
        w.appendChild(this.filler);
      }
      if(this.clearAfterLinks && n.length) {
        w.appendChild(_mkel("div", {className: "autocomplete-footer", style: {textAlign: "right"}}, [this.wipeButton]));
      }
      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(! ( this_o.filler && this_o.filler.parentNode ) ) continue;
        if(wrapper.lastChild.tagName && wrapper.lastChild.firstChild.data==default_filler) {
          

          if(this_o.filler.parentNode == wrapper)
            wrapper.removeChild(this_o.filler);
          if(n && tried && wrapper.childNodes.length==0) 
            wrapper.appendChild(_mkel("div", {className: "autocomplete-footer"}, "No results found... try fewer or more general keywords"));
        } else {
          

          if(this_o.filler.parentNode == wrapper)
            wrapper.removeChild(this_o.filler);
          if(n && tried && wrapper.childNodes.length==1) 
            wrapper.insertBefore(_mkel("div", {className: "autocomplete-footer"}, "No results found... try fewer or more general keywords"), wrapper.firstChild);
        }
        if(options.onClear && !n) options.onClear.call(wrapper);
      }
    }
    progress++; // It'll be decremented below
    function onSuccessFromConf(conf, lf) {
      return function() {conf.setResults.apply(conf, arguments); onCompleteIsh(); if(lf) lf()};
    }
    function onErrorFromConf(conf, lf) {
      return function() {(conf.onError||conf.setResults).apply(conf, arguments); onCompleteIsh(); if(lf) lf()};
    }

    var dispatch_values = [];
    function dispatcher(conf, lf) {
      conf.dispatch(autocomplete_results_check, n, onSuccessFromConf(conf, lf), onErrorFromConf(conf, lf), options);
    }
    function handle_dispatch(conf) {
      if(options.dispatchChain) {
        dispatch_values.push(conf);
      } else {
        return dispatcher(conf);
      }
    }
    // 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 if(conf.dispatch) {
        // Looks valid
        tried++;
        handle_dispatch(conf);
      } else {
        // Looks valid
        tried++;
        conf.lastSearch = n;
        xmlhttp_simple_full(
          conf.script,
          {q: n},
          autocomplete_results_check,
          onSuccessFromConf(conf),
          onErrorFromConf(conf),
          {sync: options.sync}
        );
      }
    }
    if(dispatch_values.length) chain(dispatch_values, dispatcher);
    onCompleteIsh();
  }

  




  function autocomplete_results_check(xml_el) {
    return(0 == xml_el.getAttribute("too-many"));
  }

  



  function add_links(l) {
    var by_section = [];
    var tolt = this.topOfLinkTree();
    var sections = tolt ? 
      _find_matching_elements(tolt, 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;
    var el;
    for(var i=0; i<sections.length; i++) {
      el = sections[i];
      if(!el.id) continue;
      // Remove everything
      // ... and add it back.
      var 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]

      // Must go first
      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 && ! this.noLinkCopy) {
          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);
      // Must go second
      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 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]);
      }
    }
  }

  



  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};
  }

  




  this.cacheLinks = function() { // Cache links
    links = new Array();
    var tolt = this.topOfLinkTree();
    var sections = tolt ?
      _find_matching_elements(tolt, this.sectionDetect) :
      [] ;
    var counts_by_keyword={};
    var link_with_keyword_count=0;
    var el;
    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;
    }
  }
  





  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.toLowerCase())}, 400);
    };
    i.onmouseup = function() {
      run_late_but_not_simultaneously(function() {t.sloppyMenuLinkSearch(i.id)}, 200);
    };

    var wrapper;
    if(this.existingClearButtonID) {
      document.getElementById(this.existingClearButtonID).onclick = function() {
        i.value="";
        t.menuLinkSearch("");
        return false;
      };
      this.wipeButton = document.getElementById(this.existingClearButtonID);
      wrapper = i;
    } else {
      this.wipeButton = 
        _mkel("a", {
            onclick: function() {
              i.value="";
              t.menuLinkSearch("");
              return false;
            }
          }, [this.clearLinkCaption], function(e) {e.style.marginLeft = "0.5em"}
        );
      



      wrapper = _mkel("span", "", [i, this.clearAfterLinks ? "" : this.wipeButton], 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 && !this.noLinksExpected) alert("No links found");
  }
} // End of wrapper function

