/* 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/. */

"use strict";

const { Ci } = require("chrome");
loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);

var telemetry;
try {
  const Telemetry = require("devtools/client/shared/telemetry");
  telemetry = new Telemetry();
} catch(e) {
  // DevTools Telemetry module only available in Firefox
}

const EventEmitter = require("devtools/shared/event-emitter");
const eventEmitter = new EventEmitter();

const gcli = require("gcli/index");
const l10n = require("gcli/l10n");

const enabledPaintFlashing = new Set();

const isCheckedFor = (tab) =>
  tab ? enabledPaintFlashing.has(getBrowserForTab(tab).outerWindowID) : false;

/**
 * Fire events and telemetry when paintFlashing happens
 */
function onPaintFlashingChanged(target, state) {
  const { flashing, id } = state;

  if (flashing) {
    enabledPaintFlashing.add(id);
  } else {
    enabledPaintFlashing.delete(id);
  }

  eventEmitter.emit("changed", { target: target });
  function fireChange() {
    eventEmitter.emit("changed", { target: target });
  }

  target.off("navigate", fireChange);
  target.once("navigate", fireChange);

  if (!telemetry) {
    return;
  }
  if (flashing) {
    telemetry.toolOpened("paintflashing");
  } else {
    telemetry.toolClosed("paintflashing");
  }
}

/**
 * Alter the paintFlashing state of a window and report on the new value.
 * This works with chrome or content windows.
 *
 * This is a bizarre method that you could argue should be broken up into
 * separate getter and setter functions, however keeping it as one helps
 * to simplify the commands below.
 *
 * @param state {string} One of:
 * - "on" which does window.paintFlashing = true
 * - "off" which does window.paintFlashing = false
 * - "toggle" which does window.paintFlashing = !window.paintFlashing
 * - "query" which does nothing
 * @return The new value of the window.paintFlashing flag
 */
function setPaintFlashing(window, state) {
  const winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindowUtils);

  if (!["on", "off", "toggle", "query"].includes(state)) {
    throw new Error(`Unsupported state: ${state}`);
  }

  if (state === "on") {
    winUtils.paintFlashing = true;
  } else if (state === "off") {
    winUtils.paintFlashing = false;
  } else if (state === "toggle") {
    winUtils.paintFlashing = !winUtils.paintFlashing;
  }

  return winUtils.paintFlashing;
}

exports.items = [
  {
    name: "paintflashing",
    description: l10n.lookup("paintflashingDesc")
  },
  {
    item: "command",
    runAt: "client",
    name: "paintflashing on",
    description: l10n.lookup("paintflashingOnDesc"),
    manual: l10n.lookup("paintflashingManual"),
    params: [{
      group: "options",
      params: [
        {
          type: "boolean",
          name: "chrome",
          get hidden() {
            return gcli.hiddenByChromePref();
          },
          description: l10n.lookup("paintflashingChromeDesc"),
        }
      ]
    }],
    exec: function*(args, context) {
      if (!args.chrome) {
        const output = yield context.updateExec("paintflashing_server --state on");

        onPaintFlashingChanged(context.environment.target, output.data);
      } else {
        setPaintFlashing(context.environment.chromeWindow, "on");
      }
    }
  },
  {
    item: "command",
    runAt: "client",
    name: "paintflashing off",
    description: l10n.lookup("paintflashingOffDesc"),
    manual: l10n.lookup("paintflashingManual"),
    params: [{
      group: "options",
      params: [
        {
          type: "boolean",
          name: "chrome",
          get hidden() {
            return gcli.hiddenByChromePref();
          },
          description: l10n.lookup("paintflashingChromeDesc"),
        }
      ]
    }],
    exec: function*(args, context) {
      if (!args.chrome) {
        const output = yield context.updateExec("paintflashing_server --state off");

        onPaintFlashingChanged(context.environment.target, output.data);
      } else {
        setPaintFlashing(context.environment.chromeWindow, "off");
      }
    }
  },
  {
    item: "command",
    runAt: "client",
    name: "paintflashing toggle",
    hidden: true,
    buttonId: "command-button-paintflashing",
    buttonClass: "command-button command-button-invertable",
    state: {
      isChecked: ({_tab}) => isCheckedFor(_tab),
      onChange: (_, handler) => eventEmitter.on("changed", handler),
      offChange: (_, handler) => eventEmitter.off("changed", handler),
    },
    tooltipText: l10n.lookup("paintflashingTooltip"),
    description: l10n.lookup("paintflashingToggleDesc"),
    manual: l10n.lookup("paintflashingManual"),
    exec: function*(args, context) {
      const output = yield context.updateExec("paintflashing_server --state toggle");

      onPaintFlashingChanged(context.environment.target, output.data);
    }
  },
  {
    item: "command",
    runAt: "server",
    name: "paintflashing_server",
    hidden: true,
    params: [
      {
        name: "state",
        type: {
          name: "selection",
          data: [ "on", "off", "toggle", "query" ]
        }
      },
    ],
    returnType: "paintFlashingState",
    exec: function(args, context) {
      let { window } = context.environment;
      let id = getOuterId(window);
      let flashing = setPaintFlashing(window, args.state);

      return { flashing, id };
    }
  }
];
