/* 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";

/**
 * Add a new tab in a given browser, pointing to a given URL and automatically
 * register the cleanup function to remove it at the end of the test.
 *
 * @param {Browser} browser
 *     The browser element where the tab should be added.
 * @param {string} url
 *     The URL for the tab.
 * @param {object=} options
 *     Options object to forward to BrowserTestUtils.addTab.
 * @returns {Tab}
 *     The created tab.
 */
function addTab(browser, url, options) {
  const tab = BrowserTestUtils.addTab(browser, url, options);
  registerCleanupFunction(() => browser.removeTab(tab));
  return tab;
}

/**
 * Check if a given navigation is valid and has the expected url.
 *
 * @param {object} navigation
 *     The navigation to validate.
 * @param {string} expectedUrl
 *     The expected url for the navigation.
 */
function assertNavigation(navigation, expectedUrl) {
  ok(!!navigation, "Retrieved a navigation");
  is(navigation.url, expectedUrl, "Navigation has the expected URL");
  is(
    typeof navigation.navigationId,
    "string",
    "Navigation has a string navigationId"
  );
}

/**
 * Check a pair of navigation events have the expected URL, navigation id and
 * navigable id. The pair is expected to be ordered as follows: navigation-started
 * and then navigation-stopped.
 *
 * @param {Array<object>} events
 *     The pair of events to validate.
 * @param {string} url
 *     The expected url for the navigation.
 * @param {string} navigationId
 *     The expected navigation id.
 * @param {string} navigableId
 *     The expected navigable id.
 * @param {boolean} isSameDocument
 *     If the navigation should be a same document navigation.
 */
function assertNavigationEvents(
  events,
  url,
  navigationId,
  navigableId,
  isSameDocument
) {
  const expectedEvents = isSameDocument ? 1 : 2;

  const navigationEvents = events.filter(
    e => e.data.navigationId == navigationId
  );
  is(
    navigationEvents.length,
    expectedEvents,
    `Found ${expectedEvents} events for navigationId ${navigationId}`
  );

  if (isSameDocument) {
    // Check there are no navigation-started/stopped events.
    ok(!navigationEvents.some(e => e.name === "navigation-started"));
    ok(!navigationEvents.some(e => e.name === "navigation-stopped"));

    const locationChanged = navigationEvents.find(
      e => e.name === "location-changed"
    );
    is(locationChanged.name, "location-changed", "event has the expected name");
    is(locationChanged.data.url, url, "event has the expected url");
    is(
      locationChanged.data.navigableId,
      navigableId,
      "event has the expected navigable"
    );
  } else {
    // Check there is no location-changed event.
    ok(!navigationEvents.some(e => e.name === "location-changed"));

    const started = navigationEvents.find(e => e.name === "navigation-started");
    const stopped = navigationEvents.find(e => e.name === "navigation-stopped");

    // Check navigation-started
    is(started.name, "navigation-started", "event has the expected name");
    is(started.data.url, url, "event has the expected url");
    is(
      started.data.navigableId,
      navigableId,
      "event has the expected navigable"
    );

    // Check navigation-stopped
    is(stopped.name, "navigation-stopped", "event has the expected name");
    is(stopped.data.url, url, "event has the expected url");
    is(
      stopped.data.navigableId,
      navigableId,
      "event has the expected navigable"
    );
  }
}

/**
 * Assert that the given navigations all have unique/different ids.
 *
 * @param {Array<object>} navigations
 *     The navigations to validate.
 */
function assertUniqueNavigationIds(...navigations) {
  const ids = navigations.map(navigation => navigation.navigationId);
  is(new Set(ids).size, ids.length, "Navigation ids are all different");
}

/**
 * Create a document-builder based page with an iframe served by a given domain.
 *
 * @param {string} domain
 *     The domain which should serve the page.
 * @returns {string}
 *     The URI for the page.
 */
function createFrame(domain) {
  return createFrameForUri(
    `https://${domain}/document-builder.sjs?html=frame-${domain}`
  );
}

/**
 * Create the markup for an iframe pointing to a given URI.
 *
 * @param {string} uri
 *     The uri for the iframe.
 * @returns {string}
 *     The iframe markup.
 */
function createFrameForUri(uri) {
  return `<iframe src="${encodeURI(uri)}"></iframe>`;
}

/**
 * Create the URL for a test page containing nested iframes
 *
 * @returns {string}
 *     The test page url.
 */
function createTestPageWithFrames() {
  // Create the markup for an example.net frame nested in an example.com frame.
  const NESTED_FRAME_MARKUP = createFrameForUri(
    `https://example.org/document-builder.sjs?html=${createFrame(
      "example.net"
    )}`
  );

  // Combine the nested frame markup created above with an example.com frame.
  const TEST_URI_MARKUP = `${NESTED_FRAME_MARKUP}${createFrame("example.com")}`;

  // Create the test page URI on example.org.
  return `https://example.org/document-builder.sjs?html=${encodeURI(
    TEST_URI_MARKUP
  )}`;
}

/**
 * Load the provided url in an existing browser.
 *
 * @param {Browser} browser
 *     The browser element where the URL should be loaded.
 * @param {string} url
 *     The URL to load.
 * @param {object=} options
 * @param {boolean} options.includeSubFrames
 *     Whether we should monitor load of sub frames. Defaults to false.
 * @param {boolean} options.maybeErrorPage
 *     Whether we might reach an error page or not. Defaults to false.
 * @returns {Promise}
 *     Promise which will resolve when the page is loaded with the expected url.
 */
async function loadURL(browser, url, options = {}) {
  const { includeSubFrames = false, maybeErrorPage = false } = options;
  const loaded = BrowserTestUtils.browserLoaded(
    browser,
    includeSubFrames,
    url,
    maybeErrorPage
  );
  BrowserTestUtils.startLoadingURIString(browser, url);
  return loaded;
}