/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
/* import-globals-from ../../framework/test/shared-head.js */

"use strict";

const FRAME_SCRIPT_UTILS_URL =
  "chrome://devtools/content/shared/frame-script-utils.js";

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

// DOM panel actions.
const constants = require("devtools/client/dom/content/constants");

// Uncomment this pref to dump all devtools emitted events to the console.
// Services.prefs.setBoolPref("devtools.dom.enabled", true);

// Enable the DOM panel
Services.prefs.setBoolPref("devtools.dom.enabled", true);

registerCleanupFunction(() => {
  info("finish() was called, cleaning up...");
  Services.prefs.clearUserPref("devtools.dump.emit");
  Services.prefs.clearUserPref("devtools.dom.enabled");
});

/**
 * Add a new test tab in the browser and load the given url.
 * @param {String} url
 *        The url to be loaded in the new tab
 * @return a promise that resolves to the tab object when
 *        the url is loaded
 */
function addTestTab(url) {
  info("Adding a new test tab with URL: '" + url + "'");

  return new Promise(resolve => {
    addTab(url).then(tab => {
      // Load devtools/shared/frame-script-utils.js
      getFrameScript();

      // Select the DOM panel and wait till it's initialized.
      initDOMPanel(tab).then(panel => {
        waitForDispatch(panel, "FETCH_PROPERTIES").then(() => {
          resolve({
            tab: tab,
            browser: tab.linkedBrowser,
            panel: panel
          });
        });
      });
    });
  });
}

/**
 * Open the DOM panel for the given tab.
 *
 * @param {nsIDOMElement} tab
 *        Optional tab element for which you want open the DOM panel.
 *        The default tab is taken from the global variable |tab|.
 * @return a promise that is resolved once the web console is open.
 */
function initDOMPanel(tab) {
  return new Promise(resolve => {
    let target = TargetFactory.forTab(tab || gBrowser.selectedTab);
    gDevTools.showToolbox(target, "dom").then(toolbox => {
      let panel = toolbox.getCurrentPanel();
      resolve(panel);
    });
  });
}

/**
 * Synthesize asynchronous click event (with clean stack trace).
 */
function synthesizeMouseClickSoon(panel, element) {
  return new Promise(resolve => {
    executeSoon(() => {
      EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin);
      resolve();
    });
  });
}

/**
 * Returns tree row with specified label.
 */
function getRowByLabel(panel, text) {
  let doc = panel.panelWin.document;
  let labels = [...doc.querySelectorAll(".treeLabel")];
  let label = labels.find(node => node.textContent == text);
  return label ? label.closest(".treeRow") : null;
}

/**
 * Expands elements with given label and waits till
 * children are received from the backend.
 */
function expandRow(panel, labelText) {
  let row = getRowByLabel(panel, labelText);
  return synthesizeMouseClickSoon(panel, row).then(() => {
    // Wait till children (properties) are fetched
    // from the backend.
    return waitForDispatch(panel, "FETCH_PROPERTIES");
  });
}

function evaluateJSAsync(panel, expression) {
  return new Promise(resolve => {
    panel.target.activeConsole.evaluateJSAsync(expression, res => {
      resolve(res);
    });
  });
}

function refreshPanel(panel) {
  let doc = panel.panelWin.document;
  let button = doc.querySelector(".btn.refresh");
  return synthesizeMouseClickSoon(panel, button).then(() => {
    // Wait till children (properties) are fetched
    // from the backend.
    return waitForDispatch(panel, "FETCH_PROPERTIES");
  });
}

// Redux related API, use from shared location
// as soon as bug 1261076 is fixed.

// Wait until an action of `type` is dispatched. If it's part of an
// async operation, wait until the `status` field is "done" or "error"
function _afterDispatchDone(store, type) {
  return new Promise(resolve => {
    store.dispatch({
      // Normally we would use `services.WAIT_UNTIL`, but use the
      // internal name here so tests aren't forced to always pass it
      // in
      type: "@@service/waitUntil",
      predicate: action => {
        if (action.type === type) {
          return action.status ?
            (action.status === "end" || action.status === "error") :
            true;
        }
        return false;
      },
      run: (dispatch, getState, action) => {
        resolve(action);
      }
    });
  });
}

function waitForDispatch(panel, type, eventRepeat = 1) {
  const store = panel.panelWin.view.mainFrame.store;
  const actionType = constants[type];
  let count = 0;

  return Task.spawn(function* () {
    info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
    while (count < eventRepeat) {
      yield _afterDispatchDone(store, actionType);
      count++;
      info(type + " dispatched " + count + " time(s)");
    }
  });
}
