/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

const {Cc, Ci, Cu} = require("chrome");
const Services = require("Services");
const defer = require("devtools/shared/defer");

const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");

function handleThreadState(toolbox, event, packet) {
  // Suppress interrupted events by default because the thread is
  // paused/resumed a lot for various actions.
  if (event !== "paused" || packet.why.type !== "interrupted") {
    // TODO: Bug 1225492, we continue emitting events on the target
    // like we used to, but we should emit these only on the
    // threadClient now.
    toolbox.target.emit("thread-" + event);
  }

  if (event === "paused") {
    toolbox.highlightTool("jsdebugger");

    if (packet.why.type === "debuggerStatement" ||
       packet.why.type === "breakpoint" ||
       packet.why.type === "exception") {
      toolbox.raise();
      toolbox.selectTool("jsdebugger");
    }
  } else if (event === "resumed") {
    toolbox.unhighlightTool("jsdebugger");
  }
}

function attachThread(toolbox) {
  let deferred = defer();

  let target = toolbox.target;
  let { form: { chromeDebugger, actor } } = target;

  // Sourcemaps are always turned off when using the new debugger
  // frontend. This is because it does sourcemapping on the
  // client-side, so the server should not do it. It also does not support
  // blackboxing yet.
  let useSourceMaps = false;
  let autoBlackBox = false;
  if(!Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
    useSourceMaps = Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled");
    autoBlackBox = Services.prefs.getBoolPref("devtools.debugger.auto-black-box");
  }
  let threadOptions = { useSourceMaps, autoBlackBox };

  let handleResponse = (res, threadClient) => {
    if (res.error) {
      deferred.reject(new Error("Couldn't attach to thread: " + res.error));
      return;
    }
    threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
    threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));

    if (!threadClient.paused) {
      deferred.reject(
        new Error("Thread in wrong state when starting up, should be paused")
      );
    }

    // These flags need to be set here because the client sends them
    // with the `resume` request. We make sure to do this before
    // resuming to avoid another interrupt. We can't pass it in with
    // `threadOptions` because the resume request will override them.
    threadClient.pauseOnExceptions(
      Services.prefs.getBoolPref("devtools.debugger.pause-on-exceptions"),
      Services.prefs.getBoolPref("devtools.debugger.ignore-caught-exceptions")
    );

    threadClient.resume(res => {
      if (res.error === "wrongOrder") {
        const box = toolbox.getNotificationBox();
        box.appendNotification(
          L10N.getStr("toolbox.resumeOrderWarning"),
          "wrong-resume-order",
          "",
          box.PRIORITY_WARNING_HIGH
        );
      }

      deferred.resolve(threadClient);
    });
  };

  if (target.isTabActor) {
    // Attaching a tab, a browser process, or a WebExtensions add-on.
    target.activeTab.attachThread(threadOptions, handleResponse);
  } else if (target.isAddon) {
    // Attaching a legacy addon.
    target.client.attachAddon(actor, res => {
      target.client.attachThread(res.threadActor, handleResponse);
    });
  }  else {
    // Attaching an old browser debugger or a content process.
    target.client.attachThread(chromeDebugger, handleResponse);
  }

  return deferred.promise;
}

function detachThread(threadClient) {
  threadClient.removeListener("paused");
  threadClient.removeListener("resumed");
}

module.exports = { attachThread, detachThread };
