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

"use strict";

let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
// shared-head.js handles imports, constants, and utility functions
let sharedHeadURI = testDir + "../../../framework/test/shared-head.js";
Services.scriptloader.loadSubScript(sharedHeadURI, this);

// Import the GCLI test helper
let gcliHelpersURI = testDir + "../../../commandline/test/helpers.js";
Services.scriptloader.loadSubScript(gcliHelpersURI, this);

flags.testing = true;
Services.prefs.setBoolPref("devtools.responsive.html.enabled", false);

registerCleanupFunction(() => {
  flags.testing = false;
  Services.prefs.clearUserPref("devtools.responsive.html.enabled");
  Services.prefs.clearUserPref("devtools.responsiveUI.currentPreset");
  Services.prefs.clearUserPref("devtools.responsiveUI.customHeight");
  Services.prefs.clearUserPref("devtools.responsiveUI.customWidth");
  Services.prefs.clearUserPref("devtools.responsiveUI.presets");
  Services.prefs.clearUserPref("devtools.responsiveUI.rotate");
});

SimpleTest.requestCompleteLog();

const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});

/**
 * Open the Responsive Design Mode
 * @param {Tab} The browser tab to open it into (defaults to the selected tab).
 * @param {method} The method to use to open the RDM (values: menu, keyboard)
 * @return {rdm, manager} Returns the RUI instance and the manager
 */
var openRDM = Task.async(function* (tab = gBrowser.selectedTab,
                                   method = "menu") {
  let manager = ResponsiveUIManager;

  let opened = once(manager, "on");
  let resized = once(manager, "content-resize");
  if (method == "menu") {
    document.getElementById("menu_responsiveUI").doCommand();
  } else {
    synthesizeKeyFromKeyTag(document.getElementById("key_responsiveUI"));
  }
  yield opened;

  let rdm = manager.getResponsiveUIForTab(tab);
  rdm.transitionsEnabled = false;
  registerCleanupFunction(() => {
    rdm.transitionsEnabled = true;
  });

  // Wait for content to resize.  This is triggered async by the preset menu
  // auto-selecting its default entry once it's in the document.
  yield resized;

  return {rdm, manager};
});

/**
 * Close a responsive mode instance
 * @param {rdm} ResponsiveUI instance for the tab
 */
var closeRDM = Task.async(function* (rdm) {
  let manager = ResponsiveUIManager;
  if (!rdm) {
    rdm = manager.getResponsiveUIForTab(gBrowser.selectedTab);
  }
  let closed = once(manager, "off");
  let resized = once(manager, "content-resize");
  rdm.close();
  yield resized;
  yield closed;
});

/**
 * Open the toolbox, with the inspector tool visible.
 * @return a promise that resolves when the inspector is ready
 */
var openInspector = Task.async(function* () {
  info("Opening the inspector");
  let target = TargetFactory.forTab(gBrowser.selectedTab);

  let inspector, toolbox;

  // Checking if the toolbox and the inspector are already loaded
  // The inspector-updated event should only be waited for if the inspector
  // isn't loaded yet
  toolbox = gDevTools.getToolbox(target);
  if (toolbox) {
    inspector = toolbox.getPanel("inspector");
    if (inspector) {
      info("Toolbox and inspector already open");
      return {
        toolbox: toolbox,
        inspector: inspector
      };
    }
  }

  info("Opening the toolbox");
  toolbox = yield gDevTools.showToolbox(target, "inspector");
  yield waitForToolboxFrameFocus(toolbox);
  inspector = toolbox.getPanel("inspector");

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

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

var closeToolbox = Task.async(function* () {
  let target = TargetFactory.forTab(gBrowser.selectedTab);
  yield gDevTools.closeToolbox(target);
});

/**
 * Wait for the toolbox frame to receive focus after it loads
 * @param {Toolbox} toolbox
 * @return a promise that resolves when focus has been received
 */
function waitForToolboxFrameFocus(toolbox) {
  info("Making sure that the toolbox's frame is focused");
  let def = promise.defer();
  waitForFocus(def.resolve, toolbox.win);
  return def.promise;
}

/**
 * Open the toolbox, with the inspector tool visible, and the sidebar that
 * corresponds to the given id selected
 * @return a promise that resolves when the inspector is ready and the sidebar
 * view is visible and ready
 */
var openInspectorSideBar = Task.async(function* (id) {
  let {toolbox, inspector} = yield openInspector();

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

  return {
    toolbox: toolbox,
    inspector: inspector,
    view: inspector.getPanel(id).view || inspector.getPanel(id).computedView
  };
});

/**
 * Checks whether the inspector's sidebar corresponding to the given id already
 * exists
 * @param {InspectorPanel}
 * @param {String}
 * @return {Boolean}
 */
function hasSideBarTab(inspector, id) {
  return !!inspector.sidebar.getWindowForTab(id);
}

/**
 * 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 openInspectorSideBar("computedview");
}

/**
 * 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 openInspectorSideBar("ruleview");
}

/**
 * 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
 */
var addTab = Task.async(function* (url) {
  info("Adding a new tab with URL: '" + url + "'");

  window.focus();

  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
  let browser = tab.linkedBrowser;

  yield BrowserTestUtils.browserLoaded(browser);
  info("URL '" + url + "' loading complete");

  return tab;
});

/**
 * Waits for the next load to complete in the current browser.
 *
 * @return promise
 */
function waitForDocLoadComplete(aBrowser = gBrowser) {
  let deferred = promise.defer();
  let progressListener = {
    onStateChange: function (webProgress, req, flags, status) {
      let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
                    Ci.nsIWebProgressListener.STATE_STOP;
      info(`Saw state ${flags.toString(16)} and status ${status.toString(16)}`);

      // When a load needs to be retargetted to a new process it is cancelled
      // with NS_BINDING_ABORTED so ignore that case
      if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
        aBrowser.removeProgressListener(progressListener);
        info("Browser loaded");
        deferred.resolve();
      }
    },
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                           Ci.nsISupportsWeakReference])
  };
  aBrowser.addProgressListener(progressListener);
  info("Waiting for browser load");
  return deferred.promise;
}

/**
 * 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;
});

function waitForResizeTo(manager, width, height) {
  return new Promise(resolve => {
    let onResize = (_, data) => {
      if (data.width != width || data.height != height) {
        return;
      }
      manager.off("content-resize", onResize);
      info(`Got content-resize to ${width} x ${height}`);
      resolve();
    };
    info(`Waiting for content-resize to ${width} x ${height}`);
    manager.on("content-resize", onResize);
  });
}

var setPresetIndex = Task.async(function* (rdm, manager, index) {
  info(`Current preset: ${rdm.menulist.selectedIndex}, change to: ${index}`);
  if (rdm.menulist.selectedIndex != index) {
    let resized = once(manager, "content-resize");
    rdm.menulist.selectedIndex = index;
    yield resized;
  }
});

var setSize = Task.async(function* (rdm, manager, width, height) {
  let size = rdm.getSize();
  info(`Current size: ${size.width} x ${size.height}, ` +
       `set to: ${width} x ${height}`);
  if (size.width != width || size.height != height) {
    let resized = waitForResizeTo(manager, width, height);
    rdm.setViewportSize({ width, height });
    yield resized;
  }
});
