/* 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 { Cc, Ci, Cu, ChromeWorker } = require("chrome");

Cu.import("resource://gre/modules/Services.jsm");

const { EventTarget } = require("sdk/event/target");
const { emit, off } = require("sdk/event/core");
const { Class } = require("sdk/core/heritage");
const Environment = require("sdk/system/environment").env;
const Runtime = require("sdk/system/runtime");
const Self = require("sdk/self");
const URL = require("sdk/url");
const Subprocess = require("subprocess");
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const Prefs = require("sdk/simple-prefs").prefs;

const { rootURI: ROOT_URI } = require('@loader/options');
const PROFILE_URL = ROOT_URI + "profile/";
const BIN_URL = ROOT_URI + "b2g/";

// Log subprocess error and debug messages to the console.  This logs messages
// for all consumers of the API.  We trim the messages because they sometimes
// have trailing newlines.  And note that registerLogHandler actually registers
// an error handler, despite its name.
Subprocess.registerLogHandler(
  function(s) console.error("subprocess: " + s.trim())
);
Subprocess.registerDebugHandler(
  function(s) console.debug("subprocess: " + s.trim())
);

exports.SimulatorProcess = Class({
  extends: EventTarget,
  initialize: function initialize(options) {
    EventTarget.prototype.initialize.call(this, options);

    this.on("stdout", function onStdout(data) console.log(data.trim()));
    this.on("stderr", function onStderr(data) console.error(data.trim()));
  },

  // check if b2g is running
  get isRunning() !!this.process,

  /**
   * Start the process and connect the debugger client.
   */
  run: function() {
    // kill before start if already running
    if (this.process != null) {
      this.process
          .kill()
          .then(this.run.bind(this));
      return;
    }

    // resolve b2g binaries path (raise exception if not found)
    let b2gExecutable = this.b2gExecutable;

    this.once("stdout", function () {
      if (Runtime.OS == "Darwin") {
          console.debug("WORKAROUND run osascript to show b2g-desktop window"+
                        " on Runtime.OS=='Darwin'");
        // Escape double quotes and escape characters for use in AppleScript.
        let path = b2gExecutable.path
          .replace(/\\/g, "\\\\").replace(/\"/g, '\\"');

        Subprocess.call({
          command: "/usr/bin/osascript",
          arguments: ["-e", 'tell application "' + path + '" to activate'],
        });
      }
    });

    let environment;
    if (Runtime.OS == "Linux") {
      environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path];
      if ("DISPLAY" in Environment) {
        environment.push("DISPLAY=" + Environment.DISPLAY);
      }
    }

    // spawn a b2g instance
    this.process = Subprocess.call({
      command: b2gExecutable,
      arguments: this.b2gArguments,
      environment: environment,

      // emit stdout event
      stdout: (function(data) {
        emit(this, "stdout", data);
      }).bind(this),

      // emit stderr event
      stderr: (function(data) {
        emit(this, "stderr", data);
      }).bind(this),

      // on b2g instance exit, reset tracked process, remoteDebuggerPort and
      // shuttingDown flag, then finally emit an exit event
      done: (function(result) {
        console.log(this.b2gFilename + " terminated with " + result.exitCode);
        this.process = null;
        emit(this, "exit", result.exitCode);
      }).bind(this)
    });
  },

  // request a b2g instance kill
  kill: function() {
    let deferred = promise.defer();
    if (this.process) {
      this.once("exit", (exitCode) => {
        this.shuttingDown = false;
        deferred.resolve(exitCode);
      });
      if (!this.shuttingDown) {
        this.shuttingDown = true;
        emit(this, "kill", null);
        this.process.kill();
      }
      return deferred.promise;
    } else {
      return promise.resolve(undefined);
    }
  },

  // compute current b2g filename
  get b2gFilename() {
    return this._executable ? this._executableFilename : "B2G";
  },

  // compute current b2g file handle
  get b2gExecutable() {
    if (this._executable) {
      return this._executable;
    }

    if (Prefs.customRuntime) {
      this._executable = Prefs.customRuntime;
      this._executableFilename = "Custom runtime";
      return this._executable;
    }

    let bin = URL.toFilename(BIN_URL);
    let executables = {
      WINNT: "b2g-bin.exe",
      Darwin: "B2G.app/Contents/MacOS/b2g-bin",
      Linux: "b2g-bin",
    };

    let path = bin;
    path += Runtime.OS == "WINNT" ? "\\" : "/";
    path += executables[Runtime.OS];
    console.log("simulator path: " + path);

    let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    executable.initWithPath(path);

    if (!executable.exists()) {
      // B2G binaries not found
      throw Error("b2g-desktop Executable not found.");
    }

    this._executable = executable;
    this._executableFilename = "b2g-bin";

    return executable;
  },

  // compute b2g CLI arguments
  get b2gArguments() {
    let args = [];

    let profile = Prefs.gaiaProfile || URL.toFilename(PROFILE_URL);
    args.push("-profile", profile);
    console.log("profile", profile);

    // NOTE: push dbgport option on the b2g-desktop commandline
    args.push("-start-debugger-server", "" + this.remoteDebuggerPort);

    // Ignore eventual zombie instances of b2g that are left over
    args.push("-no-remote");

    return args;
  },
});

