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

/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* globals registerTestActor, getTestActor, Task, openToolboxForTab, gBrowser */

// This file contains functions related to the inspector that are also of interest to
// other test directores as well.

/**
 * Open the toolbox, with the inspector tool visible.
 * @param {String} hostType Optional hostType, as defined in Toolbox.HostType
 * @return a promise that resolves when the inspector is ready
 */
var openInspector = Task.async(function* (hostType) {
  info("Opening the inspector");

  let toolbox = yield openToolboxForTab(gBrowser.selectedTab, "inspector",
                                        hostType);
  let inspector = toolbox.getPanel("inspector");

  if (inspector._updateProgress) {
    info("Need to wait for the inspector to update");
    yield inspector.once("inspector-updated");
  }

  info("Waiting for actor features to be detected");
  yield inspector._detectingActorFeatures;

  yield registerTestActor(toolbox.target.client);
  let testActor = yield getTestActor(toolbox);

  return {toolbox, inspector, testActor};
});

/**
 * Open the toolbox, with the inspector tool visible, and the one of the sidebar
 * tabs selected.
 *
 * @param {String} id
 *        The ID of the sidebar tab to be opened
 * @return a promise that resolves when the inspector is ready and the tab is
 * visible and ready
 */
var openInspectorSidebarTab = Task.async(function* (id) {
  let {toolbox, inspector, testActor} = yield openInspector();

  info("Selecting the " + id + " sidebar");
  inspector.sidebar.select(id);

  return {
    toolbox,
    inspector,
    testActor
  };
});

/**
 * Open the toolbox, with the inspector tool visible, and the rule-view
 * sidebar tab selected.
 *
 * @return a promise that resolves when the inspector is ready and the rule view
 * is visible and ready
 */
function openRuleView() {
  return openInspectorSidebarTab("ruleview").then(data => {
    // Replace the view to use a custom throttle function that can be triggered manually
    // through an additional ".flush()" property.
    data.inspector.ruleview.view.throttle = manualThrottle();

    return {
      toolbox: data.toolbox,
      inspector: data.inspector,
      testActor: data.testActor,
      view: data.inspector.ruleview.view
    };
  });
}

/**
 * Open the toolbox, with the inspector tool visible, and the computed-view
 * sidebar tab selected.
 *
 * @return a promise that resolves when the inspector is ready and the computed
 * view is visible and ready
 */
function openComputedView() {
  return openInspectorSidebarTab("computedview").then(data => {
    return {
      toolbox: data.toolbox,
      inspector: data.inspector,
      testActor: data.testActor,
      view: data.inspector.computedview.computedView
    };
  });
}

/**
 * Select the rule view sidebar tab on an already opened inspector panel.
 *
 * @param {InspectorPanel} inspector
 *        The opened inspector panel
 * @return {CssRuleView} the rule view
 */
function selectRuleView(inspector) {
  inspector.sidebar.select("ruleview");
  return inspector.ruleview.view;
}

/**
 * Select the computed view sidebar tab on an already opened inspector panel.
 *
 * @param {InspectorPanel} inspector
 *        The opened inspector panel
 * @return {CssComputedView} the computed view
 */
function selectComputedView(inspector) {
  inspector.sidebar.select("computedview");
  return inspector.computedview.computedView;
}

/**
 * Get the NodeFront for a node that matches a given css selector, via the
 * protocol.
 * @param {String|NodeFront} selector
 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
 * loaded in the toolbox
 * @return {Promise} Resolves to the NodeFront instance
 */
function getNodeFront(selector, {walker}) {
  if (selector._form) {
    return selector;
  }
  return walker.querySelector(walker.rootNode, selector);
}

/**
 * Set the inspector's current selection to the first match of the given css
 * selector
 * @param {String|NodeFront} selector
 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
 * loaded in the toolbox
 * @param {String} reason Defaults to "test" which instructs the inspector not
 * to highlight the node upon selection
 * @return {Promise} Resolves when the inspector is updated with the new node
 */
var selectNode = Task.async(function* (selector, inspector, reason = "test") {
  info("Selecting the node for '" + selector + "'");
  let nodeFront = yield getNodeFront(selector, inspector);
  let updated = inspector.once("inspector-updated");
  inspector.selection.setNodeFront(nodeFront, reason);
  yield updated;
});

/**
 * Create a throttling function that can be manually "flushed". This is to replace the
 * use of the `throttle` function from `devtools/client/inspector/shared/utils.js`, which
 * has a setTimeout that can cause intermittents.
 * @return {Function} This function has the same function signature as throttle, but
 *                    the property `.flush()` has been added for flushing out any
 *                    throttled calls.
 */
function manualThrottle() {
  let calls = [];

  function throttle(func, wait, scope) {
    return function () {
      let existingCall = calls.find(call => call.func === func);
      if (existingCall) {
        existingCall.args = arguments;
      } else {
        calls.push({ func, wait, scope, args: arguments });
      }
    };
  }

  throttle.flush = function () {
    calls.forEach(({func, scope, args}) => func.apply(scope, args));
    calls = [];
  };

  return throttle;
}