/* 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";

var {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;

var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
const ServerSocket = CC("@mozilla.org/network/server-socket;1", "nsIServerSocket", "initSpecialConnection");

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

Cu.import("chrome://marionette/content/dispatcher.js");
Cu.import("chrome://marionette/content/driver.js");
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/simpletest.js");

// Bug 1083711: Load transport.js as an SDK module instead of subscript
loader.loadSubScript("resource://devtools/shared/transport/transport.js");

const logger = Log.repository.getLogger("Marionette");

this.EXPORTED_SYMBOLS = ["MarionetteServer"];

const CONTENT_LISTENER_PREF = "marionette.contentListener";
const MANAGE_OFFLINE_STATUS_PREF = "network.gonk.manage-offline-status";

/**
 * Bootstraps Marionette and handles incoming client connections.
 *
 * Once started, it opens a TCP socket sporting the debugger transport
 * protocol on the provided port.  For every new client a Dispatcher is
 * created.
 *
 * @param {number} port
 *     Port for server to listen to.
 * @param {boolean} forceLocal
 *     Listen only to connections from loopback if true.  If false,
 *     accept all connections.
 */
this.MarionetteServer = function (port, forceLocal) {
  this.port = port;
  this.forceLocal = forceLocal;
  this.conns = {};
  this.nextConnId = 0;
  this.alive = false;
  this._acceptConnections = false;
};

/**
 * Function produces a GeckoDriver.
 *
 * Determines application nameto initialise the driver with.
 *
 * @return {GeckoDriver}
 *     A driver instance.
 */
MarionetteServer.prototype.driverFactory = function() {
  let appName = isMulet() ? "B2G" : Services.appinfo.name;
  let bypassOffline = false;

  Preferences.set(CONTENT_LISTENER_PREF, false);

  if (bypassOffline) {
      logger.debug("Bypassing offline status");
      Preferences.set(MANAGE_OFFLINE_STATUS_PREF, false);
      Services.io.manageOfflineStatus = false;
      Services.io.offline = false;
  }

  return new GeckoDriver(appName, this);
};

MarionetteServer.prototype.__defineSetter__("acceptConnections", function (value) {
  if (!value) {
    logger.info("New connections will no longer be accepted");
  } else {
    logger.info("New connections are accepted again");
  }

  this._acceptConnections = value;
});

MarionetteServer.prototype.start = function() {
  if (this.alive) {
    return;
  }
  let flags = Ci.nsIServerSocket.KeepWhenOffline;
  if (this.forceLocal) {
    flags |= Ci.nsIServerSocket.LoopbackOnly;
  }
  this.listener = new ServerSocket(this.port, flags, 1);
  this.listener.asyncListen(this);
  this.alive = true;
  this._acceptConnections = true;
};

MarionetteServer.prototype.stop = function() {
  if (!this.alive) {
    return;
  }
  this.closeListener();
  this.alive = false;
  this._acceptConnections = false;
};

MarionetteServer.prototype.closeListener = function() {
  this.listener.close();
  this.listener = null;
};

MarionetteServer.prototype.onSocketAccepted = function (
    serverSocket, clientSocket) {
  if (!this._acceptConnections) {
    logger.warn("New connections are currently not accepted");
    return;
  }

  let input = clientSocket.openInputStream(0, 0, 0);
  let output = clientSocket.openOutputStream(0, 0, 0);
  let transport = new DebuggerTransport(input, output);
  let connId = "conn" + this.nextConnId++;

  let dispatcher = new Dispatcher(connId, transport, this.driverFactory.bind(this));
  dispatcher.onclose = this.onConnectionClosed.bind(this);
  this.conns[connId] = dispatcher;

  logger.debug(`Accepted connection ${connId} from ${clientSocket.host}:${clientSocket.port}`);
  dispatcher.sayHello();
  transport.ready();
};

MarionetteServer.prototype.onConnectionClosed = function (conn) {
  let id = conn.connId;
  delete this.conns[id];
  logger.debug(`Closed connection ${id}`);
};

function isMulet() {
  return Preferences.get("b2g.is_mulet", false);
}