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

// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejections should be fixed.
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]");
thisTestLeaksUncaughtRejectionsAndShouldBeFixed(
  "TypeError: this.transport is null");

/**
 * Tests the SourceMapService updates generated sources when source maps
 * are subsequently found. Also checks when no column is provided, and
 * when tagging an already source mapped location initially.
 */

// Force the old debugger UI since it's directly used (see Bug 1301705)
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
registerCleanupFunction(function* () {
  Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
});

const DEBUGGER_ROOT = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
// Empty page
const PAGE_URL = `${DEBUGGER_ROOT}doc_empty-tab-01.html`;
const JS_URL = `${URL_ROOT}code_binary_search.js`;
const COFFEE_URL = `${URL_ROOT}code_binary_search.coffee`;
const { SourceMapService } = require("devtools/client/framework/source-map-service");
const { serialize } = require("devtools/client/framework/location-store");

add_task(function* () {
  const toolbox = yield openNewTabAndToolbox(PAGE_URL, "jsdebugger");
  const service = new SourceMapService(toolbox.target);
  let aggregator = new Map();

  function onUpdate(e, oldLoc, newLoc) {
    if (oldLoc.line === 6) {
      checkLoc1(oldLoc, newLoc);
    } else if (oldLoc.line === 8) {
      checkLoc2(oldLoc, newLoc);
    } else {
      throw new Error(`Unexpected location update: ${JSON.stringify(oldLoc)}`);
    }
    aggregator.set(serialize(oldLoc), newLoc);
  }

  let loc1 = { url: JS_URL, line: 6 };
  let loc2 = { url: JS_URL, line: 8, column: 3 };

  service.subscribe(loc1, onUpdate);
  service.subscribe(loc2, onUpdate);

  // Inject JS script
  let sourceShown = waitForSourceShown(toolbox.getCurrentPanel(), "code_binary_search");
  yield createScript(JS_URL);
  yield sourceShown;

  yield waitUntil(() => aggregator.size === 2);

  aggregator = Array.from(aggregator.values());

  ok(aggregator.find(i => i.url === COFFEE_URL && i.line === 4), "found first updated location");
  ok(aggregator.find(i => i.url === COFFEE_URL && i.line === 6), "found second updated location");

  yield toolbox.destroy();
  gBrowser.removeCurrentTab();
  finish();
});

function checkLoc1(oldLoc, newLoc) {
  is(oldLoc.line, 6, "Correct line for JS:6");
  is(oldLoc.column, null, "Correct column for JS:6");
  is(oldLoc.url, JS_URL, "Correct url for JS:6");
  is(newLoc.line, 4, "Correct line for JS:6 -> COFFEE");
  is(newLoc.column, 2, "Correct column for JS:6 -> COFFEE -- handles falsy column entries");
  is(newLoc.url, COFFEE_URL, "Correct url for JS:6 -> COFFEE");
}

function checkLoc2(oldLoc, newLoc) {
  is(oldLoc.line, 8, "Correct line for JS:8:3");
  is(oldLoc.column, 3, "Correct column for JS:8:3");
  is(oldLoc.url, JS_URL, "Correct url for JS:8:3");
  is(newLoc.line, 6, "Correct line for JS:8:3 -> COFFEE");
  is(newLoc.column, 10, "Correct column for JS:8:3 -> COFFEE");
  is(newLoc.url, COFFEE_URL, "Correct url for JS:8:3 -> COFFEE");
}

function createScript(url) {
  info(`Creating script: ${url}`);
  let mm = getFrameScript();
  let command = `
    let script = document.createElement("script");
    script.setAttribute("src", "${url}");
    document.body.appendChild(script);
    null;
  `;
  return evalInDebuggee(mm, command);
}

function waitForSourceShown(debuggerPanel, url) {
  let { panelWin } = debuggerPanel;
  let deferred = defer();

  info(`Waiting for source ${url} to be shown in the debugger...`);
  panelWin.on(panelWin.EVENTS.SOURCE_SHOWN, function onSourceShown(_, source) {

    let sourceUrl = source.url || source.generatedUrl;
    if (sourceUrl.includes(url)) {
      panelWin.off(panelWin.EVENTS.SOURCE_SHOWN, onSourceShown);
      info(`Source shown for ${url}`);
      deferred.resolve(source);
    }
  });

  return deferred.promise;
}
