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

#include <string>

#include "ErrorList.h"
#include "RTCStatsReport.h"
#include "jsep/JsepSession.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StateMirroring.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
#include "mozilla/dom/RTCRtpTransceiverBinding.h"
#include "nsCOMPtr.h"
#include "nsISerialEventTarget.h"
#include "nsTArray.h"
#include "transport/transportlayer.h"  // For TransportLayer::State

class nsIPrincipal;

namespace mozilla {
class PeerIdentity;
class MediaSessionConduit;
class VideoSessionConduit;
class AudioSessionConduit;
struct AudioCodecConfig;
class VideoCodecConfig;  // Why is this a class, but AudioCodecConfig a struct?
class MediaPipelineTransmit;
class MediaPipeline;
class MediaPipelineFilter;
class MediaTransportHandler;
class RTCStatsIdGenerator;
class WebrtcCallWrapper;
class JsepTrackNegotiatedDetails;
class PeerConnectionImpl;
enum class PrincipalPrivacy : uint8_t;

namespace dom {
class RTCDtlsTransport;
class RTCDTMFSender;
class RTCRtpTransceiver;
struct RTCRtpSourceEntry;
class RTCRtpReceiver;
class RTCRtpSender;

/**
 * This is what ties all the various pieces that make up a transceiver
 * together. This includes:
 * MediaStreamTrack for rendering and capture
 * MediaTransportHandler for RTP transmission/reception
 * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and
 * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP.
 */
class RTCRtpTransceiver : public nsISupports, public nsWrapperCache {
 public:
  /**
   * |aSendTrack| might or might not be set.
   */
  RTCRtpTransceiver(
      nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc,
      MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession,
      const std::string& aTransceiverId, bool aIsVideo,
      nsISerialEventTarget* aStsThread, MediaStreamTrack* aSendTrack,
      WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator);

  void Init(const RTCRtpTransceiverInit& aInit, ErrorResult& aRv);

  bool IsValid() const { return !!mConduit; }

  nsresult UpdateTransport();

  nsresult UpdateConduit();

  void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy);

  void ResetSync();

  nsresult SyncWithMatchingVideoConduits(
      nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers);

  void Close();

  void BreakCycles();
  void Unlink();

  bool ConduitHasPluginID(uint64_t aPluginID);

  // for webidl
  JSObject* WrapObject(JSContext* aCx,
                       JS::Handle<JSObject*> aGivenProto) override;
  nsPIDOMWindowInner* GetParentObject() const;
  RTCRtpReceiver* Receiver() const { return mReceiver; }
  RTCRtpSender* Sender() const { return mSender; }
  RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; }
  void GetKind(nsAString& aKind) const;
  void GetMid(nsAString& aMid) const;
  RTCRtpTransceiverDirection Direction() const {
    if (mStopping) {
      return RTCRtpTransceiverDirection::Stopped;
    }
    return mDirection;
  }
  void SetDirection(RTCRtpTransceiverDirection aDirection, ErrorResult& aRv);
  Nullable<RTCRtpTransceiverDirection> GetCurrentDirection() {
    if (mStopped) {
      return RTCRtpTransceiverDirection::Stopped;
    }
    return mCurrentDirection;
  }
  void Stop(ErrorResult& aRv);
  void SetCodecPreferences(const nsTArray<RTCRtpCodec>& aCodecs,
                           ErrorResult& aRv);
  void SetDirectionInternal(RTCRtpTransceiverDirection aDirection);
  bool HasBeenUsedToSend() const { return mHasBeenUsedToSend; }

  bool CanSendDTMF() const;
  bool Stopped() const { return mStopped; }
  bool Stopping() const { return mStopping; }
  void SyncToJsep(JsepSession& aSession) const;
  void SyncFromJsep(const JsepSession& aSession);
  std::string GetMidAscii() const;

  void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport);
  void SaveStateForRollback();
  void RollbackToStableDtlsTransport();

  std::string GetTransportId() const {
    return mJsepTransceiver.mTransport.mTransportId;
  }

  JsepTransceiver& GetJsepTransceiver() { return mJsepTransceiver; }

  bool IsVideo() const;

  bool IsSending() const;

  bool IsReceiving() const;

  bool ShouldRemove() const;

  Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
  GetNegotiatedSendCodecs() const;

  Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
  GetNegotiatedRecvCodecs() const;

  struct PayloadTypes {
    Maybe<int> mSendPayloadType;
    Maybe<int> mRecvPayloadType;
  };
  using ActivePayloadTypesPromise = MozPromise<PayloadTypes, nsresult, true>;
  RefPtr<ActivePayloadTypesPromise> GetActivePayloadTypes() const;

  MediaSessionConduit* GetConduit() const { return mConduit; }

  const std::string& GetJsepTransceiverId() const { return mTransceiverId; }

  void SetRemovedFromPc() { mHandlingUnlink = true; }

  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver)

  static void NegotiatedDetailsToAudioCodecConfigs(
      const JsepTrackNegotiatedDetails& aDetails,
      std::vector<AudioCodecConfig>* aConfigs);

  static void NegotiatedDetailsToVideoCodecConfigs(
      const JsepTrackNegotiatedDetails& aDetails,
      std::vector<VideoCodecConfig>* aConfigs);

  static void ToDomRtpCodec(const JsepCodecDescription& aCodec,
                            RTCRtpCodec* aDomCodec);

  static void ToDomRtpCodecParameters(
      const JsepCodecDescription& aCodec,
      RTCRtpCodecParameters* aDomCodecParameters);

  static void ToDomRtpCodecRtx(const JsepVideoCodecDescription& aCodec,
                               RTCRtpCodec* aDomCodec);

  static void ToDomRtpCodecParametersRtx(
      const JsepVideoCodecDescription& aCodec,
      RTCRtpCodecParameters* aDomCodecParameters);

  static void ToDomHeaderExtensions(
      const JsepTrackNegotiatedDetails& aDetails,
      Sequence<RTCRtpHeaderExtensionParameters>& aExtensions);

  /* Returns a promise that will contain the stats in aStats, along with the
   * codec stats (which is a PC-wide thing) */
  void ChainToDomPromiseWithCodecStats(nsTArray<RefPtr<RTCStatsPromise>> aStats,
                                       const RefPtr<Promise>& aDomPromise);

  /**
   * Takes a set of codec stats (per-peerconnection) and a set of
   * transceiver/transceiver-stats-promise tuples. Filters out all referenced
   * codec stats based on the transceiver's transport and rtp stream stats.
   * Finally returns the flattened stats containing the filtered codec stats and
   * all given per-transceiver-stats.
   */
  static RefPtr<RTCStatsPromise> ApplyCodecStats(
      nsTArray<RTCCodecStats> aCodecStats,
      nsTArray<std::tuple<RTCRtpTransceiver*,
                          RefPtr<RTCStatsPromise::AllPromiseType>>>
          aTransceiverStatsPromises);

  Canonical<std::string>& CanonicalMid() { return mMid; }
  Canonical<std::string>& CanonicalSyncGroup() { return mSyncGroup; }

  const std::vector<UniquePtr<JsepCodecDescription>>& GetPreferredCodecs() {
    return mPreferredCodecs;
  }

  bool GetPreferredCodecsInUse() { return mPreferredCodecsInUse; }

 private:
  virtual ~RTCRtpTransceiver();
  void InitAudio();
  void InitVideo(const TrackingId& aRecvTrackingId);
  void InitConduitControl();
  void StopImpl();
  void StopTransceiving();

  nsCOMPtr<nsPIDOMWindowInner> mWindow;
  RefPtr<PeerConnectionImpl> mPc;
  RefPtr<MediaTransportHandler> mTransportHandler;
  const std::string mTransceiverId;
  // Copy of latest from the JSEP engine.
  JsepTransceiver mJsepTransceiver;
  nsCOMPtr<nsISerialEventTarget> mStsThread;
  // state for webrtc.org that is shared between all transceivers
  RefPtr<WebrtcCallWrapper> mCallWrapper;
  RefPtr<MediaStreamTrack> mSendTrack;
  RefPtr<RTCStatsIdGenerator> mIdGenerator;
  RefPtr<MediaSessionConduit> mConduit;
  // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
  // an RTCDtlsTransport.  They are always the same, so we'll store it
  // here.
  RefPtr<RTCDtlsTransport> mDtlsTransport;
  // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
  // a last stable state RTCDtlsTransport.  They are always the same, so
  // we'll store it here.
  RefPtr<RTCDtlsTransport> mLastStableDtlsTransport;
  RefPtr<RTCRtpReceiver> mReceiver;
  RefPtr<RTCRtpSender> mSender;
  RTCRtpTransceiverDirection mDirection = RTCRtpTransceiverDirection::Sendrecv;
  Nullable<RTCRtpTransceiverDirection> mCurrentDirection;
  bool mStopped = false;
  bool mStopping = false;
  bool mShutdown = false;
  bool mHasBeenUsedToSend = false;
  PrincipalPrivacy mPrincipalPrivacy;
  bool mShouldRemove = false;
  bool mHasTransport = false;
  bool mIsVideo;
  // This is really nasty. Most of the time, PeerConnectionImpl needs to be in
  // charge of unlinking each RTCRtpTransceiver, because it needs to perform
  // stats queries on its way out, which requires all of the RTCRtpTransceivers
  // (and their transitive dependencies) to stick around until those stats
  // queries are finished. However, when an RTCRtpTransceiver is removed from
  // the PeerConnectionImpl due to negotiation, the PeerConnectionImpl
  // releases its reference, which means the PeerConnectionImpl cannot be in
  // charge of the unlink anymore. We cannot do the unlink when this reference
  // is released either, because RTCRtpTransceiver might have some work it needs
  // to do first. Also, JS may be maintaining a reference to the
  // RTCRtpTransceiver (or one of its dependencies), which means it must remain
  // fully functional after it is removed (meaning it cannot release any of its
  // dependencies, or vice versa).
  bool mHandlingUnlink = false;
  std::string mTransportId;

  Canonical<std::string> mMid;
  Canonical<std::string> mSyncGroup;

  // Preferred codecs to be negotiated set by calling
  // setCodecPreferences.
  std::vector<UniquePtr<JsepCodecDescription>> mPreferredCodecs;
  // Identifies if a preferred list and order of codecs is to be used.
  // This is true if setCodecPreferences was called successfully and passed
  // codecs (not empty).
  bool mPreferredCodecsInUse = false;
};

}  // namespace dom

}  // namespace mozilla

#endif  // TRANSCEIVERIMPL_H_
