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

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let { Promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;

const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test/";

const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";

const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";

const TEST_IMAGE = EXAMPLE_URL + "test-image.png";

// All tests are asynchronous.
waitForExplicitFinish();

// Enable logging for all the relevant tests.
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", true);

registerCleanupFunction(() => {
  info("finish() was called, cleaning up...");
  Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
});

function addTab(aUrl, aWindow) {
  info("Adding tab: " + aUrl);

  let deferred = Promise.defer();
  let targetWindow = aWindow || window;
  let targetBrowser = targetWindow.gBrowser;

  targetWindow.focus();
  let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
  let browser = tab.linkedBrowser;

  browser.addEventListener("load", function onLoad() {
    browser.removeEventListener("load", onLoad, true);
    deferred.resolve(tab);
  }, true);

  return deferred.promise;
}

function removeTab(aTab, aWindow) {
  info("Removing tab.");

  let targetWindow = aWindow || window;
  let targetBrowser = targetWindow.gBrowser;

  targetBrowser.removeTab(aTab);
}

function initNetMonitor(aUrl, aWindow) {
  info("Initializing a network monitor pane.");

  return addTab(aUrl).then((aTab) => {
    info("Net tab added successfully: " + aUrl);

    let deferred = Promise.defer();
    let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject;
    let target = TargetFactory.forTab(aTab);

    gDevTools.showToolbox(target, "netmonitor").then((aToolbox) => {
      info("Netork monitor pane shown successfully.");

      let monitor = aToolbox.getCurrentPanel();
      deferred.resolve([aTab, debuggee, monitor]);
    });

    return deferred.promise;
  });
}

function restartNetMonitor(aMonitor, aNewUrl) {
  info("Restarting the specified network monitor.");

  let deferred = Promise.defer();
  let tab = aMonitor.target.tab;
  let url = aNewUrl || tab.linkedBrowser.contentWindow.wrappedJSObject.location.href;

  aMonitor.once("destroyed", () => initNetMonitor(url).then(deferred.resolve));
  removeTab(tab);

  return deferred.promise;
}

function teardown(aMonitor) {
  info("Destroying the specified network monitor.");

  let deferred = Promise.defer();
  let tab = aMonitor.target.tab;

  aMonitor.once("destroyed", () => executeSoon(deferred.resolve));
  removeTab(tab);

  return deferred.promise;
}

function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
  let deferred = Promise.defer();

  let panel = aMonitor.panelWin;
  let genericEvents = 0;
  let postEvents = 0;

  function onGenericEvent() {
    genericEvents++;
    maybeResolve();
  }

  function onPostEvent() {
    postEvents++;
    maybeResolve();
  }

  function maybeResolve() {
    info("> Network events progress: " +
      genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " +
      postEvents + "/" + (aPostRequests * 2));

    // There are 15 updates which need to be fired for a request to be
    // considered finished. RequestPostData isn't fired for non-POST requests.
    if (genericEvents == (aGetRequests + aPostRequests) * 13 &&
        postEvents == aPostRequests * 2) {

      panel.off("NetMonitor:NetworkEventUpdating:RequestHeaders", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdated:RequestHeaders", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:RequestCookies", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:RequestPostData", onPostEvent);
      panel.off("NetMonitor:NetworkEventUpdated:RequestPostData", onPostEvent);
      panel.off("NetMonitor:NetworkEventUpdated:RequestCookies", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:ResponseHeaders", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdated:ResponseHeaders", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:ResponseCookies", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdated:ResponseCookies", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:ResponseStart", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:ResponseContent", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdated:ResponseContent", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdating:EventTimings", onGenericEvent);
      panel.off("NetMonitor:NetworkEventUpdated:EventTimings", onGenericEvent);

      executeSoon(deferred.resolve);
    }
  }

  panel.on("NetMonitor:NetworkEventUpdating:RequestHeaders", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdated:RequestHeaders", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:RequestCookies", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:RequestPostData", onPostEvent);
  panel.on("NetMonitor:NetworkEventUpdated:RequestPostData", onPostEvent);
  panel.on("NetMonitor:NetworkEventUpdated:RequestCookies", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:ResponseHeaders", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdated:ResponseHeaders", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:ResponseCookies", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdated:ResponseCookies", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:ResponseStart", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:ResponseContent", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdated:ResponseContent", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdating:EventTimings", onGenericEvent);
  panel.on("NetMonitor:NetworkEventUpdated:EventTimings", onGenericEvent);

  return deferred.promise;
}

function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
  info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
  info("> Request: " + aRequestItem.attachment.toSource());

  let requestsMenu = aRequestItem.ownerView;
  let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
  let visibleIndex = requestsMenu.orderedVisibleItems.indexOf(aRequestItem);

  info("Widget index of item: " + widgetIndex);
  info("Visible index of item: " + visibleIndex);

  let { fuzzyUrl, status, statusText, type, fullMimeType, size, time } = aData;
  let { attachment, target } = aRequestItem

  let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
  let name = uri.fileName || "/";
  let query = uri.query;
  let hostPort = uri.hostPort;

  if (fuzzyUrl) {
    ok(attachment.method.startsWith(aMethod), "The attached method is incorrect.");
    ok(attachment.url.startsWith(aUrl), "The attached url is incorrect.");
  } else {
    is(attachment.method, aMethod, "The attached method is incorrect.");
    is(attachment.url, aUrl, "The attached url is incorrect.");
  }

  is(target.querySelector(".requests-menu-method").getAttribute("value"),
    aMethod, "The displayed method is incorrect.");

  if (fuzzyUrl) {
    ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith(
      name + (query ? "?" + query : "")), "The displayed file is incorrect.");
    ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith(
      name + (query ? "?" + query : "")), "The tooltip file is incorrect.");
  } else {
    is(target.querySelector(".requests-menu-file").getAttribute("value"),
      name + (query ? "?" + query : ""), "The displayed file is incorrect.");
    is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"),
      name + (query ? "?" + query : ""), "The tooltip file is incorrect.");
  }

  is(target.querySelector(".requests-menu-domain").getAttribute("value"),
    hostPort, "The displayed domain is incorrect.");
  is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"),
    hostPort, "The tooltip domain is incorrect.");

  if (status !== undefined) {
    let value = target.querySelector(".requests-menu-status").getAttribute("code");
    let tooltip = target.querySelector(".requests-menu-status-and-method").getAttribute("tooltiptext");
    info("Displayed status: " + value);
    info("Tooltip status: " + tooltip);
    is(value, status, "The displayed status is incorrect.");
    is(tooltip, status + " " + statusText, "The tooltip status is incorrect.");
  }
  if (type !== undefined) {
    let value = target.querySelector(".requests-menu-type").getAttribute("value");
    let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext");
    info("Displayed type: " + value);
    info("Tooltip type: " + tooltip);
    is(value, type, "The displayed type is incorrect.");
    is(tooltip, fullMimeType, "The tooltip type is incorrect.");
  }
  if (size !== undefined) {
    let value = target.querySelector(".requests-menu-size").getAttribute("value");
    let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext");
    info("Displayed size: " + value);
    info("Tooltip size: " + tooltip);
    is(value, size, "The displayed size is incorrect.");
    is(tooltip, size, "The tooltip size is incorrect.");
  }
  if (time !== undefined) {
    let value = target.querySelector(".requests-menu-timings-total").getAttribute("value");
    let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext");
    info("Displayed time: " + value);
    info("Tooltip time: " + tooltip);
    ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is incorrect.");
    ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is incorrect.");
  }

  if (visibleIndex != -1) {
    if (visibleIndex % 2 == 0) {
      ok(aRequestItem.target.hasAttribute("even"),
        "Unexpected 'even' attribute for " + aRequestItem.value);
      ok(!aRequestItem.target.hasAttribute("odd"),
        "Unexpected 'odd' attribute for " + aRequestItem.value);
    } else {
      ok(!aRequestItem.target.hasAttribute("even"),
        "Unexpected 'even' attribute for " + aRequestItem.value);
      ok(aRequestItem.target.hasAttribute("odd"),
        "Unexpected 'odd' attribute for " + aRequestItem.value);
    }
  }
}
