/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et ft=cpp : */
/* 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/. */

#include "CamerasParent.h"
#include "MediaEngine.h"
#include "MediaUtils.h"

#include "mozilla/Assertions.h"
#include "mozilla/Unused.h"
#include "mozilla/Services.h"
#include "mozilla/Logging.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Preferences.h"
#include "nsIPermissionManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsNetUtil.h"

#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"

#if defined(_WIN32)
#include <process.h>
#define getpid() _getpid()
#endif

#undef LOG
#undef LOG_ENABLED
mozilla::LazyLogModule gCamerasParentLog("CamerasParent");
#define LOG(args) MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug)

namespace mozilla {
namespace camera {

// 3 threads are involved in this code:
// - the main thread for some setups, and occassionally for video capture setup
//   calls that don't work correctly elsewhere.
// - the IPC thread on which PBackground is running and which receives and
//   sends messages
// - a thread which will execute the actual (possibly slow) camera access
//   called "VideoCapture". On Windows this is a thread with an event loop
//   suitable for UI access.

// InputObserver is owned by CamerasParent, and it has a ref to CamerasParent
void InputObserver::DeviceChange() {
  LOG((__PRETTY_FUNCTION__));
  MOZ_ASSERT(mParent);

  RefPtr<InputObserver> self(this);
  RefPtr<nsIRunnable> ipc_runnable =
    media::NewRunnableFrom([self]() -> nsresult {
      if (self->mParent->IsShuttingDown()) {
        return NS_ERROR_FAILURE;
      }
      Unused << self->mParent->SendDeviceChange();
      return NS_OK;
    });

  nsIThread* thread = mParent->GetBackgroundThread();
  MOZ_ASSERT(thread != nullptr);
  thread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
};

class FrameSizeChangeRunnable : public Runnable {
public:
  FrameSizeChangeRunnable(CamerasParent *aParent, CaptureEngine capEngine,
                          int cap_id, unsigned int aWidth, unsigned int aHeight)
    : mParent(aParent), mCapEngine(capEngine), mCapId(cap_id),
      mWidth(aWidth), mHeight(aHeight) {}

  NS_IMETHOD Run() override {
    if (mParent->IsShuttingDown()) {
      // Communication channel is being torn down
      LOG(("FrameSizeChangeRunnable is active without active Child"));
      mResult = 0;
      return NS_OK;
    }
    if (!mParent->SendFrameSizeChange(mCapEngine, mCapId, mWidth, mHeight)) {
      mResult = -1;
    } else {
      mResult = 0;
    }
    return NS_OK;
  }

  int GetResult() {
    return mResult;
  }

private:
  RefPtr<CamerasParent> mParent;
  CaptureEngine mCapEngine;
  int mCapId;
  unsigned int mWidth;
  unsigned int mHeight;
  int mResult;
};

int
CallbackHelper::FrameSizeChange(unsigned int w, unsigned int h,
                                unsigned int streams)
{
  LOG(("CallbackHelper Video FrameSizeChange: %ux%u", w, h));
  RefPtr<FrameSizeChangeRunnable> runnable =
    new FrameSizeChangeRunnable(mParent, mCapEngine, mCapturerId, w, h);
  MOZ_ASSERT(mParent);
  nsIThread * thread = mParent->GetBackgroundThread();
  MOZ_ASSERT(thread != nullptr);
  thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  return 0;
}

class DeliverFrameRunnable : public Runnable {
public:
  DeliverFrameRunnable(CamerasParent *aParent,
                       CaptureEngine engine,
                       int cap_id,
                       ShmemBuffer buffer,
                       unsigned char* altbuffer,
                       size_t size,
                       uint32_t time_stamp,
                       int64_t ntp_time,
                       int64_t render_time)
    : mParent(aParent), mCapEngine(engine), mCapId(cap_id), mBuffer(Move(buffer)),
      mSize(size), mTimeStamp(time_stamp), mNtpTime(ntp_time),
      mRenderTime(render_time) {
    // No ShmemBuffer (of the right size) was available, so make an
    // extra buffer here.  We have no idea when we are going to run and
    // it will be potentially long after the webrtc frame callback has
    // returned, so the copy needs to be no later than here.
    // We will need to copy this back into a Shmem later on so we prefer
    // using ShmemBuffers to avoid the extra copy.
    if (altbuffer != nullptr) {
      mAlternateBuffer.reset(new unsigned char[size]);
      memcpy(mAlternateBuffer.get(), altbuffer, size);
    }
  };

  NS_IMETHOD Run() override {
    if (mParent->IsShuttingDown()) {
      // Communication channel is being torn down
      mResult = 0;
      return NS_OK;
    }
    if (!mParent->DeliverFrameOverIPC(mCapEngine, mCapId,
                                      Move(mBuffer), mAlternateBuffer.get(),
                                      mSize, mTimeStamp,
                                      mNtpTime, mRenderTime)) {
      mResult = -1;
    } else {
      mResult = 0;
    }
    return NS_OK;
  }

  int GetResult() {
    return mResult;
  }

private:
  RefPtr<CamerasParent> mParent;
  CaptureEngine mCapEngine;
  int mCapId;
  ShmemBuffer mBuffer;
  mozilla::UniquePtr<unsigned char[]> mAlternateBuffer;
  size_t mSize;
  uint32_t mTimeStamp;
  int64_t mNtpTime;
  int64_t mRenderTime;
  int mResult;
};

NS_IMPL_ISUPPORTS(CamerasParent, nsIObserver)

NS_IMETHODIMP
CamerasParent::Observe(nsISupports *aSubject,
                       const char *aTopic,
                       const char16_t *aData)
{
  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID));
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  MOZ_ASSERT(obs);
  obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
  StopVideoCapture();
  return NS_OK;
}

nsresult
CamerasParent::DispatchToVideoCaptureThread(Runnable* event)
{
  // Don't try to dispatch if we're already on the right thread.
  // There's a potential deadlock because the mThreadMonitor is likely
  // to be taken already.
  MOZ_ASSERT(!mVideoCaptureThread ||
             mVideoCaptureThread->thread_id() != PlatformThread::CurrentId());

  MonitorAutoLock lock(mThreadMonitor);

  while(mChildIsAlive && mWebRTCAlive &&
        (!mVideoCaptureThread || !mVideoCaptureThread->IsRunning())) {
    mThreadMonitor.Wait();
  }
  if (!mVideoCaptureThread || !mVideoCaptureThread->IsRunning()) {
    return NS_ERROR_FAILURE;
  }
  RefPtr<Runnable> addrefedEvent = event;
  mVideoCaptureThread->message_loop()->PostTask(addrefedEvent.forget());
  return NS_OK;
}

void
CamerasParent::StopVideoCapture()
{
  LOG((__PRETTY_FUNCTION__));
  // We are called from the main thread (xpcom-shutdown) or
  // from PBackground (when the Actor shuts down).
  // Shut down the WebRTC stack (on the capture thread)
  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self]() -> nsresult {
      MonitorAutoLock lock(self->mThreadMonitor);
      self->CloseEngines();
      self->mThreadMonitor.NotifyAll();
      return NS_OK;
    });
  DebugOnly<nsresult> rv = DispatchToVideoCaptureThread(webrtc_runnable);
#ifdef DEBUG
  // It's ok for the dispatch to fail if the cleanup it has to do
  // has been done already.
  MOZ_ASSERT(NS_SUCCEEDED(rv) || !mWebRTCAlive);
#endif
  // Hold here until the WebRTC thread is gone. We need to dispatch
  // the thread deletion *now*, or there will be no more possibility
  // to get to the main thread.
  MonitorAutoLock lock(mThreadMonitor);
  while (mWebRTCAlive) {
    mThreadMonitor.Wait();
  }
  // After closing the WebRTC stack, clean up the
  // VideoCapture thread.
  if (self->mVideoCaptureThread) {
    base::Thread *thread = self->mVideoCaptureThread;
    self->mVideoCaptureThread = nullptr;
    RefPtr<Runnable> threadShutdown =
      media::NewRunnableFrom([thread]() -> nsresult {
        if (thread->IsRunning()) {
          thread->Stop();
        }
        delete thread;
        return NS_OK;
      });
    if (NS_FAILED(NS_DispatchToMainThread(threadShutdown))) {
      LOG(("Could not dispatch VideoCaptureThread destruction"));
    }
  }
}

int
CamerasParent::DeliverFrameOverIPC(CaptureEngine cap_engine,
                                   int cap_id,
                                   ShmemBuffer buffer,
                                   unsigned char* altbuffer,
                                   size_t size,
                                   uint32_t time_stamp,
                                   int64_t ntp_time,
                                   int64_t render_time)
{
  // No ShmemBuffers were available, so construct one now of the right size
  // and copy into it. That is an extra copy, but we expect this to be
  // the exceptional case, because we just assured the next call *will* have a
  // buffer of the right size.
  if (altbuffer != nullptr) {
    // Get a shared memory buffer from the pool, at least size big
    ShmemBuffer shMemBuff = mShmemPool.Get(this, size);

    if (!shMemBuff.Valid()) {
      LOG(("No usable Video shmem in DeliverFrame (out of buffers?)"));
      // We can skip this frame if we run out of buffers, it's not a real error.
      return 0;
    }

    // get() and Size() check for proper alignment of the segment
    memcpy(shMemBuff.GetBytes(), altbuffer, size);

    if (!SendDeliverFrame(cap_engine, cap_id,
                          shMemBuff.Get(), size,
                          time_stamp, ntp_time, render_time)) {
      return -1;
    }
  } else {
    MOZ_ASSERT(buffer.Valid());
    // ShmemBuffer was available, we're all good. A single copy happened
    // in the original webrtc callback.
    if (!SendDeliverFrame(cap_engine, cap_id,
                          buffer.Get(), size,
                          time_stamp, ntp_time, render_time)) {
      return -1;
    }
  }

  return 0;
}

ShmemBuffer
CamerasParent::GetBuffer(size_t aSize)
{
  return mShmemPool.GetIfAvailable(aSize);
}

int
CallbackHelper::DeliverFrame(unsigned char* buffer,
                             size_t size,
                             uint32_t time_stamp,
                             int64_t ntp_time,
                             int64_t render_time,
                             void *handle)
{
  // Get a shared memory buffer to copy the frame data into
  ShmemBuffer shMemBuffer = mParent->GetBuffer(size);
  if (!shMemBuffer.Valid()) {
    // Either we ran out of buffers or they're not the right size yet
    LOG(("Correctly sized Video shmem not available in DeliverFrame"));
    // We will do the copy into a(n extra) temporary buffer inside
    // the DeliverFrameRunnable constructor.
  } else {
    // Shared memory buffers of the right size are available, do the copy here.
    memcpy(shMemBuffer.GetBytes(), buffer, size);
    // Mark the original buffer as cleared.
    buffer = nullptr;
  }
  RefPtr<DeliverFrameRunnable> runnable =
    new DeliverFrameRunnable(mParent, mCapEngine, mCapturerId,
                             Move(shMemBuffer), buffer, size, time_stamp,
                             ntp_time, render_time);
  MOZ_ASSERT(mParent);
  nsIThread* thread = mParent->GetBackgroundThread();
  MOZ_ASSERT(thread != nullptr);
  thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  return 0;
}
// XXX!!! FIX THIS -- we should move to pure DeliverI420Frame
int
CallbackHelper::DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame)
{
  return DeliverFrame(const_cast<uint8_t*>(webrtc_frame.buffer(webrtc::kYPlane)),
                      CalcBufferSize(webrtc::kI420, webrtc_frame.width(), webrtc_frame.height()),
                      webrtc_frame.timestamp(),
                      webrtc_frame.ntp_time_ms(),
                      webrtc_frame.render_time_ms(),
                      (void*) webrtc_frame.native_handle());
}

bool
CamerasParent::RecvReleaseFrame(mozilla::ipc::Shmem&& s) {
  mShmemPool.Put(ShmemBuffer(s));
  return true;
}

bool
CamerasParent::SetupEngine(CaptureEngine aCapEngine)
{
  MOZ_ASSERT(mVideoCaptureThread->thread_id() == PlatformThread::CurrentId());
  EngineHelper *helper = &mEngines[aCapEngine];

  // Already initialized
  if (helper->mEngine) {
    return true;
  }

  webrtc::CaptureDeviceInfo *captureDeviceInfo = nullptr;

  switch (aCapEngine) {
  case ScreenEngine:
    captureDeviceInfo =
      new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Screen);
    break;
  case BrowserEngine:
    captureDeviceInfo =
      new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Browser);
    break;
  case WinEngine:
    captureDeviceInfo =
      new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Window);
    break;
  case AppEngine:
    captureDeviceInfo =
      new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Application);
    break;
  case CameraEngine:
    captureDeviceInfo =
      new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Camera);
    break;
  default:
    LOG(("Invalid webrtc Video engine"));
    MOZ_CRASH();
    break;
  }

  helper->mConfig.Set<webrtc::CaptureDeviceInfo>(captureDeviceInfo);
  helper->mEngine = webrtc::VideoEngine::Create(helper->mConfig);

  if (!helper->mEngine) {
    LOG(("VideoEngine::Create failed"));
    return false;
  }

  helper->mPtrViEBase = webrtc::ViEBase::GetInterface(helper->mEngine);
  if (!helper->mPtrViEBase) {
    LOG(("ViEBase::GetInterface failed"));
    return false;
  }

  if (helper->mPtrViEBase->Init() < 0) {
    LOG(("ViEBase::Init failed"));
    return false;
  }

  helper->mPtrViECapture = webrtc::ViECapture::GetInterface(helper->mEngine);
  if (!helper->mPtrViECapture) {
    LOG(("ViECapture::GetInterface failed"));
    return false;
  }

  RefPtr<InputObserver>* observer = mObservers.AppendElement(new InputObserver(this));

#ifdef DEBUG
  MOZ_ASSERT(0 == helper->mPtrViECapture->RegisterInputObserver(observer->get()));
#else
  helper->mPtrViECapture->RegisterInputObserver(observer->get());
#endif

  helper->mPtrViERender = webrtc::ViERender::GetInterface(helper->mEngine);
  if (!helper->mPtrViERender) {
    LOG(("ViERender::GetInterface failed"));
    return false;
  }

  return true;
}

void
CamerasParent::CloseEngines()
{
  LOG((__PRETTY_FUNCTION__));
  if (!mWebRTCAlive) {
    return;
  }
  MOZ_ASSERT(mVideoCaptureThread->thread_id() == PlatformThread::CurrentId());

  // Stop the callers
  while (mCallbacks.Length()) {
    auto capEngine = mCallbacks[0]->mCapEngine;
    auto capNum = mCallbacks[0]->mCapturerId;
    LOG(("Forcing shutdown of engine %d, capturer %d", capEngine, capNum));
    StopCapture(capEngine, capNum);
    Unused << ReleaseCaptureDevice(capEngine, capNum);
  }

  for (int i = 0; i < CaptureEngine::MaxEngine; i++) {
    if (mEngines[i].mEngineIsRunning) {
      LOG(("Being closed down while engine %d is running!", i));
    }
    if (mEngines[i].mPtrViERender) {
      mEngines[i].mPtrViERender->Release();
      mEngines[i].mPtrViERender = nullptr;
    }
    if (mEngines[i].mPtrViECapture) {
#ifdef DEBUG
      MOZ_ASSERT(0 == mEngines[i].mPtrViECapture->DeregisterInputObserver());
#else
      mEngines[i].mPtrViECapture->DeregisterInputObserver();
#endif

      mEngines[i].mPtrViECapture->Release();
        mEngines[i].mPtrViECapture = nullptr;
    }
    if(mEngines[i].mPtrViEBase) {
      mEngines[i].mPtrViEBase->Release();
      mEngines[i].mPtrViEBase = nullptr;
    }
    if (mEngines[i].mEngine) {
      mEngines[i].mEngine->SetTraceCallback(nullptr);
      webrtc::VideoEngine::Delete(mEngines[i].mEngine);
      mEngines[i].mEngine = nullptr;
    }
  }

  mObservers.Clear();

  mWebRTCAlive = false;
}

bool
CamerasParent::EnsureInitialized(int aEngine)
{
  LOG((__PRETTY_FUNCTION__));
  // We're shutting down, don't try to do new WebRTC ops.
  if (!mWebRTCAlive) {
    return false;
  }
  CaptureEngine capEngine = static_cast<CaptureEngine>(aEngine);
  if (!SetupEngine(capEngine)) {
    LOG(("CamerasParent failed to initialize engine"));
    return false;
  }

  return true;
}

// Dispatch the runnable to do the camera operation on the
// specific Cameras thread, preventing us from blocking, and
// chain a runnable to send back the result on the IPC thread.
// It would be nice to get rid of the code duplication here,
// perhaps via Promises.
bool
CamerasParent::RecvNumberOfCaptureDevices(const CaptureEngine& aCapEngine)
{
  LOG((__PRETTY_FUNCTION__));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine]() -> nsresult {
      int num = -1;
      if (self->EnsureInitialized(aCapEngine)) {
        num = self->mEngines[aCapEngine].mPtrViECapture->NumberOfCaptureDevices();
      }
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, num]() -> nsresult {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          if (num < 0) {
            LOG(("RecvNumberOfCaptureDevices couldn't find devices"));
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          } else {
            LOG(("RecvNumberOfCaptureDevices: %d", num));
            Unused << self->SendReplyNumberOfCaptureDevices(num);
            return NS_OK;
          }
        });
        self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

bool
CamerasParent::RecvEnsureInitialized(const CaptureEngine& aCapEngine)
{
  LOG((__PRETTY_FUNCTION__));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine]() -> nsresult {
      bool result = self->EnsureInitialized(aCapEngine);

      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, result]() -> nsresult {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          if (!result) {
            LOG(("RecvEnsureInitialized failed"));
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          } else {
            LOG(("RecvEnsureInitialized succeeded"));
            Unused << self->SendReplySuccess();
            return NS_OK;
          }
        });
        self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

bool
CamerasParent::RecvNumberOfCapabilities(const CaptureEngine& aCapEngine,
                                        const nsCString& unique_id)
{
  LOG((__PRETTY_FUNCTION__));
  LOG(("Getting caps for %s", unique_id.get()));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, unique_id, aCapEngine]() -> nsresult {
      int num = -1;
      if (self->EnsureInitialized(aCapEngine)) {
        num =
          self->mEngines[aCapEngine].mPtrViECapture->NumberOfCapabilities(
            unique_id.get(),
            MediaEngineSource::kMaxUniqueIdLength);
      }
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, num]() -> nsresult {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          if (num < 0) {
            LOG(("RecvNumberOfCapabilities couldn't find capabilities"));
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          } else {
            LOG(("RecvNumberOfCapabilities: %d", num));
          }
          Unused << self->SendReplyNumberOfCapabilities(num);
          return NS_OK;
        });
      self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

bool
CamerasParent::RecvGetCaptureCapability(const CaptureEngine& aCapEngine,
                                        const nsCString& unique_id,
                                        const int& num)
{
  LOG((__PRETTY_FUNCTION__));
  LOG(("RecvGetCaptureCapability: %s %d", unique_id.get(), num));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, unique_id, aCapEngine, num]() -> nsresult {
      webrtc::CaptureCapability webrtcCaps;
      int error = -1;
      if (self->EnsureInitialized(aCapEngine)) {
        error = self->mEngines[aCapEngine].mPtrViECapture->GetCaptureCapability(
          unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, num, webrtcCaps);
      }
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, webrtcCaps, error]() -> nsresult {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          CaptureCapability capCap(webrtcCaps.width,
                                   webrtcCaps.height,
                                   webrtcCaps.maxFPS,
                                   webrtcCaps.expectedCaptureDelay,
                                   webrtcCaps.rawType,
                                   webrtcCaps.codecType,
                                   webrtcCaps.interlaced);
          LOG(("Capability: %u %u %u %u %d %d",
               webrtcCaps.width,
               webrtcCaps.height,
               webrtcCaps.maxFPS,
               webrtcCaps.expectedCaptureDelay,
               webrtcCaps.rawType,
               webrtcCaps.codecType));
          if (error) {
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          }
          Unused << self->SendReplyGetCaptureCapability(capCap);
          return NS_OK;
        });
      self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

bool
CamerasParent::RecvGetCaptureDevice(const CaptureEngine& aCapEngine,
                                    const int& aListNumber)
{
  LOG((__PRETTY_FUNCTION__));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine, aListNumber]() -> nsresult {
      char deviceName[MediaEngineSource::kMaxDeviceNameLength];
      char deviceUniqueId[MediaEngineSource::kMaxUniqueIdLength];
      nsCString name;
      nsCString uniqueId;
      int devicePid = 0;
      int error = -1;
      if (self->EnsureInitialized(aCapEngine)) {
          error = self->mEngines[aCapEngine].mPtrViECapture->GetCaptureDevice(aListNumber,
                                                                              deviceName,
                                                                              sizeof(deviceName),
                                                                              deviceUniqueId,
                                                                              sizeof(deviceUniqueId),
                                                                              &devicePid);
      }
      if (!error) {
        name.Assign(deviceName);
        uniqueId.Assign(deviceUniqueId);
      }
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, error, name, uniqueId, devicePid]() {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          if (error) {
            LOG(("GetCaptureDevice failed: %d", error));
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          }
          bool scary = (devicePid == getpid());

          LOG(("Returning %s name %s id (pid = %d)%s", name.get(),
               uniqueId.get(), devicePid, (scary? " (scary)" : "")));
          Unused << self->SendReplyGetCaptureDevice(name, uniqueId, scary);
          return NS_OK;
        });
      self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

static nsresult
GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
{
  nsAutoCString originNoSuffix;
  mozilla::PrincipalOriginAttributes attrs;
  if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
  principal.forget(aPrincipal);
  return NS_OK;
}

// Find out whether the given origin has permission to use the
// camera. If the permission is not persistent, we'll make it
// a one-shot by removing the (session) permission.
static bool
HasCameraPermission(const nsCString& aOrigin)
{
  // Name used with nsIPermissionManager
  static const char* cameraPermission = "MediaManagerVideo";
  bool allowed = false;
  nsresult rv;
  nsCOMPtr<nsIPermissionManager> mgr =
    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIIOService> ioServ(do_GetIOService());
    nsCOMPtr<nsIURI> uri;
    rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri));
    if (NS_SUCCEEDED(rv)) {
      // Permanent permissions are only retrievable via principal, not uri
      nsCOMPtr<nsIPrincipal> principal;
      rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
      if (NS_SUCCEEDED(rv)) {
        uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
        rv = mgr->TestExactPermissionFromPrincipal(principal,
                                                   cameraPermission,
                                                   &video);
        if (NS_SUCCEEDED(rv)) {
          allowed = (video == nsIPermissionManager::ALLOW_ACTION);
        }
        // Session permissions are removed after one use.
        if (allowed) {
          mgr->RemoveFromPrincipal(principal, cameraPermission);
        }
      }
    }
  }
  return allowed;
}

bool
CamerasParent::RecvAllocateCaptureDevice(const CaptureEngine& aCapEngine,
                                         const nsCString& unique_id,
                                         const nsCString& aOrigin)
{
  LOG(("%s: Verifying permissions for %s", __PRETTY_FUNCTION__, aOrigin.get()));
  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> mainthread_runnable =
    media::NewRunnableFrom([self, aCapEngine, unique_id, aOrigin]() -> nsresult {
      // Verify whether the claimed origin has received permission
      // to use the camera, either persistently or this session (one shot).
      bool allowed = HasCameraPermission(aOrigin);
      if (!allowed) {
        // Developer preference for turning off permission check.
        if (Preferences::GetBool("media.navigator.permission.disabled", false)
            || Preferences::GetBool("media.navigator.permission.fake")) {
          allowed = true;
          LOG(("No permission but checks are disabled or fake sources active"));
        } else {
          LOG(("No camera permission for this origin"));
        }
      }
      // After retrieving the permission (or not) on the main thread,
      // bounce to the WebRTC thread to allocate the device (or not),
      // then bounce back to the IPC thread for the reply to content.
      RefPtr<Runnable> webrtc_runnable =
      media::NewRunnableFrom([self, allowed, aCapEngine, unique_id]() -> nsresult {
        int numdev = -1;
        int error = -1;
        if (allowed && self->EnsureInitialized(aCapEngine)) {
          error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice(
                    unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev);
        }
        RefPtr<nsIRunnable> ipc_runnable =
          media::NewRunnableFrom([self, numdev, error]() -> nsresult {
            if (self->IsShuttingDown()) {
              return NS_ERROR_FAILURE;
            }
            if (error) {
              Unused << self->SendReplyFailure();
              return NS_ERROR_FAILURE;
            } else {
              LOG(("Allocated device nr %d", numdev));
              Unused << self->SendReplyAllocateCaptureDevice(numdev);
              return NS_OK;
            }
          });
        self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
        return NS_OK;
        });
      self->DispatchToVideoCaptureThread(webrtc_runnable);
      return NS_OK;
    });
  NS_DispatchToMainThread(mainthread_runnable);
  return true;
}

int
CamerasParent::ReleaseCaptureDevice(const CaptureEngine& aCapEngine,
                                    const int& capnum)
{
  int error = -1;
  if (EnsureInitialized(aCapEngine)) {
    error = mEngines[aCapEngine].mPtrViECapture->ReleaseCaptureDevice(capnum);
  }
  return error;
}

bool
CamerasParent::RecvReleaseCaptureDevice(const CaptureEngine& aCapEngine,
                                        const int& numdev)
{
  LOG((__PRETTY_FUNCTION__));
  LOG(("RecvReleaseCamera device nr %d", numdev));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine, numdev]() -> nsresult {
      int error = self->ReleaseCaptureDevice(aCapEngine, numdev);
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, error, numdev]() -> nsresult {
          if (self->IsShuttingDown()) {
            LOG(("In Shutdown, not Releasing"));
            return NS_ERROR_FAILURE;
          }
          if (error) {
            Unused << self->SendReplyFailure();
            LOG(("Failed to free device nr %d", numdev));
            return NS_ERROR_FAILURE;
          } else {
            Unused << self->SendReplySuccess();
            LOG(("Freed device nr %d", numdev));
            return NS_OK;
          }
        });
      self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

bool
CamerasParent::RecvStartCapture(const CaptureEngine& aCapEngine,
                                const int& capnum,
                                const CaptureCapability& ipcCaps)
{
  LOG((__PRETTY_FUNCTION__));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine, capnum, ipcCaps]() -> nsresult {
      CallbackHelper** cbh;
      webrtc::ExternalRenderer* render;
      EngineHelper* helper = nullptr;
      int error = -1;
      if (self->EnsureInitialized(aCapEngine)) {
        cbh = self->mCallbacks.AppendElement(
          new CallbackHelper(static_cast<CaptureEngine>(aCapEngine), capnum, self));
        render = static_cast<webrtc::ExternalRenderer*>(*cbh);

        helper = &self->mEngines[aCapEngine];
        error =
          helper->mPtrViERender->AddRenderer(capnum, webrtc::kVideoI420, render);
        if (!error) {
          error = helper->mPtrViERender->StartRender(capnum);
        }

        webrtc::CaptureCapability capability;
        capability.width = ipcCaps.width();
        capability.height = ipcCaps.height();
        capability.maxFPS = ipcCaps.maxFPS();
        capability.expectedCaptureDelay = ipcCaps.expectedCaptureDelay();
        capability.rawType = static_cast<webrtc::RawVideoType>(ipcCaps.rawType());
        capability.codecType = static_cast<webrtc::VideoCodecType>(ipcCaps.codecType());
        capability.interlaced = ipcCaps.interlaced();

        if (!error) {
          error = helper->mPtrViECapture->StartCapture(capnum, capability);
        }
        if (!error) {
          helper->mEngineIsRunning = true;
        }
      }
      RefPtr<nsIRunnable> ipc_runnable =
        media::NewRunnableFrom([self, error]() -> nsresult {
          if (self->IsShuttingDown()) {
            return NS_ERROR_FAILURE;
          }
          if (!error) {
            Unused << self->SendReplySuccess();
            return NS_OK;
          } else {
            Unused << self->SendReplyFailure();
            return NS_ERROR_FAILURE;
          }
        });
      self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
      return NS_OK;
    });
  DispatchToVideoCaptureThread(webrtc_runnable);
  return true;
}

void
CamerasParent::StopCapture(const CaptureEngine& aCapEngine,
                           const int& capnum)
{
  if (EnsureInitialized(aCapEngine)) {
    mEngines[aCapEngine].mPtrViECapture->StopCapture(capnum);
    mEngines[aCapEngine].mPtrViERender->StopRender(capnum);
    mEngines[aCapEngine].mPtrViERender->RemoveRenderer(capnum);
    mEngines[aCapEngine].mEngineIsRunning = false;

    for (size_t i = 0; i < mCallbacks.Length(); i++) {
      if (mCallbacks[i]->mCapEngine == aCapEngine
          && mCallbacks[i]->mCapturerId == capnum) {
        delete mCallbacks[i];
        mCallbacks.RemoveElementAt(i);
        break;
      }
    }
  }
}

bool
CamerasParent::RecvStopCapture(const CaptureEngine& aCapEngine,
                               const int& capnum)
{
  LOG((__PRETTY_FUNCTION__));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> webrtc_runnable =
    media::NewRunnableFrom([self, aCapEngine, capnum]() -> nsresult {
      self->StopCapture(aCapEngine, capnum);
      return NS_OK;
    });
  nsresult rv = DispatchToVideoCaptureThread(webrtc_runnable);
  if (self->IsShuttingDown()) {
    return NS_SUCCEEDED(rv);
  } else {
    if (NS_SUCCEEDED(rv)) {
      return SendReplySuccess();
    } else {
      return SendReplyFailure();
    }
  }
}

void
CamerasParent::StopIPC()
{
  MOZ_ASSERT(!mDestroyed);
  // Release shared memory now, it's our last chance
  mShmemPool.Cleanup(this);
  // We don't want to receive callbacks or anything if we can't
  // forward them anymore anyway.
  mChildIsAlive = false;
  mDestroyed = true;
}

bool
CamerasParent::RecvAllDone()
{
  LOG((__PRETTY_FUNCTION__));
  // Don't try to send anything to the child now
  mChildIsAlive = false;
  return Send__delete__(this);
}

void
CamerasParent::ActorDestroy(ActorDestroyReason aWhy)
{
  // No more IPC from here
  LOG((__PRETTY_FUNCTION__));
  StopIPC();
  // Shut down WebRTC (if we're not in full shutdown, else this
  // will already have happened)
  StopVideoCapture();
}

CamerasParent::CamerasParent()
  : mShmemPool(CaptureEngine::MaxEngine),
    mThreadMonitor("CamerasParent::mThreadMonitor"),
    mVideoCaptureThread(nullptr),
    mChildIsAlive(true),
    mDestroyed(false),
    mWebRTCAlive(true)
{
  LOG(("CamerasParent: %p", this));

  mPBackgroundThread = NS_GetCurrentThread();
  MOZ_ASSERT(mPBackgroundThread != nullptr, "GetCurrentThread failed");

  LOG(("Spinning up WebRTC Cameras Thread"));

  RefPtr<CamerasParent> self(this);
  RefPtr<Runnable> threadStart =
    media::NewRunnableFrom([self]() -> nsresult {
      // Register thread shutdown observer
      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
      if (NS_WARN_IF(!obs)) {
        return NS_ERROR_FAILURE;
      }
      nsresult rv =
        obs->AddObserver(self, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      // Start the thread
      MonitorAutoLock lock(self->mThreadMonitor);
      self->mVideoCaptureThread = new base::Thread("VideoCapture");
      base::Thread::Options options;
#if defined(_WIN32)
      options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD;
#else

      options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD;
#endif
      if (!self->mVideoCaptureThread->StartWithOptions(options)) {
        MOZ_CRASH();
      }
      self->mThreadMonitor.NotifyAll();
      return NS_OK;
    });
  NS_DispatchToMainThread(threadStart);

  MOZ_COUNT_CTOR(CamerasParent);
}

CamerasParent::~CamerasParent()
{
  LOG(("~CamerasParent: %p", this));

  MOZ_COUNT_DTOR(CamerasParent);
#ifdef DEBUG
  // Verify we have shut down the webrtc engines, this is
  // supposed to happen in ActorDestroy.
  // That runnable takes a ref to us, so it must have finished
  // by the time we get here.
  for (int i = 0; i < CaptureEngine::MaxEngine; i++) {
    MOZ_ASSERT(!mEngines[i].mEngine);
  }
#endif
}

already_AddRefed<CamerasParent>
CamerasParent::Create() {
  mozilla::ipc::AssertIsOnBackgroundThread();
  RefPtr<CamerasParent> camerasParent = new CamerasParent();
  return camerasParent.forget();
}

}
}
