/* 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 {
  CONNECTION_TYPES,
} = require("resource://devtools/client/shared/remote-debugging/constants.js");

/**
 * This class is designed to be a singleton shared by all DevTools to get access to
 * existing clients created for remote debugging.
 */
class RemoteClientManager {
  constructor() {
    this._clients = new Map();
    this._runtimeInfoMap = new Map();
    this._onClientClosed = this._onClientClosed.bind(this);
  }

  /**
   * Store a remote client that is already connected.
   *
   * @param {String} id
   *        Remote runtime id (see devtools/client/aboutdebugging/src/types).
   * @param {String} type
   *        Remote runtime type (see devtools/client/aboutdebugging/src/types).
   * @param {DevToolsClient} client
   * @param {Object} runtimeInfo
   *        See runtimeInfo type from client/aboutdebugging/src/types/runtime.js
   */
  setClient(id, type, client, runtimeInfo) {
    const key = this._getKey(id, type);
    this._clients.set(key, client);
    if (runtimeInfo) {
      this._runtimeInfoMap.set(key, runtimeInfo);
    }
    client.once("closed", this._onClientClosed);
  }

  // See JSDoc for id, type from setClient.
  hasClient(id, type) {
    return this._clients.has(this._getKey(id, type));
  }

  // See JSDoc for id, type from setClient.
  getClient(id, type) {
    return this._clients.get(this._getKey(id, type));
  }

  // See JSDoc for id, type from setClient.
  removeClient(id, type) {
    const key = this._getKey(id, type);
    this._removeClientByKey(key);
  }

  removeAllClients() {
    const keys = [...this._clients.keys()];
    for (const key of keys) {
      this._removeClientByKey(key);
    }
  }

  /**
   * Retrieve a unique, url-safe key based on a runtime id and type.
   */
  getRemoteId(id, type) {
    return encodeURIComponent(this._getKey(id, type));
  }

  /**
   * Retrieve a managed client for a remote id. The remote id should have been generated
   * using getRemoteId.
   */
  getClientByRemoteId(remoteId) {
    const key = this._getKeyByRemoteId(remoteId);
    return this._clients.get(key);
  }

  /**
   * Retrieve the runtime info for a remote id. To display metadata about a runtime, such
   * as name, device name, version... this runtimeInfo should be used rather than calling
   * APIs on the client.
   */
  getRuntimeInfoByRemoteId(remoteId) {
    const key = this._getKeyByRemoteId(remoteId);
    return this._runtimeInfoMap.get(key);
  }

  /**
   * Retrieve a managed client for a remote id. The remote id should have been generated
   * using getRemoteId.
   */
  getConnectionTypeByRemoteId(remoteId) {
    const key = this._getKeyByRemoteId(remoteId);
    for (const type of Object.values(CONNECTION_TYPES)) {
      if (key.endsWith(type)) {
        return type;
      }
    }
    return CONNECTION_TYPES.UNKNOWN;
  }

  _getKey(id, type) {
    return id + "-" + type;
  }

  _getKeyByRemoteId(remoteId) {
    if (!remoteId) {
      // If no remote id was provided, return the key corresponding to the local
      // this-firefox runtime.
      const { THIS_FIREFOX } = CONNECTION_TYPES;
      return this._getKey(THIS_FIREFOX, THIS_FIREFOX);
    }

    return decodeURIComponent(remoteId);
  }

  _removeClientByKey(key) {
    const client = this._clients.get(key);
    if (client) {
      client.off("closed", this._onClientClosed);
      this._clients.delete(key);
      this._runtimeInfoMap.delete(key);
    }
  }

  /**
   * Cleanup all closed clients when a "closed" notification is received from a client.
   */
  _onClientClosed() {
    const closedClientKeys = [...this._clients.keys()].filter(key => {
      return this._clients.get(key)._transportClosed;
    });

    for (const key of closedClientKeys) {
      this._removeClientByKey(key);
    }
  }
}

// Expose a singleton of RemoteClientManager.
module.exports = {
  remoteClientManager: new RemoteClientManager(),
};
