/* 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 {classes: Cc, interfaces: Ci} = Components;
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
this.EXPORTED_SYMBOLS = ["emulator", "Emulator", "EmulatorCallback"];

this.emulator = {};

/**
 * Determines if command ID is an emulator callback.
 */
this.emulator.isCallback = function(cmdId) {
  return cmdId < 0;
};

/**
 * Represents the connection between Marionette and the emulator it's
 * running on.
 *
 * When injected scripts call the JS routines {@code runEmulatorCmd} or
 * {@code runEmulatorShell}, the second argument to those is a callback
 * which is stored in cbs.  They are later retreived by their unique ID
 * using popCallback.
 *
 * @param {function(Object)} sendFn
 *     Callback function that sends a message to the emulator.
 */
this.Emulator = function(sendFn) {
  this.send = sendFn;
  this.cbs = [];
};

/**
 * Pops a callback off the stack if found.  Otherwise this is a no-op.
 *
 * @param {number} id
 *     Unique ID associated with the callback.
 *
 * @return {?function(Object)}
 *     Callback function that takes an emulator response message as
 *     an argument.
 */
Emulator.prototype.popCallback = function(id) {
  let f, fi;
  for (let i = 0; i < this.cbs.length; ++i) {
    if (this.cbs[i].id == id) {
      f = this.cbs[i];
      fi = i;
    }
  }

  if (!f) {
    return null;
  }

  this.cbs.splice(fi, 1);
  return f;
};

/**
 * Pushes callback on to the stack.
 *
 * @param {function(Object)} cb
 *     Callback function that takes an emulator response message as
 *     an argument.
 */
Emulator.prototype.pushCallback = function(cb) {
  cb.send_ = this.sendFn;
  this.cbs.push(cb);
};

/**
 * Encapsulates a callback to the emulator and provides an execution
 * environment for them.
 *
 * Each callback is assigned a unique identifier, id, that can be used
 * to retrieve them from Emulator's stack using popCallback.
 *
 * The onresult event listener is triggered when a result arrives on
 * the callback.
 *
 * The onerror event listener is triggered when an error occurs during
 * the execution of that callback.
 */
this.EmulatorCallback = function() {
  this.id = uuidGen.generateUUID().toString();
  this.onresult = null;
  this.onerror = null;
  this.send_ = null;
};

EmulatorCallback.prototype.command = function(cmd, cb) {
  this.onresult = cb;
  this.send_({emulator_cmd: cmd, id: this.id});
};

EmulatorCallback.prototype.shell = function(args, cb) {
  this.onresult = cb;
  this.send_({emulator_shell: args, id: this.id});
};

EmulatorCallback.prototype.result = function(msg) {
  if (this.send_ === null) {
    throw new TypeError(
      "EmulatorCallback must be registered with Emulator to fire");
  }

  try {
    if (!this.onresult) {
      return;
    }
    this.onresult(msg.result);
  } catch (e) {
    if (this.onerror) {
      this.onerror(e);
    }
  }
};
