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

#ifndef mozilla_CamerasChild_h
#define mozilla_CamerasChild_h

#include <utility>

#include "MediaEventSource.h"
#include "mozilla/Mutex.h"
#include "mozilla/camera/PCamerasChild.h"
#include "mozilla/camera/PCamerasParent.h"
#include "nsCOMPtr.h"

// conflicts with #include of scoped_ptr.h
#undef FF
#include "modules/video_capture/video_capture_defines.h"

namespace mozilla {

namespace ipc {
class BackgroundChildImpl;
}  // namespace ipc

namespace camera {

class FrameRelay {
 public:
  virtual void OnCaptureEnded() = 0;
  virtual int DeliverFrame(
      uint8_t* buffer, const mozilla::camera::VideoFrameProperties& props) = 0;
};

struct CapturerElement {
  int id{};
  FrameRelay* callback{};
};

// Forward declaration so we can work with pointers to it.
class CamerasChild;
// Helper class in impl that we friend.
template <class T>
class LockAndDispatch;

static constexpr int kSuccess = 0;
static constexpr int kError = -1;
static constexpr int kIpcError = -2;

// We emulate the sync webrtc.org API with the help of singleton
// CamerasSingleton, which manages a pointer to an IPC object, a thread
// where IPC operations should run on, and a mutex.
// The static function Cameras() will use that Singleton to set up,
// if needed, both the thread and the associated IPC objects and return
// a pointer to the IPC object. Users can then do IPC calls on that object
// after dispatching them to aforementioned thread.

// 2 Threads are involved in this code:
// - the MediaManager thread, which will call the (static, sync API) functions
//   through MediaEngineRemoteVideoSource
// - the Cameras IPC thread, which will be doing our IPC to the parent process
//   via PBackground

// Our main complication is that we emulate a sync API while (having to do)
// async messaging. We dispatch the messages to another thread to send them
// async and hold a Monitor to wait for the result to be asynchronously received
// again. The requirement for async messaging originates on the parent side:
// it's not reasonable to block all PBackground IPC there while waiting for
// something like device enumeration to complete.

class CamerasSingleton {
 public:
  static OffTheBooksMutex& Mutex() { return singleton().mCamerasMutex; }

  static CamerasChild*& Child() {
    Mutex().AssertCurrentThreadOwns();
    return singleton().mCameras;
  }

  static nsCOMPtr<nsIThread>& Thread() {
    Mutex().AssertCurrentThreadOwns();
    return singleton().mCamerasChildThread;
  }
  // The mutex is not held because mCameras is known not to be modified
  // concurrently when this is asserted.
  static void AssertNoChild() { MOZ_ASSERT(!singleton().mCameras); }

 private:
  CamerasSingleton();
  ~CamerasSingleton();

  static CamerasSingleton& singleton() {
    static CamerasSingleton camera;
    return camera;
  }

  // Reinitializing CamerasChild will change the pointers below.
  // We don't want this to happen in the middle of preparing IPC.
  // We will be alive on destruction, so this needs to be off the books.
  mozilla::OffTheBooksMutex mCamerasMutex;

  // This is owned by the IPC code, and the same code controls the lifetime.
  // It will set and clear this pointer as appropriate in setup/teardown.
  // We'd normally make this a WeakPtr but unfortunately the IPC code already
  // uses the WeakPtr mixin in a protected base class of CamerasChild, and in
  // any case the object becomes unusable as soon as IPC is tearing down, which
  // will be before actual destruction.
  CamerasChild* mCameras;
  nsCOMPtr<nsIThread> mCamerasChildThread;
};

// Get a pointer to a CamerasChild object we can use to do IPC with.
// This does everything needed to set up, including starting the IPC
// channel with PBackground, blocking until thats done, and starting the
// thread to do IPC on. This will fail if we're in shutdown. On success
// it will set up the CamerasSingleton.
CamerasChild* GetCamerasChild();

// Shut down the IPC channel and everything associated, like WebRTC.
// This is a static call because the CamerasChild object may not even
// be alive when we're called.
void Shutdown(void);

// Obtain the CamerasChild object (if possible, i.e. not shutting down),
// and maintain a grip on the object for the duration of the call.
template <class MEM_FUN, class... ARGS>
int GetChildAndCall(MEM_FUN&& f, ARGS&&... args) {
  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
  CamerasChild* child = GetCamerasChild();
  if (child) {
    return (child->*f)(std::forward<ARGS>(args)...);
  } else {
    return -1;
  }
}

class CamerasChild final : public PCamerasChild {
  friend class mozilla::ipc::BackgroundChildImpl;
  template <class T>
  friend class mozilla::camera::LockAndDispatch;

 public:
  // We are owned by the PBackground thread only. CamerasSingleton
  // takes a non-owning reference.
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CamerasChild)

  // IPC messages recevied, received on the PBackground thread
  // these are the actual callbacks with data
  mozilla::ipc::IPCResult RecvCaptureEnded(
      nsTArray<int>&& aCaptureIds) override;
  mozilla::ipc::IPCResult RecvDeliverFrame(
      const int& aCaptureId, nsTArray<int>&& aStreamIds,
      mozilla::ipc::Shmem&& aShmem,
      const VideoFrameProperties& aProps) override;

  mozilla::ipc::IPCResult RecvDeviceChange() override;

  // these are response messages to our outgoing requests
  mozilla::ipc::IPCResult RecvReplyNumberOfCaptureDevices(const int&) override;
  mozilla::ipc::IPCResult RecvReplyNumberOfCapabilities(const int&) override;
  mozilla::ipc::IPCResult RecvReplyAllocateCapture(const int&) override;
  mozilla::ipc::IPCResult RecvReplyGetCaptureCapability(
      const VideoCaptureCapability& capability) override;
  mozilla::ipc::IPCResult RecvReplyGetCaptureDevice(
      const nsACString& device_name, const nsACString& device_id,
      const bool& scary) override;
  mozilla::ipc::IPCResult RecvReplyFailure(void) override;
  mozilla::ipc::IPCResult RecvReplySuccess(void) override;
  void ActorDestroy(ActorDestroyReason aWhy) override;

  // the webrtc.org ViECapture calls are mirrored here, but with access
  // to a specific PCameras instance to communicate over. These also
  // run on the MediaManager thread
  int NumberOfCaptureDevices(CaptureEngine aCapEngine);
  int NumberOfCapabilities(CaptureEngine aCapEngine,
                           const char* deviceUniqueIdUTF8);
  int ReleaseCapture(CaptureEngine aCapEngine, const int capture_id);
  int StartCapture(CaptureEngine aCapEngine, const int capture_id,
                   const webrtc::VideoCaptureCapability& capability,
                   const NormalizedConstraints& constraints,
                   const dom::VideoResizeModeEnum& resize_mode,
                   FrameRelay* func);
  int FocusOnSelectedSource(CaptureEngine aCapEngine, const int capture_id);
  int StopCapture(CaptureEngine aCapEngine, const int capture_id);
  // Returns a non-negative capture identifier or -1 on failure.
  int AllocateCapture(CaptureEngine aCapEngine, const char* unique_idUTF8,
                      uint64_t aWindowID);
  int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8,
                           const unsigned int capability_number,
                           webrtc::VideoCaptureCapability* capability);
  int GetCaptureDevice(CaptureEngine aCapEngine, unsigned int list_number,
                       char* device_nameUTF8,
                       const unsigned int device_nameUTF8Length,
                       char* unique_idUTF8,
                       const unsigned int unique_idUTF8Length, bool* scary);
  int EnsureInitialized(CaptureEngine aCapEngine);
  int InvalidateDesktopCaptureDeviceCache(CaptureEngine aCapEngine);

  template <typename This>
  int ConnectDeviceListChangeListener(MediaEventListener* aListener,
                                      AbstractThread* aTarget, This* aThis,
                                      void (This::*aMethod)()) {
    // According to the spec, if the script sets
    // navigator.mediaDevices.ondevicechange and the permission state is
    // "always granted", the User Agent MUST fires a devicechange event when
    // a new media input device is made available, even the script never
    // call getusermedia or enumerateDevices.

    // In order to detect the event, we need to init the camera engine.
    // Currently EnsureInitialized(aCapEngine) is only called when one of
    // CamerasParent api, e.g., RecvNumberOfCaptureDevices(), is called.

    // So here we setup camera engine via EnsureInitialized(aCapEngine)

    EnsureInitialized(CameraEngine);
    *aListener = mDeviceListChangeEvent.Connect(aTarget, aThis, aMethod);
    return IPC_OK();
  }

  FrameRelay* Callback(int capture_id);

  // Remove a FrameRelay callback by capture_id if a CamerasChild exists.
  static void RemoveCallbackIfExists(int capture_id);

  void ClearAllCallbacks();

 private:
  CamerasChild();
  ~CamerasChild();
  // Dispatch a Runnable to the PCamerasParent, by executing it on the
  // decidecated Cameras IPC/PBackground thread.
  enum class DispatchToParentResult : int8_t {
    SUCCESS = 0,
    FAILURE = -1,
    DISCONNECTED = -2,
  };
  DispatchToParentResult DispatchToParent(nsIRunnable* aRunnable,
                                          MonitorAutoLock& aMonitor);
  void AddCallback(int capture_id, FrameRelay* render);
  void RemoveCallback(int capture_id);

  nsTArray<CapturerElement> mCallbacks;
  // Protects the callback arrays
  Mutex mCallbackMutex MOZ_UNANNOTATED;

  bool mIPCIsAlive;

  // Hold to prevent multiple outstanding requests. We don't use
  // request IDs so we only support one at a time. Don't want try
  // to use the webrtc.org API from multiple threads simultanously.
  // The monitor below isn't sufficient for this, as it will drop
  // the lock when Wait-ing for a response, allowing us to send a new
  // request. The Notify on receiving the response will then unblock
  // both waiters and one will be guaranteed to get the wrong result.
  // Take this one before taking mReplyMonitor.
  Mutex mRequestMutex MOZ_UNANNOTATED;
  // Hold to wait for an async response to our calls *and* until the
  // user of LockAndDispatch<> has read the data out. This is done by
  // keeping the LockAndDispatch object alive.
  Monitor mReplyMonitor MOZ_UNANNOTATED;
  // Async response valid?
  bool mReceivedReply;
  // Async responses data contents;
  bool mReplySuccess;
  int mReplyInteger;
  webrtc::VideoCaptureCapability* mReplyCapability = nullptr;
  nsCString mReplyDeviceName;
  nsCString mReplyDeviceID;
  bool mReplyScary;
  MediaEventProducer<void> mDeviceListChangeEvent;
};

}  // namespace camera
}  // namespace mozilla

#endif  // mozilla_CamerasChild_h
