/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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";

function ChromeTask_ChromeScript() {
  /* eslint-env mozilla/chrome-script */

  "use strict";

  const { Assert: AssertCls } = ChromeUtils.importESModule(
    "resource://testing-common/Assert.sys.mjs"
  );

  addMessageListener("chrome-task:spawn", async function (aData) {
    let id = aData.id;
    let source = aData.runnable || "()=>{}";

    function getStack(aStack) {
      let frames = [];
      for (let frame = aStack; frame; frame = frame.caller) {
        frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
      }
      return frames.join("\n");
    }

    /* eslint-disable no-unused-vars */
    var Assert = new AssertCls((err, message, stack) => {
      sendAsyncMessage("chrome-task:test-result", {
        id,
        condition: !err,
        name: err ? err.message : message,
        stack: getStack(err ? err.stack : stack),
      });
    });

    var ok = Assert.ok.bind(Assert);
    var is = Assert.equal.bind(Assert);
    var isnot = Assert.notEqual.bind(Assert);

    function todo(expr, name) {
      sendAsyncMessage("chrome-task:test-todo", { id, expr, name });
    }

    function todo_is(a, b, name) {
      sendAsyncMessage("chrome-task:test-todo_is", { id, a, b, name });
    }

    function info(name) {
      sendAsyncMessage("chrome-task:test-info", { id, name });
    }
    /* eslint-enable no-unused-vars */

    try {
      let runnablestr = `
        (() => {
          return (${source});
        })();`;

      // eslint-disable-next-line no-eval
      let runnable = eval(runnablestr);
      let result = await runnable.call(this, aData.arg);
      sendAsyncMessage("chrome-task:complete", {
        id,
        result,
      });
    } catch (ex) {
      sendAsyncMessage("chrome-task:complete", {
        id,
        error: ex.toString(),
      });
    }
  });
}

/**
 * This object provides the public module functions.
 */
var ChromeTask = {
  /**
   * the ChromeScript if it has already been loaded.
   */
  _chromeScript: null,

  /**
   * Mapping from message id to associated promise.
   */
  _promises: new Map(),

  /**
   * Incrementing integer to generate unique message id.
   */
  _messageID: 1,

  /**
   * Creates and starts a new task in the chrome process.
   *
   * @param arg A single serializable argument that will be passed to the
   *             task when executed on the content process.
   * @param task
   *        - A generator or function which will be serialized and sent to
   *          the remote browser to be executed. Unlike Task.spawn, this
   *          argument may not be an iterator as it will be serialized and
   *          sent to the remote browser.
   * @return A promise object where you can register completion callbacks to be
   *         called when the task terminates.
   * @resolves With the final returned value of the task if it executes
   *           successfully.
   * @rejects An error message if execution fails.
   */
  spawn: function ChromeTask_spawn(arg, task) {
    // Load the frame script if needed.
    let handle = ChromeTask._chromeScript;
    if (!handle) {
      handle = SpecialPowers.loadChromeScript(ChromeTask_ChromeScript);
      handle.addMessageListener("chrome-task:complete", ChromeTask.onComplete);
      handle.addMessageListener("chrome-task:test-result", ChromeTask.onResult);
      handle.addMessageListener("chrome-task:test-info", ChromeTask.onInfo);
      handle.addMessageListener("chrome-task:test-todo", ChromeTask.onTodo);
      handle.addMessageListener(
        "chrome-task:test-todo_is",
        ChromeTask.onTodoIs
      );
      ChromeTask._chromeScript = handle;
    }

    let deferred = {};
    deferred.promise = new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    });

    let id = ChromeTask._messageID++;
    ChromeTask._promises.set(id, deferred);

    handle.sendAsyncMessage("chrome-task:spawn", {
      id,
      runnable: task.toString(),
      arg,
    });

    return deferred.promise;
  },

  onComplete(aData) {
    let deferred = ChromeTask._promises.get(aData.id);
    ChromeTask._promises.delete(aData.id);

    if (aData.error) {
      deferred.reject(aData.error);
    } else {
      deferred.resolve(aData.result);
    }
  },

  onResult(aData) {
    SimpleTest.record(aData.condition, aData.name);
  },

  onInfo(aData) {
    SimpleTest.info(aData.name);
  },

  onTodo(aData) {
    SimpleTest.todo(aData.expr, aData.name);
  },

  onTodoIs(aData) {
    SimpleTest.todo_is(aData.a, aData.b, aData.name);
  },
};
