/* import-globals-from ../attributes.js */
/* import-globals-from ../common.js */
/* import-globals-from ../events.js */
/* import-globals-from ../name.js */

// //////////////////////////////////////////////////////////////////////////////
// Name tests described by "markuprules.xml" file.

var gNameRulesFileURL = "markuprules.xml";

var gRuleDoc = null;

// Debuggin stuff.
var gDumpToConsole = false;

/**
 * Start name tests. Run through markup elements and test names for test
 * element (see namerules.xml for details).
 */
function testNames() {
  // enableLogging("tree,stack"); // debugging

  var request = new XMLHttpRequest();
  request.open("get", gNameRulesFileURL, false);
  request.send();

  gRuleDoc = request.responseXML;

  var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
  gTestIterator.iterateMarkups(markupElms);
}

// //////////////////////////////////////////////////////////////////////////////
// Private section.

/**
 * Helper class to interate through name tests.
 */
var gTestIterator = {
  iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms) {
    this.markupElms = aMarkupElms;

    this.iterateNext();
  },

  iterateRules: function gTestIterator_iterateRules(
    aElm,
    aContainer,
    aRuleSetElm,
    aRuleElms,
    aTestID
  ) {
    this.ruleSetElm = aRuleSetElm;
    this.ruleElms = aRuleElms;
    this.elm = aElm;
    this.container = aContainer;
    this.testID = aTestID;

    this.iterateNext();
  },

  iterateNext: function gTestIterator_iterateNext() {
    if (this.markupIdx == -1) {
      this.markupIdx++;
      testNamesForMarkup(this.markupElms[this.markupIdx]);
      return;
    }

    this.ruleIdx++;
    if (this.ruleIdx == this.ruleElms.length) {
      // When test is finished then name is empty and no explict-name.
      var defaultName = this.ruleSetElm.hasAttribute("defaultName")
        ? this.ruleSetElm.getAttribute("defaultName")
        : null;
      testName(
        this.elm,
        defaultName,
        "Default name test (" + gTestIterator.testID + "). "
      );
      testAbsentAttrs(this.elm, { "explicit-name": "true" });

      this.markupIdx++;
      if (this.markupIdx == this.markupElms.length) {
        // disableLogging("tree"); // debugging
        SimpleTest.finish();
        return;
      }

      this.ruleIdx = -1;

      if (gDumpToConsole) {
        dump(
          "\nPend next markup processing. Wait for reorder event on " +
            prettyName(document) +
            "'\n"
        );
      }
      waitForEvent(
        EVENT_REORDER,
        document,
        testNamesForMarkup,
        null,
        this.markupElms[this.markupIdx]
      );

      document.body.removeChild(this.container);
      return;
    }

    testNameForRule(this.elm, this.ruleElms[this.ruleIdx]);
  },

  markupElms: null,
  markupIdx: -1,
  rulesetElm: null,
  ruleElms: null,
  ruleIdx: -1,
  elm: null,
  container: null,
  testID: "",
};

/**
 * Process every 'markup' element and test names for it. Used by testNames
 * function.
 */
function testNamesForMarkup(aMarkupElm) {
  if (gDumpToConsole) {
    dump("\nProcessing markup '" + aMarkupElm.getAttribute("id") + "'\n");
  }

  var div = document.createElement("div");
  div.setAttribute("id", "test");

  var child = aMarkupElm.firstChild;
  while (child) {
    var newChild = document.importNode(child, true);
    div.appendChild(newChild);
    child = child.nextSibling;
  }

  if (gDumpToConsole) {
    dump(
      "\nProcessing markup. Wait for reorder event on " +
        prettyName(document) +
        "'\n"
    );
  }
  waitForEvent(
    EVENT_REORDER,
    document,
    testNamesForMarkupRules,
    null,
    aMarkupElm,
    div
  );

  document.body.appendChild(div);
}

function testNamesForMarkupRules(aMarkupElm, aContainer) {
  var testID = aMarkupElm.getAttribute("id");
  if (gDumpToConsole) {
    dump("\nProcessing markup rules '" + testID + "'\n");
  }

  var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref");
  var elm = evaluateXPath(document, expr, htmlDocResolver)[0];

  var ruleId = aMarkupElm.getAttribute("ruleset");
  var ruleElm = gRuleDoc.querySelector("[id='" + ruleId + "']");
  var ruleElms = getRuleElmsByRulesetId(ruleId);

  var processMarkupRules = gTestIterator.iterateRules.bind(
    gTestIterator,
    elm,
    aContainer,
    ruleElm,
    ruleElms,
    testID
  );

  // Images may be recreated after we append them into subtree. We need to wait
  // in this case. If we are on profiling enabled build then stack tracing
  // works and thus let's log instead. Note, that works if you enabled logging
  // (refer to testNames() function).
  if (isAccessible(elm) || isLogged("stack")) {
    processMarkupRules();
  } else {
    waitForEvent(EVENT_SHOW, elm, processMarkupRules);
  }
}

/**
 * Test name for current rule and current 'markup' element. Used by
 * testNamesForMarkup function.
 */
function testNameForRule(aElm, aRuleElm) {
  if (aRuleElm.hasAttribute("attr")) {
    if (gDumpToConsole) {
      dump(
        "\nProcessing rule { attr: " + aRuleElm.getAttribute("attr") + " }\n"
      );
    }

    testNameForAttrRule(aElm, aRuleElm);
  } else if (aRuleElm.hasAttribute("elm")) {
    if (gDumpToConsole) {
      dump(
        "\nProcessing rule { elm: " +
          aRuleElm.getAttribute("elm") +
          ", elmattr: " +
          aRuleElm.getAttribute("elmattr") +
          " }\n"
      );
    }

    testNameForElmRule(aElm, aRuleElm);
  } else if (aRuleElm.getAttribute("fromsubtree") == "true") {
    if (gDumpToConsole) {
      dump(
        "\nProcessing rule { fromsubtree: " +
          aRuleElm.getAttribute("fromsubtree") +
          " }\n"
      );
    }

    testNameForSubtreeRule(aElm, aRuleElm);
  }
}

function testNameForAttrRule(aElm, aRule) {
  var name = "";

  var attr = aRule.getAttribute("attr");
  var attrValue = aElm.getAttribute(attr);

  var type = aRule.getAttribute("type");
  if (type == "string") {
    name = attrValue;
  } else if (type == "ref" && attrValue) {
    var ids = attrValue.split(/\s+/);
    for (var idx = 0; idx < ids.length; idx++) {
      var labelElm = getNode(ids[idx]);
      if (name != "") {
        name += " ";
      }

      name += labelElm.getAttribute("textequiv");
    }
  }

  var msg = "Attribute '" + attr + "' test (" + gTestIterator.testID + "). ";
  testName(aElm, name, msg);

  if (aRule.getAttribute("explict-name") != "false") {
    testAttrs(aElm, { "explicit-name": "true" }, true);
  } else {
    testAbsentAttrs(aElm, { "explicit-name": "true" });
  }

  waitForEvent(
    EVENT_NAME_CHANGE,
    aElm,
    gTestIterator.iterateNext,
    gTestIterator
  );

  aElm.removeAttribute(attr);
}

function testNameForElmRule(aElm, aRule) {
  var labelElm;

  var tagname = aRule.getAttribute("elm");
  var attrname = aRule.getAttribute("elmattr");
  if (attrname) {
    var filter = {
      acceptNode: function filter_acceptNode(aNode) {
        if (
          aNode.localName == this.mLocalName &&
          aNode.getAttribute(this.mAttrName) == this.mAttrValue
        ) {
          return NodeFilter.FILTER_ACCEPT;
        }

        return NodeFilter.FILTER_SKIP;
      },

      mLocalName: tagname,
      mAttrName: attrname,
      mAttrValue: aElm.getAttribute("id"),
    };

    var treeWalker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_ELEMENT,
      filter
    );
    labelElm = treeWalker.nextNode();
  } else {
    // if attrname is empty then look for the element in subtree.
    labelElm = aElm.getElementsByTagName(tagname)[0];
    if (!labelElm) {
      labelElm = aElm.getElementsByTagName("html:" + tagname)[0];
    }
  }

  if (!labelElm) {
    ok(false, msg + " Failed to find '" + tagname + "' element.");
    gTestIterator.iterateNext();
    return;
  }

  var msg = "Element '" + tagname + "' test (" + gTestIterator.testID + ").";
  testName(aElm, labelElm.getAttribute("textequiv"), msg);
  testAttrs(aElm, { "explicit-name": "true" }, true);

  var parentNode = labelElm.parentNode;

  if (gDumpToConsole) {
    dump(
      "\nProcessed elm rule. Wait for name change event on " +
        prettyName(aElm) +
        "\n"
    );
  }
  waitForEvent(
    EVENT_NAME_CHANGE,
    aElm,
    gTestIterator.iterateNext,
    gTestIterator
  );

  parentNode.removeChild(labelElm);
}

function testNameForSubtreeRule(aElm) {
  var msg = "From subtree test (" + gTestIterator.testID + ").";
  testName(aElm, aElm.getAttribute("textequiv"), msg);
  testAbsentAttrs(aElm, { "explicit-name": "true" });

  if (gDumpToConsole) {
    dump(
      "\nProcessed from subtree rule. Wait for name change event on " +
        prettyName(aElm) +
        "\n"
    );
  }
  waitForEvent(
    EVENT_NAME_CHANGE,
    aElm,
    gTestIterator.iterateNext,
    gTestIterator
  );

  while (aElm.firstChild) {
    aElm.firstChild.remove();
  }
}

/**
 * Return array of 'rule' elements. Used in conjunction with
 * getRuleElmsFromRulesetElm() function.
 */
function getRuleElmsByRulesetId(aRulesetId) {
  var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']";
  var rulesetElm = evaluateXPath(gRuleDoc, expr);
  return getRuleElmsFromRulesetElm(rulesetElm[0]);
}

function getRuleElmsFromRulesetElm(aRulesetElm) {
  var rulesetId = aRulesetElm.getAttribute("ref");
  if (rulesetId) {
    return getRuleElmsByRulesetId(rulesetId);
  }

  var ruleElms = [];

  var child = aRulesetElm.firstChild;
  while (child) {
    if (child.localName == "ruleset") {
      ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child));
    }
    if (child.localName == "rule") {
      ruleElms.push(child);
    }

    child = child.nextSibling;
  }

  return ruleElms;
}

/**
 * Helper method to evaluate xpath expression.
 */
function evaluateXPath(aNode, aExpr, aResolver) {
  var xpe = new XPathEvaluator();

  var resolver = aResolver;
  if (!resolver) {
    var node =
      aNode.ownerDocument == null
        ? aNode.documentElement
        : aNode.ownerDocument.documentElement;
    resolver = xpe.createNSResolver(node);
  }

  var result = xpe.evaluate(aExpr, aNode, resolver, 0, null);
  var found = [];
  var res;
  while ((res = result.iterateNext())) {
    found.push(res);
  }

  return found;
}

function htmlDocResolver(aPrefix) {
  var ns = {
    html: "http://www.w3.org/1999/xhtml",
  };
  return ns[aPrefix] || null;
}
