/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

function compare10nArgs(elem, expectedValues) {
  let l10nArgs = elem.ownerDocument.l10n.getAttributes(elem).args;
  for (let [name, value] of Object.entries(expectedValues)) {
    if (value !== l10nArgs[name]) {
      info(
        `compare10nArgs, expected ${name}: ${value}, actual: ${l10nArgs[name]}`
      );
      return false;
    }
  }
  return true;
}

async function waitForPageStatusUpdate(elem, expected, message) {
  await TestUtils.waitForCondition(
    () => compare10nArgs(elem, expected),
    message
  );
}

async function waitUntilVisible(elem) {
  await TestUtils.waitForCondition(
    () =>
      BrowserTestUtils.isVisible(elem) && getComputedStyle(elem).opacity == "1",
    "Waiting for element to be visible and have opacity:1"
  );
}

async function waitUntilTransparent(elem) {
  // Note that is_visible considers a fully transparent element "visible"
  await TestUtils.waitForCondition(
    () => getComputedStyle(elem).opacity == "0",
    "Waiting for element to be have opacity:0"
  );
}

async function mouseMoveAndWait(elem) {
  let mouseMovePromise = BrowserTestUtils.waitForEvent(elem, "mousemove");
  EventUtils.synthesizeMouseAtCenter(elem, { type: "mousemove" });
  await mouseMovePromise;
  await TestUtils.waitForTick();
}

add_task(async function testToolbarVisibility() {
  // move the mouse to a known position
  await mouseMoveAndWait(gURLBar);

  await PrintHelper.withTestPage(async helper => {
    await helper.startPrint();

    // On first dialog open, the pagination element is created before printPreview.css
    // is fully applied, causing it to start with default opacity (1). When the CSS
    // loads and sets opacity:0, this triggers a CSS transition with a 500ms delay
    // (see printPreview.css: "transition: opacity 100ms 500ms"). After a previous
    // test, the CSS is cached and applies immediately, so the element starts at
    // opacity:0 with no transition. Wait for the transition to complete.
    await TestUtils.waitForCondition(
      () => getComputedStyle(helper.paginationElem).opacity === "0",
      "Waiting for initial opacity transition to complete"
    );

    let previewStack = document.querySelector(".previewStack");

    // The toolbar has 0 opacity until we hover or focus it
    is(
      getComputedStyle(helper.paginationElem).opacity,
      "0",
      "Initially transparent"
    );

    let visiblePromise = waitUntilVisible(helper.paginationElem);
    helper.paginationElem.shadowRoot.querySelector("#navigateEnd").focus();
    await visiblePromise;
    is(
      getComputedStyle(helper.paginationElem).opacity,
      "1",
      "Opaque with button focused"
    );

    await EventUtils.synthesizeKey("KEY_Tab", {});
    await waitUntilTransparent(helper.paginationElem);
    is(
      getComputedStyle(helper.paginationElem).opacity,
      "0",
      "Returns to transparent"
    );

    visiblePromise = waitUntilVisible(helper.paginationElem);
    info("Waiting for mousemove event, and for the toolbar to become opaque");
    await mouseMoveAndWait(previewStack);
    await visiblePromise;
    is(getComputedStyle(helper.paginationElem).opacity, "1", "Opaque toolbar");

    // put the mouse back where it won't interfere with later tests
    await mouseMoveAndWait(gURLBar);
    await helper.closeDialog();
  });
});

add_task(async function testPreviewSheetCount() {
  await PrintHelper.withTestPage(async helper => {
    await helper.startPrint();

    // We have to wait for the first _updatePrintPreview to get the sheet count
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );

    // then switch to page range 1-1 and verify page count changes
    await helper.dispatchSettingsChange({
      pageRanges: ["1", "1"],
    });
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 1 },
      "Indicates the updated number of sheets"
    );

    await helper.closeDialog();
  }, "longerArticle.html");
});

add_task(async function testPreviewScroll() {
  await PrintHelper.withTestPage(async helper => {
    await helper.startPrint();

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );
    let previewBrowser = helper.currentPrintPreviewBrowser;

    // scroll down the document
    // and verify the indicator is updated correctly
    await SpecialPowers.spawn(previewBrowser, [], async function () {
      const { ContentTaskUtils } = ChromeUtils.importESModule(
        "resource://testing-common/ContentTaskUtils.sys.mjs"
      );
      const EventUtils = ContentTaskUtils.getEventUtils(content);
      content.focus();
      EventUtils.synthesizeKey("VK_PAGE_DOWN", {}, content);
    });
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 3 },
      "Indicator updates on scroll"
    );

    // move focus before closing the dialog
    helper.get("cancel-button").focus();
    await helper.closeDialog();
  }, "longerArticle.html");
});

add_task(async function testPreviewNavigationCommands() {
  await PrintHelper.withTestPage(async helper => {
    await helper.startPrint();

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );

    // click the navigation buttons
    // and verify the indicator is updated correctly
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 3 },
      "Indicator updates on navigation to next"
    );

    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Indicator updates on navigation to previous"
    );

    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateEnd"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 3, sheetCount: 3 },
      "Indicator updates on navigation to end"
    );

    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateHome"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Indicator updates on navigation to start"
    );

    // Test rapid clicks on the navigation buttons
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 3, sheetCount: 3 },
      "2 successive 'next' clicks correctly update the sheet indicator"
    );

    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
      {}
    );
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "2 successive 'previous' clicks correctly update the sheet indicator"
    );

    // move focus before closing the dialog
    helper.get("cancel-button").focus();
    await helper.closeDialog();
  }, "longerArticle.html");
});

add_task(async function testMultiplePreviewNavigation() {
  await PrintHelper.withTestPage(async helper => {
    await helper.startPrint();
    const tab1 = gBrowser.selectedTab;

    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Indicator has the correct initial sheetCount"
    );

    const tab2 = await BrowserTestUtils.openNewForegroundTab(
      gBrowser,
      PrintHelper.defaultTestPageUrl
    );
    let helper2 = new PrintHelper(tab2.linkedBrowser);
    await helper2.startPrint();

    let [previewBrowser1, previewBrowser2] = document.querySelectorAll(
      ".printPreviewBrowser[previewtype='source']"
    );
    ok(previewBrowser1 && previewBrowser2, "There are 2 preview browsers");

    let [toolbar1, toolbar2] = document.querySelectorAll(
      ".printPreviewNavigation"
    );
    ok(toolbar1 && toolbar2, "There are 2 preview navigation toolbars");
    is(
      toolbar1.previewBrowser,
      previewBrowser1,
      "toolbar1 has the correct previewBrowser"
    );
    ok(
      compare10nArgs(helper.paginationSheetIndicator, {
        sheetNum: 1,
        sheetCount: 3,
      }),
      "First toolbar has the correct content"
    );

    is(
      toolbar2.previewBrowser,
      previewBrowser2,
      "toolbar2 has the correct previewBrowser"
    );
    ok(
      compare10nArgs(helper2.paginationSheetIndicator, {
        sheetNum: 1,
        sheetCount: 1,
      }),
      "2nd toolbar has the correct content"
    );

    // Switch back to the first tab and ensure the correct preview navigation is updated when clicked
    await BrowserTestUtils.switchTab(gBrowser, tab1);

    EventUtils.synthesizeMouseAtCenter(
      toolbar1.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 3 },
      "Indicator updates on navigation multiple"
    );

    gBrowser.removeTab(tab2);
  }, "longerArticle.html");
});

add_task(async function testPreviewNavigationSelection() {
  await PrintHelper.withTestPage(async helper => {
    await SpecialPowers.spawn(helper.sourceBrowser, [], async function () {
      let element = content.document.querySelector("#page-2");
      content.window.getSelection().selectAllChildren(element);
    });

    await helper.startPrint();

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );

    // click a navigation button
    // and verify the indicator is updated correctly
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 3 },
      "Indicator updates on navigation next selection"
    );

    await helper.openMoreSettings();
    let printSelect = helper.get("source-version-selection-radio");
    await helper.waitForPreview(() => helper.click(printSelect));

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 2 },
      "Paginator indicates the correct number of sheets"
    );

    // click a navigation button
    // and verify the indicator is updated correctly
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 2 },
      "Indicator updates on navigation next selection 2"
    );

    // move focus before closing the dialog
    helper.get("cancel-button").focus();
    await helper.closeDialog();
  }, "longerArticle.html");
});

add_task(async function testPaginatorAfterSettingsUpdate() {
  const mockPrinterName = "Fake Printer";
  await PrintHelper.withTestPage(async helper => {
    helper.addMockPrinter(mockPrinterName);
    await helper.startPrint();

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );

    // click the navigation buttons
    // and verify the indicator is updated correctly
    EventUtils.synthesizeMouseAtCenter(
      helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
      {}
    );
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 2, sheetCount: 3 },
      "Indicator updates on navigation next after update"
    );

    // Select a new printer
    await helper.dispatchSettingsChange({ printerName: mockPrinterName });
    await waitForPageStatusUpdate(
      helper.paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Indicator updates on navigation next after printer change"
    );
    ok(
      compare10nArgs(helper.paginationSheetIndicator, {
        sheetNum: 1,
        sheetCount: 3,
      }),
      "Sheet indicator has correct value"
    );

    // move focus before closing the dialog
    helper.get("cancel-button").focus();
    await helper.closeDialog();
  }, "longerArticle.html");
});

add_task(async function testTooltips() {
  await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] });
  const mockPrinterName = "Fake Printer";
  await PrintHelper.withTestPage(async helper => {
    helper.addMockPrinter(mockPrinterName);
    await helper.startPrint();

    let paginationElem = document.querySelector(".printPreviewNavigation");
    let paginationSheetIndicator =
      paginationElem.shadowRoot.querySelector("#sheetIndicator");

    // Wait for the first _updatePrintPreview before interacting with the preview
    await waitForPageStatusUpdate(
      paginationSheetIndicator,
      { sheetNum: 1, sheetCount: 3 },
      "Paginator indicates the correct number of sheets"
    );

    let awaitTooltipOpen = new Promise(resolve => {
      window.addEventListener(
        "popupshown",
        function (event) {
          resolve(event.originalTarget);
        },
        { once: true }
      );
    });

    let navigateEnd = paginationElem.shadowRoot.querySelector("#navigateEnd");
    info("Initial mouse move to end navigation button");
    EventUtils.synthesizeMouseAtCenter(navigateEnd, { type: "mousemove" });
    let tooltip = await awaitTooltipOpen;
    is(tooltip.label, navigateEnd.title, "Tooltip shows correct text");
    awaitTooltipOpen = new Promise(resolve => {
      window.addEventListener(
        "popupshown",
        function (event) {
          resolve(event.originalTarget);
        },
        { once: true }
      );
    });

    let navigateNext = paginationElem.shadowRoot.querySelector("#navigateNext");
    let navigateNextRect = navigateNext.getBoundingClientRect();
    info("Initial mouse move to next navigation button");
    EventUtils.synthesizeMouseAtCenter(navigateNext, { type: "mousemove" });
    info("Waiting");
    EventUtils.synthesizeMouse(
      navigateNext,
      navigateNextRect.width / 2 + 5,
      navigateNextRect.height / 2,
      { type: "mousemove" },
      window
    );
    tooltip = await awaitTooltipOpen;
    is(tooltip.label, navigateNext.title, "Tooltip shows correct text");

    // move focus before closing the dialog
    helper.get("cancel-button").focus();
    await helper.awaitAnimationFrame();
    await helper.closeDialog();
  }, "longerArticle.html");
});
