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

#include <functional>
#include <map>
#include <span>
#include <vector>

#include "CodecConfig.h"
#include "ImageContainer.h"
#include "MediaConduitControl.h"
#include "MediaConduitErrors.h"
#include "PerformanceRecorder.h"
#include "WebrtcVideoCodecFactory.h"
#include "jsapi/RTCStatsReport.h"
#include "mozilla/MozPromise.h"
#include "mozilla/dom/RTCRtpSourcesBinding.h"
#include "mozilla/media/MediaUtils.h"
#include "nsTArray.h"
#include "transport/mediapacket.h"

// libwebrtc includes
#include "api/audio/audio_frame.h"
#include "api/call/transport.h"
#include "api/media_stream_interface.h"
#include "api/rtp_headers.h"
#include "api/rtp_parameters.h"
#include "api/transport/rtp/rtp_source.h"
#include "api/video/video_frame_buffer.h"
#include "call/audio_receive_stream.h"
#include "call/audio_send_stream.h"
#include "call/call.h"
#include "call/video_receive_stream.h"
#include "call/video_send_stream.h"
#include "rtc_base/copy_on_write_buffer.h"

namespace webrtc {
class RtpPacketReceived;
class VideoFrame;
}  // namespace webrtc

namespace mozilla {
namespace dom {
struct RTCRtpSourceEntry;
}

enum class MediaSessionConduitLocalDirection : int { kSend, kRecv };

class VideoSessionConduit;
class AudioSessionConduit;
class WebrtcCallWrapper;
class FrameTransformerProxy;

/**
 * 1. Abstract renderer for video data
 * 2. This class acts as abstract interface between the video-engine and
 *    video-engine agnostic renderer implementation.
 * 3. Concrete implementation of this interface is responsible for
 *    processing and/or rendering the obtained raw video frame to appropriate
 *    output , say, <video>
 */
class VideoRenderer {
 protected:
  virtual ~VideoRenderer() = default;

 public:
  /**
   * Callback Function reporting decoded frame for processing.
   * @param video_frame: reference to decoded video frame
   * NOTE: If decoded video frame is passed through buffer , it is the
   * responsibility of the concrete implementations of this class to own copy
   * of the frame if needed for time longer than scope of this callback.
   * Such implementations should be quick in processing the frames and return
   * immediately.
   */
  virtual void RenderVideoFrame(const webrtc::VideoFrame& video_frame) = 0;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoRenderer)
};

/**
 * Generic Interface for representing Audio/Video Session
 * MediaSession conduit is identified by 2 main components
 * 1. Attached Transport Interface (through events) for inbound and outbound RTP
 *    transport
 * 2. Attached Renderer Interface for rendering media data off the network
 * This class hides specifics of Media-Engine implementation from the consumers
 * of this interface.
 * Also provides codec configuration API for the media sent and recevied
 */
class MediaSessionConduit {
 protected:
  virtual ~MediaSessionConduit() = default;

 public:
  enum Type { AUDIO, VIDEO };
  enum class PacketType { RTP, RTCP };

  static std::string LocalDirectionToString(
      const MediaSessionConduitLocalDirection aDirection) {
    return aDirection == MediaSessionConduitLocalDirection::kSend ? "send"
                                                                  : "receive";
  }

  virtual Type type() const = 0;

  // Call thread only
  virtual Maybe<int> ActiveSendPayloadType() const = 0;
  virtual Maybe<int> ActiveRecvPayloadType() const = 0;

  // Whether transport is currently sending and receiving packets
  virtual void SetTransportActive(bool aActive) = 0;

  // Sending packets
  virtual MediaEventSourceExc<MediaPacket>& SenderRtpSendEvent() = 0;
  virtual MediaEventSourceExc<MediaPacket>& SenderRtcpSendEvent() = 0;
  virtual MediaEventSourceExc<MediaPacket>& ReceiverRtcpSendEvent() = 0;

  // Receiving RTP packets
  virtual void ConnectReceiverRtpEvent(
      MediaEventSourceExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>&
          aEvent) = 0;

  virtual void ConnectReceiverRtcpEvent(
      MediaEventSourceExc<webrtc::CopyOnWriteBuffer>& aEvent) = 0;

  virtual void ConnectSenderRtcpEvent(
      MediaEventSourceExc<webrtc::CopyOnWriteBuffer>& aEvent) = 0;

  // Sts thread only.
  virtual Maybe<uint16_t> RtpSendBaseSeqFor(uint32_t aSsrc) const = 0;

  // Any thread.
  virtual const dom::RTCStatsTimestampMaker& GetTimestampMaker() const = 0;

  virtual Ssrcs GetLocalSSRCs() const = 0;

  virtual Maybe<Ssrc> GetRemoteSSRC() const = 0;
  virtual void UnsetRemoteSSRC(Ssrc aSsrc) = 0;

  virtual void DisableSsrcChanges() = 0;

  virtual bool HasCodecPluginID(uint64_t aPluginID) const = 0;

  // Stuff for driving mute/unmute events
  virtual MediaEventSource<void>& RtcpByeEvent() = 0;
  virtual MediaEventSource<void>& RtcpTimeoutEvent() = 0;
  virtual MediaEventSource<void>& RtpPacketEvent() = 0;

  virtual bool SendRtp(const uint8_t* aData, size_t aLength,
                       const webrtc::PacketOptions& aOptions) = 0;
  virtual bool SendSenderRtcp(const uint8_t* aData, size_t aLength) = 0;
  virtual bool SendReceiverRtcp(const uint8_t* aData, size_t aLength) = 0;

  virtual void DeliverPacket(webrtc::CopyOnWriteBuffer packet,
                             PacketType type) = 0;

  virtual RefPtr<GenericPromise> Shutdown() = 0;
  // Call thread only. Is set at the end of Shutdown()
  virtual bool IsShutdown() const = 0;

  virtual Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() = 0;
  virtual Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() = 0;

  virtual Maybe<webrtc::Call::Stats> GetCallStats() const = 0;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSessionConduit)

  void GetRtpSources(nsTArray<dom::RTCRtpSourceEntry>& outSources) const;

  virtual void SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) = 0;

  // test-only: inserts fake CSRCs and audio level data.
  // NB: fake data is only valid during the current main thread task.
  void InsertAudioLevelForContributingSource(const uint32_t aCsrcSource,
                                             const int64_t aTimestamp,
                                             const uint32_t aRtpTimestamp,
                                             const bool aHasAudioLevel,
                                             const uint8_t aAudioLevel);

 protected:
  virtual const std::vector<webrtc::RtpSource>& GetUpstreamRtpSources()
      const = 0;

 private:
  void UpdateRtpSources(const std::vector<webrtc::RtpSource>& aSources) const;

  // Marks the cache as having been updated in the current task, and keeps it
  // stable until the current task is finished.
  void OnSourcesUpdated() const;

  // Accessed only on main thread. This exists for a couple of reasons:
  // 1. The webrtc spec says that source stats are updated using a queued task;
  //    libwebrtc's internal representation of these stats is updated without
  //    any task queueing, which means we need a mainthread-only cache.
  // 2. libwebrtc uses its own clock that is not consistent with the one we
  // need to use for stats (the so-called JS timestamps), which means we need
  // to adjust the timestamps.  Since timestamp adjustment is inexact and will
  // not necessarily yield exactly the same result if performed again later, we
  // need to avoid performing it more than once for each entry, which means we
  // need to remember both the JS timestamp (in dom::RTCRtpSourceEntry) and the
  // libwebrtc timestamp (in SourceKey::mLibwebrtcTimestampMs).
  class SourceKey {
   public:
    explicit SourceKey(const webrtc::RtpSource& aSource)
        : SourceKey(aSource.timestamp().ms(), aSource.source_id()) {}

    SourceKey(uint32_t aTimestamp, uint32_t aSrc)
        : mLibwebrtcTimestampMs(aTimestamp), mSrc(aSrc) {}

    // TODO: Once we support = default for this in our toolchain, do so
    auto operator>(const SourceKey& aRhs) const {
      if (mLibwebrtcTimestampMs == aRhs.mLibwebrtcTimestampMs) {
        return mSrc > aRhs.mSrc;
      }
      return mLibwebrtcTimestampMs > aRhs.mLibwebrtcTimestampMs;
    }

   private:
    uint32_t mLibwebrtcTimestampMs;
    uint32_t mSrc;
  };
  mutable std::map<SourceKey, dom::RTCRtpSourceEntry, std::greater<>>
      mSourcesCache;
  // Accessed only on main thread. A flag saying whether mSourcesCache needs
  // updating. Ensures that get*Sources() appear stable from javascript
  // throughout a main thread task, even though we don't follow the spec to the
  // letter (dispatch a task to update the sources).
  mutable bool mSourcesUpdateNeeded = true;
};

class WebrtcSendTransport : public webrtc::Transport {
  // WeakRef to the owning conduit
  MediaSessionConduit* mConduit;

 public:
  explicit WebrtcSendTransport(MediaSessionConduit* aConduit)
      : mConduit(aConduit) {}
  bool SendRtp(std::span<const uint8_t> aPacket,
               const webrtc::PacketOptions& aOptions) {
    return mConduit->SendRtp(aPacket.data(), aPacket.size(), aOptions);
  }
  bool SendRtcp(std::span<const uint8_t> aPacket,
                const webrtc::PacketOptions& aOptions) {
    return mConduit->SendSenderRtcp(aPacket.data(), aPacket.size());
  }
};

class WebrtcReceiveTransport : public webrtc::Transport {
  // WeakRef to the owning conduit
  MediaSessionConduit* mConduit;

 public:
  explicit WebrtcReceiveTransport(MediaSessionConduit* aConduit)
      : mConduit(aConduit) {}
  bool SendRtp(std::span<const uint8_t> aPacket,
               const webrtc::PacketOptions& aOptions) {
    MOZ_CRASH("Unexpected RTP packet");
  }
  bool SendRtcp(std::span<const uint8_t> aPacket,
                const webrtc::PacketOptions& aOptions) {
    return mConduit->SendReceiverRtcp(aPacket.data(), aPacket.size());
  }
};

// Abstract base classes for external encoder/decoder.

// Interface to help signal PluginIDs
class CodecPluginID {
 public:
  virtual MediaEventSource<uint64_t>* InitPluginEvent() { return nullptr; }
  virtual MediaEventSource<uint64_t>* ReleasePluginEvent() { return nullptr; }
  virtual ~CodecPluginID() = default;
};

class VideoEncoder : public CodecPluginID {
 public:
  virtual ~VideoEncoder() = default;
};

class VideoDecoder : public CodecPluginID {
 public:
  virtual ~VideoDecoder() = default;
};

/**
 * MediaSessionConduit for video
 * Refer to the comments on MediaSessionConduit above for overall
 * information
 */
class VideoSessionConduit : public MediaSessionConduit {
 public:
  struct Options {
    bool mVideoLatencyTestEnable = false;
    // All in bps.
    int mMinBitrate = 0;
    int mStartBitrate = 0;
    int mPrefMaxBitrate = 0;
    int mMinBitrateEstimate = 0;
    bool mDenoising = false;
    bool mLockScaling = false;
    uint8_t mSpatialLayers = 1;
    uint8_t mTemporalLayers = 1;
  };

  /**
   * Factory function to create and initialize a Video Conduit Session
   * @param  webrtc::Call instance shared by paired audio and video
   *         media conduits
   * @param  aOptions are a number of options, typically from prefs, used to
   *         configure the created VideoConduit.
   * @param  aPCHandle is a string representing the RTCPeerConnection that is
   *         creating this VideoConduit. This is used when reporting GMP plugin
   *         crashes.
   * @result Concrete VideoSessionConduitObject or nullptr in the case
   *         of failure
   */
  static RefPtr<VideoSessionConduit> Create(
      RefPtr<WebrtcCallWrapper> aCall,
      nsCOMPtr<nsISerialEventTarget> aStsThread, Options aOptions,
      std::string aPCHandle, const TrackingId& aRecvTrackingId);

  enum FrameRequestType {
    FrameRequestNone,
    FrameRequestFir,
    FrameRequestPli,
    FrameRequestUnknown
  };

  VideoSessionConduit()
      : mFrameRequestMethod(FrameRequestNone),
        mUsingNackBasic(false),
        mUsingTmmbr(false),
        mUsingFEC(false) {}

  virtual ~VideoSessionConduit() = default;

  Type type() const override { return VIDEO; }

  Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() override {
    return Nothing();
  }

  Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() override {
    return Some(RefPtr<VideoSessionConduit>(this));
  }

  /**
   * Hooks up mControl Mirrors with aControl Canonicals, and sets up
   * mWatchManager to react on Mirror changes.
   */
  virtual void InitControl(VideoConduitControlInterface* aControl) = 0;

  /**
   * Function to attach Renderer end-point of the Media-Video conduit.
   * @param aRenderer : Reference to the concrete Video renderer implementation
   * Note: Multiple invocations of this API shall remove an existing renderer
   * and attaches the new to the Conduit.
   */
  virtual MediaConduitErrorCode AttachRenderer(
      RefPtr<mozilla::VideoRenderer> aRenderer) = 0;
  virtual void DetachRenderer() = 0;

  virtual void SetTrackSource(webrtc::VideoTrackSourceInterface* aSource) = 0;

  /**
   * These methods allow unit tests to double-check that the
   * rtcp-fb settings are as expected.
   */
  FrameRequestType FrameRequestMethod() const { return mFrameRequestMethod; }

  bool UsingNackBasic() const { return mUsingNackBasic; }

  bool UsingTmmbr() const { return mUsingTmmbr; }

  bool UsingFEC() const { return mUsingFEC; }

  virtual bool LockScaling() const = 0;

  virtual Maybe<webrtc::VideoReceiveStreamInterface::Stats> GetReceiverStats()
      const = 0;
  virtual Maybe<webrtc::VideoSendStream::Stats> GetSenderStats() const = 0;

  virtual void CollectTelemetryData() = 0;

  virtual bool AddFrameHistory(
      dom::Sequence<dom::RTCVideoFrameHistoryInternal>* outHistories) const = 0;

  virtual Maybe<Ssrc> GetAssociatedLocalRtxSSRC(Ssrc aSsrc) const = 0;

  virtual Maybe<gfx::IntSize> GetLastResolution() const = 0;
  virtual AbstractCanonical<Maybe<gfx::IntSize>>* CanonicalReceivingSize() = 0;

  virtual void RequestKeyFrame(FrameTransformerProxy* aProxy) = 0;
  virtual void GenerateKeyFrame(const Maybe<std::string>& aRid,
                                FrameTransformerProxy* aProxy) = 0;

 protected:
  /* RTCP feedback settings, for unit testing purposes */
  FrameRequestType mFrameRequestMethod;
  bool mUsingNackBasic;
  bool mUsingTmmbr;
  bool mUsingFEC;
};

/**
 * MediaSessionConduit for audio
 * Refer to the comments on MediaSessionConduit above for overall
 * information
 */
class AudioSessionConduit : public MediaSessionConduit {
 public:
  /**
   * Factory function to create and initialize an Audio Conduit Session
   * @param  webrtc::Call instance shared by paired audio and video
   *         media conduits
   * @result Concrete AudioSessionConduitObject or nullptr in the case
   *         of failure
   */
  static RefPtr<AudioSessionConduit> Create(
      RefPtr<WebrtcCallWrapper> aCall,
      nsCOMPtr<nsISerialEventTarget> aStsThread);

  virtual ~AudioSessionConduit() = default;

  Type type() const override { return AUDIO; }

  Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() override {
    return Some(this);
  }

  Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() override {
    return Nothing();
  }

  /**
   * Hooks up mControl Mirrors with aControl Canonicals, and sets up
   * mWatchManager to react on Mirror changes.
   */
  virtual void InitControl(AudioConduitControlInterface* aControl) = 0;

  /**
   * Function to deliver externally captured audio sample for encoding and
   * transport
   * @param frame [in]: AudioFrame in upstream's format for forwarding to the
   *                    send stream. Ownership is passed along.
   * NOTE: ConfigureSendMediaCodec() SHOULD be called before this function can
   * be invoked. This ensures the inserted audio-samples can be transmitted by
   * the conduit.
   */
  virtual MediaConduitErrorCode SendAudioFrame(
      std::unique_ptr<webrtc::AudioFrame> frame) = 0;

  /**
   * Function to grab a decoded audio-sample from the media engine for
   * rendering / playout of length 10 milliseconds.
   *
   * @param samplingFreqHz [in]: Frequency of the sampling for playback in
   *                             Hertz (16000, 32000,..)
   * @param frame [in/out]: Pointer to an AudioFrame to which audio data will be
   *                        copied
   * NOTE: This function should be invoked every 10 milliseconds for the best
   *       performance
   * NOTE: ConfigureRecvMediaCodec() SHOULD be called before this function can
   *       be invoked
   * This ensures the decoded samples are ready for reading and playout is
   * enabled.
   */
  virtual MediaConduitErrorCode GetAudioFrame(int32_t samplingFreqHz,
                                              webrtc::AudioFrame* frame) = 0;

  /**
   * Checks if given sampling frequency is supported
   * @param freq: Sampling rate (in Hz) to check
   */
  virtual bool IsSamplingFreqSupported(int freq) const = 0;

  virtual Maybe<webrtc::AudioReceiveStreamInterface::Stats> GetReceiverStats()
      const = 0;
  virtual Maybe<webrtc::AudioSendStream::Stats> GetSenderStats() const = 0;
};
}  // namespace mozilla
#endif
