/* 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 DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_
#define DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_

#include "ExternalEngineStateMachine.h"
#include "MFMediaEngineUtils.h"
#include "TimeUnits.h"
#include "mozilla/Atomics.h"
#include "mozilla/NotNull.h"
#include "mozilla/PMFMediaEngineChild.h"

namespace mozilla {

class MFMediaEngineWrapper;

/**
 * MFMediaEngineChild is a wrapper class for a MediaEngine in the content
 * process. It communicates with MFMediaEngineParent in the remote process by
 * using IPDL interfaces to send commands to the MediaEngine.
 * https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nn-mfmediaengine-imfmediaengine
 */
class MFMediaEngineChild final : public PMFMediaEngineChild {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFMediaEngineChild, final);

  MFMediaEngineChild(MFMediaEngineWrapper* aOwner,
                     FrameStatistics* aFrameStats);

  void OwnerDestroyed();
  void ActorDestroy(ActorDestroyReason aWhy) override;

  RefPtr<GenericNonExclusivePromise> Init(
      const MediaInfo& aInfo,
      const ExternalPlaybackEngine::InitFlagSet& aFlags);
  void Shutdown();

  // Methods for PMFMediaEngineChild
  mozilla::ipc::IPCResult RecvRequestSample(TrackInfo::TrackType aType,
                                            bool aIsEnough);
  mozilla::ipc::IPCResult RecvUpdateCurrentTime(double aCurrentTimeInSecond);
  mozilla::ipc::IPCResult RecvNotifyEvent(MFMediaEngineEvent aEvent);
  mozilla::ipc::IPCResult RecvNotifyError(const MediaResult& aError);
  mozilla::ipc::IPCResult RecvNotifyHardwareReset(uint32_t aPlatformError);
  mozilla::ipc::IPCResult RecvNotifyWaitingForKey();
  mozilla::ipc::IPCResult RecvUpdateStatisticData(const StatisticData& aData);
  mozilla::ipc::IPCResult RecvNotifyResizing(uint32_t aWidth, uint32_t aHeight);
  mozilla::ipc::IPCResult RecvNotifyFrameServerMode();

  nsISerialEventTarget* ManagerThread() const { return mManagerThread; }
  void AssertOnManagerThread() const {
    MOZ_ASSERT(mManagerThread->IsOnCurrentThread());
  }

  uint64_t Id() const { return mMediaEngineId; }

 private:
  ~MFMediaEngineChild() = default;

  uint64_t GetUpdatedRenderedFrames(const StatisticData& aData);
  uint64_t GetUpdatedDroppedFrames(const StatisticData& aData);

  // Only modified on the manager thread.
  MFMediaEngineWrapper* MOZ_NON_OWNING_REF mOwner;

  const nsCOMPtr<nsISerialEventTarget> mManagerThread;

  // This represents an unique Id to indentify the media engine in the remote
  // process. Zero is used for the status before initializaing Id from the
  // remote process.
  // Modified on the manager thread, and read on other threads.
  Atomic<uint64_t> mMediaEngineId;

  MozPromiseHolder<GenericNonExclusivePromise> mInitPromiseHolder;
  MozPromiseRequestHolder<InitMediaEnginePromise> mInitEngineRequest;

  // This is guaranteed always being alive in our lifetime.
  NotNull<FrameStatistics*> const MOZ_NON_OWNING_REF mFrameStats;

  bool mShutdown = false;

  // Whenever the remote media engine process crashes, we will create a new
  // engine child to rebuild the connection. These engine child shares the same
  // frame stats data so we need to keep accumulate same data from previous
  // engine.
  Maybe<uint64_t> mAccumulatedPresentedFramesFromPrevEngine;
  Maybe<uint64_t> mAccumulatedDroppedFramesFromPrevEngine;
};

/**
 * MFMediaEngineWrapper acts as an external playback engine, which is
 * implemented by using the Media Foundation Media Engine. It holds hold an
 * actor used to communicate with the engine in the remote process. Its methods
 * are all thread-safe.
 */
class MFMediaEngineWrapper final : public ExternalPlaybackEngine {
 public:
  MFMediaEngineWrapper(ExternalEngineStateMachine* aOwner,
                       FrameStatistics* aFrameStats);
  ~MFMediaEngineWrapper();

  // Methods for ExternalPlaybackEngine
  RefPtr<GenericNonExclusivePromise> Init(const MediaInfo& aInfo,
                                          const InitFlagSet& aFlags) override;
  void Play() override;
  void Pause() override;
  void Seek(const media::TimeUnit& aTargetTime) override;
  void Shutdown() override;
  void SetPlaybackRate(double aPlaybackRate) override;
  void SetVolume(double aVolume) override;
  void SetLooping(bool aLooping) override;
  void SetPreservesPitch(bool aPreservesPitch) override;
  media::TimeUnit GetCurrentPosition() override;
  void NotifyEndOfStream(TrackInfo::TrackType aType) override;
  uint64_t Id() const override { return mEngine->Id(); }
  bool IsInited() const { return mEngine->Id() != 0; }
  bool SetCDMProxy(CDMProxy* aProxy) override;
  void NotifyResizing(uint32_t aWidth, uint32_t aHeight) override;

  nsISerialEventTarget* ManagerThread() { return mEngine->ManagerThread(); }
  void AssertOnManagerThread() const { mEngine->AssertOnManagerThread(); }

 private:
  friend class MFMediaEngineChild;

  void UpdateCurrentTime(double aCurrentTimeInSecond);
  void NotifyEvent(ExternalEngineEvent aEvent);
  void NotifyError(const MediaResult& aError);
  void NotifyHardwareReset(uint32_t aPlatformError);
  void NotifyWaitingForKey();
#ifdef MOZ_WMF_CDM
  void NotifyFrameServerMode();
#endif

  const RefPtr<MFMediaEngineChild> mEngine;

  // The current time which we receive from the MediaEngine or set by the state
  // machine when seeking.
  std::atomic<double> mCurrentTimeInSecond;
};

}  // namespace mozilla

#endif  // DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_
