/* 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/. */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */

"use strict";

// shared-head.js handles imports, constants, and utility functions
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
  this
);

const { DOMHelpers } = require("resource://devtools/shared/dom-helpers.js");
const {
  Hosts,
} = require("resource://devtools/client/framework/toolbox-hosts.js");

const TEST_URI_ROOT = "http://example.com/browser/devtools/client/shared/test/";
const TEST_URI_ROOT_SSL =
  "https://example.com/browser/devtools/client/shared/test/";

const EXAMPLE_URL =
  "chrome://mochitests/content/browser/devtools/client/shared/test/";

function catchFail(func) {
  return function () {
    try {
      return func.apply(null, arguments);
    } catch (ex) {
      ok(false, ex);
      console.error(ex);
      finish();
      throw ex;
    }
  };
}

/**
 * Polls a given function waiting for the given value.
 *
 * @param object options
 *        Options object with the following properties:
 *        - validator
 *        A validator function that should return the expected value. This is
 *        called every few milliseconds to check if the result is the expected
 *        one. When the returned result is the expected one, then the |success|
 *        function is called and polling stops. If |validator| never returns
 *        the expected value, then polling timeouts after several tries and
 *        a failure is recorded - the given |failure| function is invoked.
 *        - success
 *        A function called when the validator function returns the expected
 *        value.
 *        - failure
 *        A function called if the validator function timeouts - fails to return
 *        the expected value in the given time.
 *        - name
 *        Name of test. This is used to generate the success and failure
 *        messages.
 *        - timeout
 *        Timeout for validator function, in milliseconds. Default is 5000 ms.
 *        - value
 *        The expected value. If this option is omitted then the |validator|
 *        function must return a trueish value.
 *        Each of the provided callback functions will receive two arguments:
 *        the |options| object and the last value returned by |validator|.
 */
function waitForValue(options) {
  const start = Date.now();
  const timeout = options.timeout || 5000;
  let lastValue;

  function wait(validatorFn, successFn, failureFn) {
    if (Date.now() - start > timeout) {
      // Log the failure.
      ok(false, "Timed out while waiting for: " + options.name);
      const expected =
        "value" in options ? "'" + options.value + "'" : "a trueish value";
      info("timeout info :: got '" + lastValue + "', expected " + expected);
      failureFn(options, lastValue);
      return;
    }

    lastValue = validatorFn(options, lastValue);
    const successful =
      "value" in options ? lastValue == options.value : lastValue;
    if (successful) {
      ok(true, options.name);
      successFn(options, lastValue);
    } else {
      setTimeout(() => {
        wait(validatorFn, successFn, failureFn);
      }, 100);
    }
  }

  wait(options.validator, options.success, options.failure);
}

function oneTimeObserve(name, callback) {
  return new Promise(resolve => {
    const func = function () {
      Services.obs.removeObserver(func, name);
      if (callback) {
        callback();
      }
      resolve();
    };
    Services.obs.addObserver(func, name);
  });
}

const createHost = async function (
  type = "bottom",
  src = CHROME_URL_ROOT + "dummy.html"
) {
  const host = new Hosts[type](gBrowser.selectedTab);
  const iframe = await host.create();

  await new Promise(resolve => {
    iframe.setAttribute("src", src);
    DOMHelpers.onceDOMReady(iframe.contentWindow, resolve);
  });

  // Popup tests fail very frequently on Linux + webrender because they run
  // too early.
  await waitForPresShell(iframe);

  return { host, win: iframe.contentWindow, doc: iframe.contentDocument };
};

/**
 * Open and close the toolbox in the current browser tab, several times, waiting
 * some amount of time in between.
 * @param {Number} nbOfTimes
 * @param {Number} usageTime in milliseconds
 * @param {String} toolId
 */
async function openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
  for (let i = 0; i < nbOfTimes; i++) {
    info("Opening toolbox " + (i + 1));

    const tab = gBrowser.selectedTab;
    const toolbox = await gDevTools.showToolboxForTab(tab, { toolId });

    // We use a timeout to check the toolbox's active time
    await new Promise(resolve => setTimeout(resolve, usageTime));

    info("Closing toolbox " + (i + 1));
    await toolbox.destroy();
  }
}

/**
 * Waits until a predicate returns true.
 *
 * @param function predicate
 *        Invoked once in a while until it returns true.
 * @param number interval [optional]
 *        How often the predicate is invoked, in milliseconds.
 */
function waitUntil(predicate, interval = 10) {
  if (predicate()) {
    return Promise.resolve(true);
  }
  return new Promise(resolve => {
    setTimeout(function () {
      waitUntil(predicate).then(() => resolve(true));
    }, interval);
  });
}

/**
 * Show the presets list sidebar in the cssfilter widget popup
 * @param {CSSFilterWidget} widget
 * @return {Promise}
 */
function showFilterPopupPresets(widget) {
  const onRender = widget.once("render");
  widget._togglePresets();
  return onRender;
}

/**
 * Show presets list and create a sample preset with the name and value provided
 * @param  {CSSFilterWidget} widget
 * @param  {string} name
 * @param  {string} value
 * @return {Promise}
 */
const showFilterPopupPresetsAndCreatePreset = async function (
  widget,
  name,
  value
) {
  await showFilterPopupPresets(widget);

  let onRender = widget.once("render");
  widget.setCssValue(value);
  await onRender;

  const footer = widget.el.querySelector(".presets-list .footer");
  footer.querySelector("input").value = name;

  onRender = widget.once("render");
  widget._savePreset({
    preventDefault: () => {},
  });

  await onRender;
};