/**
 * Split a string, returning only non-empty values.
 *
 * Examples using /[[\]]+/:
 * "" => []
 * "[foo][bar]" => ["foo", "bar"]
 * "[foo bar]" => ["foo bar"]
 */
function splitIgnoreEmpty(bstr, pat) {
  if (!bstr)
    return [];

  var bits = bstr.split(pat);
  var result = [];
  // filter out empty entries
  for (var i = 0; i < bits.length; i++) {
    if (bits[i])
      result.push(bits[i]);
  }
  return result;
}

// helper functions that prove I should have just broken down and used e4x.

/**
 * get Child Named Value.  Basically, find the child with the given tag name and
 *  get the payload which is stored as textContent rather than an attribute or
 *  such.
 */
function cnv(aDataNode, aElemName) {
  var elems = aDataNode.getElementsByTagName(aElemName);
  return elems[0].textContent;
}

/**
 * Get Child Named Attribute
 */
function cna(aDataNode, aElemName, aAttrName) {
  var elems = aDataNode.getElementsByTagName(aElemName);
  return elems[0].getAttribute(aAttrName);
}

/**
 * Get Multiple Child Values as List.
 */
function mcvl(aDataNode, aElemName, aSubElemName) {
  var elems = aDataNode.getElementsByTagName(aElemName);
  var subElems = elems[0].getElementsByTagName(aSubElemName);
  var results = [];
  for (var i = 0; i < subElems.length; i++) {
    results.push(subElems[i].textContent);
  }
  return results;
}

/**
 * The format=search gives us a date of the form: 20090604220233.  After years
 * of reverse engineering, it appears that we can split it up like so:
 * 2009 06 04 22 02 33.  We are still not sure what the numbers mean, but gosh
 * darn it, I will not rest until I know!
 */
function parseUglyDate(d) {
  return new Date(parseInt(d.substr(0, 4)),
                  parseInt(d.substr(4, 6)),
                  parseInt(d.substr(6, 8)),
                  parseInt(d.substr(8, 10)),
                  parseInt(d.substr(10, 12)),
                  parseInt(d.substr(12, 14)));
}

var searchLangCode;

/**
 * Given a path delimited by "/", convert it into a list of all of the ancestor
 *  paths.  We make every result begin with "/" so there is an obvious root
 *  node.
 * We also get rid of the language.
 *
 * Examples:
 *  "Foo" => ["/"]
 *  "Foo/Bar" => ["/", "/Foo"]
 *  "Foo/Bar/Baz" => ["/", "/Foo/Bar"]"
 */
function ancestorize(aPath) {
  var bits = aPath.split(/\//);
  // nuke the language part...
  if (bits[0] == searchLangCode)
    bits.splice(0, 1);
  else if (bits[0].indexOf(":" + searchLangCode) == bits[0].length - 3)
    bits[0] = bits[0].substr(0, bits[0].length-3);
  var result = ["/"];
  var soFar = "";
  for (var i = 0; i < bits.length - 1; i++) {
    if (bits[i]) {
      soFar += "/" + bits[i];
      result.push(soFar);
    }
  }
  return result;

}

/**
 * Break the score into bands so that we can have the sorting logic be able to
 *  show the thing with the most views in that score band.
 */
function stratify(aScore) {
  return Math.floor(aScore * 10) / 10;
}

/**
 * Bin the page into various types that I arbitrarily have decided upon.
 */
function figurePageType(aPage) {
  var root = aPage.path;
  var idxSlash = root.indexOf("/");
  if (idxSlash != -1)
    root = root.substr(0, idxSlash);
  var idxColon = root.indexOf(":");
  if (idxColon == -1)
    return "Real";
  var prefix = root.substr(0, idxColon);
  return prefix;
}

var patNoIncludeDiv = /<div class="noinclude">.*?<\/div>/g;
var patScriptSpans = /<span class="script">.*?<\/span>/g;
var patEmptyPara = /<p> +<\/p>/g;
var patHrefAttr = / href="[^\"]+"/g;
var patTags = /<\/?[^\/>]+\/?>/g;

/**
 * The default summary frequently has UI chrome at the top, which is completely
 *  not helpful.  Cleverly remove it!
 * @param aSummary The contents of the summary element from format=xml; the
 *     HTML-ish stuff in there provides us with useful info, whereas
 *     format=search has stripped all the tags out.
 */
function smartSnippet(aSummary) {
  var snip = aSummary.replace(patNoIncludeDiv, "")
                     .replace(patScriptSpans, "")
                     .replace(patEmptyPara, "")
                     .replace(patHrefAttr, "")
                     .replace(patTags, "");
  // kill an opened but not closed tag due to the substring-blind snippeting
  //  that happened to the summary we are provided.
  var lastLT, lastGT;
  lastLT = snip.lastIndexOf("<");
  lastGT = snip.lastIndexOf(">");
  if (lastLT > lastGT)
    snip = snip.substr(0, lastLT);

  return snip;
}

/**
 * @param xmlResults The format=xml data.
 * @param searchResults The format=search data.
 */
function dekiConverter(xmlResults, searchResults) {
  var data = {
    items: [],
    properties: {
      score: {valueType: "number"},
      wordCount: {valueType: "number"},
      viewCount: {valueType: "number"},
      revCount: {valueType: "number"},
      lastEdit: {valueType: "date"},
    }
  };
  var items = data.items;

  var iChild, xmlNodes, searchNodes, xd, sd, page;
  xmlNodes = xmlResults.childNodes;
  searchNodes = searchResults.childNodes;
  // searchNodes can include things of type "document", whereas xmlNodes cannot.
  //  so if they don't match, we want to try and filter searchNodes
  if (xmlNodes.length != searchNodes.length) {
    var filteredSearchNodes = [searchNodes[0]];
    for (iChild = 1; iChild < searchNodes.length; iChild++) {
      sd = searchNodes[iChild];
      if (cnv(sd, "type") != "document")
        filteredSearchNodes.push(sd);
    }
    searchNodes = filteredSearchNodes;
  }

  if (xmlNodes.length != searchNodes.length)
    throw new Error("The queries changed in a split-second, no go. XML count:" +
                    xmlNodes.length + " search count: " + searchNodes.length);
  // the 0th element is always a "parsedquery" dude
  for (iChild = 1; iChild < xmlNodes.length; iChild++) {
    xd = xmlNodes[iChild];
    sd = searchNodes[iChild];

    page = {};
    items.push(page);

    page.label = cnv(sd, "path");
    page.title = cnv(sd, "title");
    // uri gets this wrong for devmo because it claims http, so just do it
    //  ourselves.
    page.link = website + cnv(sd, "path");
    page.path = cnv(sd, "path");
    page.ancestorPaths = ancestorize(cnv(sd, "path"));
    page.score = parseFloat(cnv(sd, "score"));
    page.wordCount = parseInt(cnv(sd, "wordcount"));
    page.tags = splitIgnoreEmpty($.trim(cnv(sd, "tag")), /\s+/g);
    page.preview = cnv(sd, "preview");

    page.viewCount = cnv(xd, "metric.views");
    page.revCount = parseInt(cna(xd, "revisions", "count"));
    page.linksTo = mcvl(xd, "outbound", "path");
    page.linkedFrom = mcvl(xd, "inbound", "path");

    page.lastEdit = parseUglyDate(cnv(sd, "date.edited"));
    page.lastEditor = cnv(sd, "author");

    // -- added value!
    // compute a decile for the score so we can sub-order by view count
    page.scoreDecile = stratify(page.score);
    page.pageType = figurePageType(page);
    page.smartSnippet = smartSnippet(cnv(xd, "summary"));
    page.noLangPath = page.path.replace(searchLangCode + "/", "", "i")
                               .replace(":" + searchLangCode + "/", "/", "i");
  }

  return data;
}

var pendingSearch = {
  xmlResults: null,
  searchResults: null,
};

function gotSomeSearchResults(data, status) {
  if (this.iAm == "xml")
    pendingSearch.xmlResults = data.documentElement;
  else if (this.iAm == "search")
    pendingSearch.searchResults = data.documentElement;

  if (pendingSearch.xmlResults && pendingSearch.searchResults) {
    database.loadData(dekiConverter(pendingSearch.xmlResults,
                                    pendingSearch.searchResults));
    setupExhibit();
  }
}

/**
 * Kick off concurrent queries to get us both 'xml' and 'search' formatted
 *  results.
 *
 * Only one dekiSearch may be active at a time, because we are lazy and do not
 *  instance things.  Also, there's no way to ever search more than once.
 */
function dekiSearch(aBaseUrl, aQueryString, aLang) {
  var lang = aLang ? aLang : "en";
  // save this to a global for our language removal logic
  searchLangCode = lang;
  var url = aBaseUrl + "@api/deki/site/search";
  var data = "q=" + encodeURI(aQueryString) + "&constraint=language:" + lang;
  // XXX of course, XHR issues.
  url = "/xhr/devmo/search";

  pendingSearch.xmlResults = null;
  pendingSearch.searchResults = null;
  $.ajax({
    type: "GET",
    url: url,
    data: data + "&format=search",
    dataType: "xml",
    success: gotSomeSearchResults,
    iAm: "search",
  });
  $.ajax({
    type: "GET",
    url: url,
    data: data + "&format=xml",
    dataType: "xml",
    success: gotSomeSearchResults,
    iAm: "xml"
  });
}

var website = "https://developer.mozilla.org/";
function pageLoaded() {
  var params = SimileAjax.parseURLParameters();

  window.database = Exhibit.Database.create();

  if (params.qs)
    dekiSearch(website, params.qs);
  else
    $("#manual").show();
}

function setupExhibit() {
  window.exhibit = Exhibit.create();
  window.exhibit.configureFromDOM();
  exhibitLoaded();
}

function exhibitLoaded() {
  $(".exhibit-facet-header").click(
    function() {
      $(this).toggleClass("collapsed").next().toggle("fast");
      return false;
    }).toggleClass("collapsed").next().hide();
  $(".exhibit-facet[defaultExpanded=true]").children()
    .removeClass("collapsed").next().show();
}

