/* 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/. */

import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "MediaManagerService",
  "@mozilla.org/mediaManagerService;1",
  "nsIMediaManagerService"
);

const STATUS_RECORDING = "recording";
const STATUS_INACTIVE = "inactive";
const TYPE_CAMERA = "camera";
const TYPE_MICROPHONE = "microphone";

export class GeckoViewPermissionProcessChild extends JSProcessActorChild {
  getActor(window) {
    return window.windowGlobalChild.getActor("GeckoViewPermission");
  }

  /* ----------  nsIObserver  ---------- */
  async observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case "getUserMedia:ask-device-permission": {
        await this.sendQuery("AskDevicePermission", aData);
        Services.obs.notifyObservers(
          aSubject,
          "getUserMedia:got-device-permission"
        );
        break;
      }
      case "getUserMedia:request": {
        const { callID } = aSubject;
        const allowedDevices = await this.handleMediaRequest(aSubject);
        Services.obs.notifyObservers(
          allowedDevices,
          allowedDevices
            ? "getUserMedia:response:allow"
            : "getUserMedia:response:deny",
          callID
        );
        break;
      }
      case "PeerConnection:request": {
        Services.obs.notifyObservers(
          null,
          "PeerConnection:response:allow",
          aSubject.callID
        );
        break;
      }
      case "recording-device-events": {
        this.handleRecordingDeviceEvents(aSubject);
        break;
      }
    }
  }

  handleRecordingDeviceEvents(aRequest) {
    aRequest.QueryInterface(Ci.nsIPropertyBag2);
    const contentWindow = aRequest.getProperty("window");
    const devices = [];

    const getStatusString = function (activityStatus) {
      switch (activityStatus) {
        case lazy.MediaManagerService.STATE_CAPTURE_ENABLED:
        case lazy.MediaManagerService.STATE_CAPTURE_DISABLED:
          return STATUS_RECORDING;
        case lazy.MediaManagerService.STATE_NOCAPTURE:
          return STATUS_INACTIVE;
        default:
          throw new Error("Unexpected activityStatus value");
      }
    };

    const hasCamera = {};
    const hasMicrophone = {};
    const screen = {};
    const window = {};
    const browser = {};
    const mediaDevices = {};
    lazy.MediaManagerService.mediaCaptureWindowState(
      contentWindow,
      hasCamera,
      hasMicrophone,
      screen,
      window,
      browser,
      mediaDevices
    );
    var cameraStatus = getStatusString(hasCamera.value);
    var microphoneStatus = getStatusString(hasMicrophone.value);
    if (hasCamera.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
      devices.push({
        type: TYPE_CAMERA,
        status: cameraStatus,
      });
    }
    if (hasMicrophone.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
      devices.push({
        type: TYPE_MICROPHONE,
        status: microphoneStatus,
      });
    }
    this.getActor(contentWindow).mediaRecordingStatusChanged(devices);
  }

  async handleMediaRequest(aRequest) {
    const constraints = aRequest.getConstraints();
    const { devices, windowID } = aRequest;
    const window = Services.wm.getOuterWindowWithId(windowID);
    if (window.closed) {
      return null;
    }

    // Release the request first
    aRequest = undefined;

    const sources = devices.map(device => {
      device = device.QueryInterface(Ci.nsIMediaDevice);
      return {
        type: device.type,
        id: device.rawId,
        rawId: device.rawId,
        name: device.rawName, // unfiltered device name to show to the user
        mediaSource: device.mediaSource,
      };
    });

    if (
      constraints.video &&
      !sources.some(source => source.type === "videoinput")
    ) {
      console.error("Media device error: no video source");
      return null;
    } else if (
      constraints.audio &&
      !sources.some(source => source.type === "audioinput")
    ) {
      console.error("Media device error: no audio source");
      return null;
    }

    const response = await this.getActor(window).getMediaPermission({
      uri: window.document.documentURI,
      video: constraints.video
        ? sources.filter(source => source.type === "videoinput")
        : null,
      audio: constraints.audio
        ? sources.filter(source => source.type === "audioinput")
        : null,
    });

    if (!response) {
      // Rejected.
      return null;
    }

    const allowedDevices = Cc["@mozilla.org/array;1"].createInstance(
      Ci.nsIMutableArray
    );
    if (constraints.video) {
      const video = devices.find(device => response.video === device.rawId);
      if (!video) {
        console.error("Media device error: invalid video id");
        return null;
      }
      await this.getActor(window).addCameraPermission();
      allowedDevices.appendElement(video);
    }
    if (constraints.audio) {
      const audio = devices.find(device => response.audio === device.rawId);
      if (!audio) {
        console.error("Media device error: invalid audio id");
        return null;
      }
      allowedDevices.appendElement(audio);
    }
    return allowedDevices;
  }
}

const { debug, warn } = GeckoViewUtils.initLogging(
  "GeckoViewPermissionProcessChild"
);
