/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* exported addPdfStructTreeTest, addPdfOutlineTest */

Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
  this
);
loadScripts(
  { name: "common.js", dir: MOCHITESTS_DIR },
  { name: "promisified-events.js", dir: MOCHITESTS_DIR }
);
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js",
  this
);
const pdfjsLib = ChromeUtils.importESModule("resource://pdf.js/build/pdf.mjs");
pdfjsLib.GlobalWorkerOptions.workerSrc =
  "resource://pdf.js/build/pdf.worker.mjs";

/**
 * The PDF struct tree doesn't contain text content itself. Instead, it uses a
 * node which refers to marked content in the PDF content stream using an id. In
 * the PDF content stream, marked content is delimited by a begin operator
 * specifying the id and an end operator. Rather than making individual tests
 * match up the ids and separately test the struct tree and content items, this
 * function finds marked content items and inserts their strings directly into a
 * .content array on the struct tree node.
 */
function simplifyStructTreeNode(node, contentItems) {
  if (node.type == "content") {
    // Find the associated content items and append their strings to
    // node.content.
    node.content = [];
    let inMarked = false;
    for (const item of contentItems) {
      if (item.type == "beginMarkedContentProps" && item.id == node.id) {
        inMarked = true;
        continue;
      }
      if (!inMarked) {
        continue;
      }
      if (item.str) {
        node.content.push(item.str);
        continue;
      }
      if (item.type == "endMarkedContent") {
        break;
      }
    }
    delete node.type;
    delete node.id;
  }
  if (node.children) {
    for (const child of node.children) {
      simplifyStructTreeNode(child, contentItems);
    }
  }
}

/**
 * PDF outline nodes contain a lot of properties we can't or don't want to test
 * yet. Remove any properties we're not interested in.
 */
function simplifyOutlineNode(node) {
  for (const key in node) {
    if (!["items", "title"].includes(key)) {
      delete node[key];
    }
  }
  for (const child of node.items) {
    simplifyOutlineNode(child);
  }
}

function addPdfTest(testName, doc, task, options = {}) {
  async function pdfTask(browser) {
    const helper = new PrintHelper(browser);
    await helper.startPrint();
    const file = helper.mockFilePicker("accessible_test.pdf");
    await helper.assertPrintToFile(file, () => {
      helper.click(helper.get("print-button"));
    });
    const data = await IOUtils.read(file.path);
    const pdf = await pdfjsLib.getDocument({ data }).promise;
    await task(pdf);
    file.remove(false);
    Services.prefs.clearUserPref("print_printer");
  }
  // Propagate the name of the test to our wrapper function so it shows up in
  // test run output.
  Object.defineProperty(pdfTask, "name", { value: testName });
  addAccessibleTask(doc, pdfTask, options);
}

/**
 * Add a PDF struct tree test.
 *
 * @param testName The name of the test to show in log output.
 * @param doc The markup to convert to PDF.
 * @param pageTrees An array of PDF struct trees for each page of the PDF.
 * @param options Options to pass to addAccessibleTask.
 */
function addPdfStructTreeTest(testName, doc, pageTrees, options = {}) {
  async function task(pdf) {
    for (let p = 0; p < pageTrees.length; ++p) {
      const pageNum = p + 1;
      const page = await pdf.getPage(pageNum);
      const actualTree = await page.getStructTree();
      const contentItems = (
        await page.getTextContent({ includeMarkedContent: true })
      ).items;
      simplifyStructTreeNode(actualTree, contentItems);
      SimpleTest.isDeeply(
        actualTree,
        pageTrees[p],
        `Page ${pageNum} struct tree correct`
      );
    }
  }
  addPdfTest(testName, doc, task, options);
}

/**
 * Add a PDF outline test.
 *
 * @param testName The name of the test to show in log output.
 * @param doc The markup to convert to PDF.
 * @param outline An array of PDF outline node information.
 * @param options Options to pass to addAccessibleTask.
 */
function addPdfOutlineTest(testName, doc, outline, options = {}) {
  async function task(pdf) {
    const actualOutline = await pdf.getOutline();
    for (const node of actualOutline) {
      simplifyOutlineNode(node);
    }
    SimpleTest.isDeeply(actualOutline, outline, "Outline correct");
  }
  addPdfTest(testName, doc, task, options);
}
