/* 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 "RemoteMediaManagerParent.h"

#if XP_WIN
#  include <objbase.h>
#endif

#include "ImageContainer.h"
#include "PDMFactory.h"
#include "RemoteAudioDecoder.h"
#include "RemoteCDMParent.h"
#include "RemoteMediaDataEncoderParent.h"
#include "RemoteVideoDecoder.h"
#include "VideoUtils.h"  // for MediaThreadType
#include "mozilla/RDDParent.h"
#include "mozilla/RemoteDecodeUtils.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/UtilityProcessChild.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/VideoBridgeChild.h"
#include "mozilla/layers/VideoBridgeParent.h"
#include "nsIObserverService.h"

#ifdef MOZ_WMF_MEDIA_ENGINE
#  include "MFMediaEngineParent.h"
#endif

#ifdef MOZ_WMF_CDM
#  include "MFCDMParent.h"
#endif

#ifdef MOZ_WIDGET_ANDROID
#  include "mozilla/MediaDrmRemoteCDMParent.h"
#endif

namespace mozilla {

#define LOG(msg, ...) \
  MOZ_LOG_FMT(gRemoteDecodeLog, LogLevel::Debug, msg, ##__VA_ARGS__)

using namespace ipc;
using namespace layers;
using namespace gfx;

StaticRefPtr<TaskQueue> sRemoteMediaManagerParentThread;

void RemoteMediaManagerParent::StoreImage(const SurfaceDescriptorGPUVideo& aSD,
                                          Image* aImage,
                                          TextureClient* aTexture) {
  MOZ_ASSERT(OnManagerThread());
  mImageMap[static_cast<SurfaceDescriptorRemoteDecoder>(aSD).handle()] = aImage;
  mTextureMap[static_cast<SurfaceDescriptorRemoteDecoder>(aSD).handle()] =
      aTexture;
}

class RemoteDecoderManagerThreadShutdownObserver : public nsIObserver {
  virtual ~RemoteDecoderManagerThreadShutdownObserver() = default;

 public:
  RemoteDecoderManagerThreadShutdownObserver() = default;

  NS_DECL_ISUPPORTS

  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
                     const char16_t* aData) override {
    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);

    RemoteMediaManagerParent::ShutdownVideoBridge();
    RemoteMediaManagerParent::ShutdownThreads();
    return NS_OK;
  }
};
NS_IMPL_ISUPPORTS(RemoteDecoderManagerThreadShutdownObserver, nsIObserver);

bool RemoteMediaManagerParent::StartupThreads() {
  MOZ_ASSERT(NS_IsMainThread());

  if (sRemoteMediaManagerParentThread) {
    return true;
  }

  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
  if (!observerService) {
    return false;
  }

  sRemoteMediaManagerParentThread = TaskQueue::Create(
      GetMediaThreadPool(MediaThreadType::SUPERVISOR), "RemVidParent");
  if (XRE_IsGPUProcess()) {
    MOZ_ALWAYS_SUCCEEDS(
        sRemoteMediaManagerParentThread->Dispatch(NS_NewRunnableFunction(
            "RemoteMediaManagerParent::StartupThreads",
            []() { layers::VideoBridgeChild::StartupForGPUProcess(); })));
  }

  auto* obs = new RemoteDecoderManagerThreadShutdownObserver();
  observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
  return true;
}

void RemoteMediaManagerParent::ShutdownThreads() {
  sRemoteMediaManagerParentThread->BeginShutdown();
  sRemoteMediaManagerParentThread->AwaitShutdownAndIdle();
  sRemoteMediaManagerParentThread = nullptr;
}

/* static */
void RemoteMediaManagerParent::ShutdownVideoBridge() {
  if (sRemoteMediaManagerParentThread) {
    RefPtr<Runnable> task =
        NS_NewRunnableFunction("RemoteMediaManagerParent::ShutdownVideoBridge",
                               []() { VideoBridgeChild::Shutdown(); });
    SyncRunnable::DispatchToThread(sRemoteMediaManagerParentThread, task);
  }
}

/* static */
bool RemoteMediaManagerParent::OnManagerThread() {
  return sRemoteMediaManagerParentThread->IsOnCurrentThread();
}

/* static */
void RemoteMediaManagerParent::Dispatch(
    already_AddRefed<nsIRunnable> aRunnable) {
  if (!sRemoteMediaManagerParentThread) {
    MOZ_DIAGNOSTIC_CRASH(
        "Dispatching after RemoteMediaManagerParent thread shutdown!");
    return;
  }

  MOZ_ALWAYS_SUCCEEDS(
      sRemoteMediaManagerParentThread->Dispatch(std::move(aRunnable)));
}

PDMFactory& RemoteMediaManagerParent::EnsurePDMFactory() {
  MOZ_ASSERT(OnManagerThread());
  if (!mPDMFactory) {
    mPDMFactory = MakeRefPtr<PDMFactory>();
  }
  return *mPDMFactory;
}

bool RemoteMediaManagerParent::CreateForContent(
    Endpoint<PRemoteMediaManagerParent>&& aEndpoint,
    dom::ContentParentId aChildId) {
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD ||
             XRE_GetProcessType() == GeckoProcessType_Utility ||
             XRE_GetProcessType() == GeckoProcessType_GPU);
  MOZ_ASSERT(NS_IsMainThread());

  if (!StartupThreads()) {
    return false;
  }

  RefPtr<RemoteMediaManagerParent> parent =
      new RemoteMediaManagerParent(sRemoteMediaManagerParentThread, aChildId);

  RefPtr<Runnable> task =
      NewRunnableMethod<Endpoint<PRemoteMediaManagerParent>&&>(
          "dom::RemoteMediaManagerParent::Open", parent,
          &RemoteMediaManagerParent::Open, std::move(aEndpoint));
  MOZ_ALWAYS_SUCCEEDS(sRemoteMediaManagerParentThread->Dispatch(task.forget()));
  return true;
}

bool RemoteMediaManagerParent::CreateVideoBridgeToOtherProcess(
    Endpoint<PVideoBridgeChild>&& aEndpoint) {
  LOG("Create video bridge");
  // We never want to decode in the GPU process, but output
  // frames to the parent process.
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD ||
             XRE_GetProcessType() == GeckoProcessType_Utility);
#ifdef MOZ_WMF_MEDIA_ENGINE
  MOZ_ASSERT_IF(
      XRE_GetProcessType() == GeckoProcessType_Utility,
      GetCurrentSandboxingKind() == SandboxingKind::MF_MEDIA_ENGINE_CDM);
#endif
  MOZ_ASSERT(NS_IsMainThread());

  if (!StartupThreads()) {
    return false;
  }

  RefPtr<Runnable> task =
      NewRunnableFunction("gfx::VideoBridgeChild::Open",
                          &VideoBridgeChild::Open, std::move(aEndpoint));
  MOZ_ALWAYS_SUCCEEDS(sRemoteMediaManagerParentThread->Dispatch(task.forget()));
  return true;
}

RemoteMediaManagerParent::RemoteMediaManagerParent(
    nsISerialEventTarget* aThread, dom::ContentParentId aContentId)
    : mThread(aThread), mContentId(aContentId) {
  MOZ_COUNT_CTOR(RemoteMediaManagerParent);
  auto& registrar =
      XRE_IsGPUProcess() ? GPUParent::GetSingleton()->AsyncShutdownService()
      : XRE_IsUtilityProcess()
          ? UtilityProcessChild::GetSingleton()->AsyncShutdownService()
          : RDDParent::GetSingleton()->AsyncShutdownService();
  registrar.Register(this);
}

RemoteMediaManagerParent::~RemoteMediaManagerParent() {
  MOZ_COUNT_DTOR(RemoteMediaManagerParent);
  auto& registrar =
      XRE_IsGPUProcess() ? GPUParent::GetSingleton()->AsyncShutdownService()
      : XRE_IsUtilityProcess()
          ? UtilityProcessChild::GetSingleton()->AsyncShutdownService()
          : RDDParent::GetSingleton()->AsyncShutdownService();
  registrar.Deregister(this);
}

void RemoteMediaManagerParent::ActorDestroy(
    mozilla::ipc::IProtocol::ActorDestroyReason) {
  mThread = nullptr;
}

already_AddRefed<PRemoteDecoderParent>
RemoteMediaManagerParent::AllocPRemoteDecoderParent(
    const RemoteDecoderInfoIPDL& aRemoteDecoderInfo,
    const CreateDecoderParams::OptionSet& aOptions,
    const Maybe<layers::TextureFactoryIdentifier>& aIdentifier,
    const Maybe<uint64_t>& aMediaEngineId, const Maybe<TrackingId>& aTrackingId,
    PRemoteCDMParent* aCDM) {
  RefPtr<TaskQueue> decodeTaskQueue =
      TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
                        "RemoteVideoDecoderParent::mDecodeTaskQueue");

  auto* cdm = static_cast<RemoteCDMParent*>(aCDM);

  if (aRemoteDecoderInfo.type() ==
      RemoteDecoderInfoIPDL::TVideoDecoderInfoIPDL) {
    const VideoDecoderInfoIPDL& decoderInfo =
        aRemoteDecoderInfo.get_VideoDecoderInfoIPDL();
    return MakeAndAddRef<RemoteVideoDecoderParent>(
        this, decoderInfo.videoInfo(), decoderInfo.framerate(), aOptions,
        aIdentifier, sRemoteMediaManagerParentThread, decodeTaskQueue,
        aMediaEngineId, aTrackingId, cdm);
  }

  if (aRemoteDecoderInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) {
    return MakeAndAddRef<RemoteAudioDecoderParent>(
        this, aRemoteDecoderInfo.get_AudioInfo(), aOptions,
        sRemoteMediaManagerParentThread, decodeTaskQueue, aMediaEngineId, cdm);
  }

  MOZ_CRASH("unrecognized type of RemoteDecoderInfoIPDL union");
  return nullptr;
}

already_AddRefed<PRemoteEncoderParent>
RemoteMediaManagerParent::AllocPRemoteEncoderParent(
    const EncoderConfig& aConfig) {
  return MakeAndAddRef<RemoteMediaDataEncoderParent>(aConfig);
}

already_AddRefed<PMFMediaEngineParent>
RemoteMediaManagerParent::AllocPMFMediaEngineParent() {
#ifdef MOZ_WMF_MEDIA_ENGINE
  return MakeAndAddRef<MFMediaEngineParent>(this,
                                            sRemoteMediaManagerParentThread);
#else
  return nullptr;
#endif
}

already_AddRefed<PMFCDMParent> RemoteMediaManagerParent::AllocPMFCDMParent(
    const nsAString& aKeySystem) {
#ifdef MOZ_WMF_CDM
  return MakeAndAddRef<MFCDMParent>(aKeySystem, this,
                                    sRemoteMediaManagerParentThread);
#else
  return nullptr;
#endif
}

already_AddRefed<PRemoteCDMParent>
RemoteMediaManagerParent::AllocPRemoteCDMParent(const nsAString& aKeySystem) {
#ifdef MOZ_WIDGET_ANDROID
  return MakeAndAddRef<MediaDrmRemoteCDMParent>(aKeySystem);
#else
  return nullptr;
#endif
}

void RemoteMediaManagerParent::Open(
    Endpoint<PRemoteMediaManagerParent>&& aEndpoint) {
  if (!aEndpoint.Bind(this)) {
    // We can't recover from this.
    MOZ_CRASH("Failed to bind RemoteMediaManagerParent to endpoint");
  }
}

mozilla::ipc::IPCResult RemoteMediaManagerParent::RecvReadback(
    const SurfaceDescriptorGPUVideo& aSD, SurfaceDescriptor* aResult) {
  const SurfaceDescriptorRemoteDecoder& sd = aSD;
  RefPtr<Image> image = mImageMap[sd.handle()];
  if (!image) {
    *aResult = null_t();
    return IPC_OK();
  }

  // Read directly into the shmem to avoid extra copies, if possible.
  SurfaceDescriptorBuffer sdb;
  nsresult rv = image->BuildSurfaceDescriptorBuffer(
      sdb, Image::BuildSdbFlags::RgbOnly, [&](uint32_t aBufferSize) {
        Shmem buffer;
        if (!AllocShmem(aBufferSize, &buffer)) {
          return MemoryOrShmem();
        }
        return MemoryOrShmem(std::move(buffer));
      });

  if (NS_SUCCEEDED(rv)) {
    *aResult = std::move(sdb);
    return IPC_OK();
  }

  if (sdb.data().type() == MemoryOrShmem::TShmem) {
    DeallocShmem(sdb.data().get_Shmem());
  }
  *aResult = null_t();
  return IPC_OK();
}

mozilla::ipc::IPCResult
RemoteMediaManagerParent::RecvDeallocateSurfaceDescriptorGPUVideo(
    const SurfaceDescriptorGPUVideo& aSD) {
  MOZ_ASSERT(OnManagerThread());
  const SurfaceDescriptorRemoteDecoder& sd = aSD;
  mImageMap.erase(sd.handle());
  mTextureMap.erase(sd.handle());
  return IPC_OK();
}

mozilla::ipc::IPCResult RemoteMediaManagerParent::RecvOnSetCurrent(
    const SurfaceDescriptorGPUVideo& aSD) {
  MOZ_ASSERT(OnManagerThread());
  const SurfaceDescriptorRemoteDecoder& sd = aSD;
  RefPtr<Image> image = mImageMap[sd.handle()];
  if (!image) {
    return IPC_OK();
  }

  image->OnSetCurrent();
  return IPC_OK();
}

already_AddRefed<Image> RemoteMediaManagerParent::TransferToImage(
    const SurfaceDescriptorGPUVideo& aSD, const IntSize& aSize,
    const ColorDepth& aColorDepth, YUVColorSpace aYUVColorSpace,
    ColorSpace2 aColorPrimaries, TransferFunction aTransferFunction,
    ColorRange aColorRange) {
  MOZ_ASSERT(OnManagerThread());
  const SurfaceDescriptorRemoteDecoder& sd = aSD;
  const auto i = mImageMap.find(sd.handle());
  if (NS_WARN_IF(i == mImageMap.end())) {
    return nullptr;
  }
  return do_AddRef(i->second);
}

void RemoteMediaManagerParent::DeallocateSurfaceDescriptor(
    const SurfaceDescriptorGPUVideo& aSD) {
  if (!OnManagerThread()) {
    MOZ_ALWAYS_SUCCEEDS(
        sRemoteMediaManagerParentThread->Dispatch(NS_NewRunnableFunction(
            "RemoteMediaManagerParent::DeallocateSurfaceDescriptor",
            [ref = RefPtr{this}, sd = aSD]() {
              ref->RecvDeallocateSurfaceDescriptorGPUVideo(sd);
            })));
  } else {
    RecvDeallocateSurfaceDescriptorGPUVideo(aSD);
  }
}

#undef LOG

}  // namespace mozilla
