/*
 *  Copyright 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#import "RTCPeerConnection+Private.h"

#import "RTCConfiguration+Private.h"
#import "RTCDataChannel+Private.h"
#import "RTCIceCandidate+Private.h"
#import "RTCIceCandidateErrorEvent+Private.h"
#import "RTCLegacyStatsReport+Private.h"
#import "RTCMediaConstraints+Private.h"
#import "RTCMediaStream+Private.h"
#import "RTCMediaStreamTrack+Private.h"
#import "RTCPeerConnectionFactory+Private.h"
#import "RTCRtpReceiver+Private.h"
#import "RTCRtpSender+Private.h"
#import "RTCRtpTransceiver+Private.h"
#import "RTCSessionDescription+Private.h"
#import "base/RTCLogging.h"
#import "helpers/NSString+StdString.h"

#include <memory>

#include "api/jsep.h"
#include "api/rtc_event_log_output_file.h"
#include "api/set_local_description_observer_interface.h"
#include "api/set_remote_description_observer_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/system/plan_b_only.h"
#include "sdk/objc/native/api/ssl_certificate_verifier.h"

NSString *const kRTCPeerConnectionErrorDomain =
    @"org.webrtc.RTC_OBJC_TYPE(RTCPeerConnection)";
int const kRTCPeerConnnectionSessionDescriptionError = -1;

namespace {

class SetSessionDescriptionObserver
    : public webrtc::SetLocalDescriptionObserverInterface,
      public webrtc::SetRemoteDescriptionObserverInterface {
 public:
  SetSessionDescriptionObserver(
      RTCSetSessionDescriptionCompletionHandler completionHandler) {
    completion_handler_ = completionHandler;
  }

  virtual void OnSetLocalDescriptionComplete(webrtc::RTCError error) override {
    OnCompelete(error);
  }

  virtual void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override {
    OnCompelete(error);
  }

 private:
  void OnCompelete(webrtc::RTCError error) {
    RTC_DCHECK(completion_handler_ != nil);
    if (error.ok()) {
      completion_handler_(nil);
    } else {
      // TODO(hta): Add handling of error.type()
      NSString *str = [NSString stringForStdString:error.message()];
      NSError *err =
          [NSError errorWithDomain:kRTCPeerConnectionErrorDomain
                              code:kRTCPeerConnnectionSessionDescriptionError
                          userInfo:@{NSLocalizedDescriptionKey : str}];
      completion_handler_(err);
    }
    completion_handler_ = nil;
  }
  RTCSetSessionDescriptionCompletionHandler completion_handler_;
};

}  // anonymous namespace

namespace webrtc {

class CreateSessionDescriptionObserverAdapter
    : public CreateSessionDescriptionObserver {
 public:
  CreateSessionDescriptionObserverAdapter(void (^completionHandler)(
      RTC_OBJC_TYPE(RTCSessionDescription) * sessionDescription,
      NSError *error)) {
    completion_handler_ = completionHandler;
  }

  ~CreateSessionDescriptionObserverAdapter() override {
    completion_handler_ = nil;
  }

  void OnSuccess(SessionDescriptionInterface *desc) override {
    RTC_DCHECK(completion_handler_);
    std::unique_ptr<webrtc::SessionDescriptionInterface> description =
        std::unique_ptr<webrtc::SessionDescriptionInterface>(desc);
    RTC_OBJC_TYPE(RTCSessionDescription) *session =
        [[RTC_OBJC_TYPE(RTCSessionDescription) alloc]
            initWithNativeDescription:description.get()];
    completion_handler_(session, nil);
    completion_handler_ = nil;
  }

  void OnFailure(RTCError error) override {
    RTC_DCHECK(completion_handler_);
    // TODO(hta): Add handling of error.type()
    NSString *str = [NSString stringForStdString:error.message()];
    NSError *err =
        [NSError errorWithDomain:kRTCPeerConnectionErrorDomain
                            code:kRTCPeerConnnectionSessionDescriptionError
                        userInfo:@{NSLocalizedDescriptionKey : str}];
    completion_handler_(nil, err);
    completion_handler_ = nil;
  }

 private:
  void (^completion_handler_)(RTC_OBJC_TYPE(RTCSessionDescription) *
                                  sessionDescription,
                              NSError *error);
};

PeerConnectionDelegateAdapter::PeerConnectionDelegateAdapter(
    RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection) {
  peer_connection_ = peerConnection;
}

PeerConnectionDelegateAdapter::~PeerConnectionDelegateAdapter() {
  peer_connection_ = nil;
}

void PeerConnectionDelegateAdapter::OnSignalingChange(
    PeerConnectionInterface::SignalingState new_state) {
  RTCSignalingState state = [[RTC_OBJC_TYPE(RTCPeerConnection) class]
      signalingStateForNativeState:new_state];
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  [delegate peerConnection:peer_connection didChangeSignalingState:state];
}

void PeerConnectionDelegateAdapter::OnAddStream(
    webrtc::scoped_refptr<MediaStreamInterface> stream) {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }

  RTC_OBJC_TYPE(RTCMediaStream) *media_stream = [[RTC_OBJC_TYPE(RTCMediaStream)
      alloc] initWithFactory:peer_connection.factory nativeMediaStream:stream];

  [delegate peerConnection:peer_connection didAddStream:media_stream];
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
}

void PeerConnectionDelegateAdapter::OnRemoveStream(
    webrtc::scoped_refptr<MediaStreamInterface> stream) {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTC_OBJC_TYPE(RTCMediaStream) *mediaStream = [[RTC_OBJC_TYPE(RTCMediaStream)
      alloc] initWithFactory:peer_connection.factory nativeMediaStream:stream];

  [delegate peerConnection:peer_connection didRemoveStream:mediaStream];
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
}

void PeerConnectionDelegateAdapter::OnTrack(
    webrtc::scoped_refptr<RtpTransceiverInterface> nativeTransceiver) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }

  RTC_OBJC_TYPE(RTCRtpTransceiver) *transceiver =
      [[RTC_OBJC_TYPE(RTCRtpTransceiver) alloc]
               initWithFactory:peer_connection.factory
          nativeRtpTransceiver:nativeTransceiver];
  if ([delegate respondsToSelector:@selector(peerConnection:
                                       didStartReceivingOnTransceiver:)]) {
    [delegate peerConnection:peer_connection
        didStartReceivingOnTransceiver:transceiver];
  }
}

void PeerConnectionDelegateAdapter::OnDataChannel(
    webrtc::scoped_refptr<DataChannelInterface> data_channel) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTC_OBJC_TYPE(RTCDataChannel) *dataChannel = [[RTC_OBJC_TYPE(RTCDataChannel)
      alloc] initWithFactory:peer_connection.factory
           nativeDataChannel:data_channel];
  [delegate peerConnection:peer_connection didOpenDataChannel:dataChannel];
}

void PeerConnectionDelegateAdapter::OnRenegotiationNeeded() {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  [delegate peerConnectionShouldNegotiate:peer_connection];
}

void PeerConnectionDelegateAdapter::OnIceConnectionChange(
    PeerConnectionInterface::IceConnectionState new_state) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTCIceConnectionState state = [RTC_OBJC_TYPE(RTCPeerConnection)
      iceConnectionStateForNativeState:new_state];
  [delegate peerConnection:peer_connection didChangeIceConnectionState:state];
}

void PeerConnectionDelegateAdapter::OnStandardizedIceConnectionChange(
    PeerConnectionInterface::IceConnectionState new_state) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  if ([delegate respondsToSelector:@selector
                (peerConnection:didChangeStandardizedIceConnectionState:)]) {
    RTCIceConnectionState state = [RTC_OBJC_TYPE(RTCPeerConnection)
        iceConnectionStateForNativeState:new_state];
    [delegate peerConnection:peer_connection
        didChangeStandardizedIceConnectionState:state];
  }
}

void PeerConnectionDelegateAdapter::OnConnectionChange(
    PeerConnectionInterface::PeerConnectionState new_state) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  if ([delegate respondsToSelector:@selector(peerConnection:
                                       didChangeConnectionState:)]) {
    RTCPeerConnectionState state = [RTC_OBJC_TYPE(RTCPeerConnection)
        connectionStateForNativeState:new_state];
    [delegate peerConnection:peer_connection didChangeConnectionState:state];
  }
}

void PeerConnectionDelegateAdapter::OnIceGatheringChange(
    PeerConnectionInterface::IceGatheringState new_state) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTCIceGatheringState state = [[RTC_OBJC_TYPE(RTCPeerConnection) class]
      iceGatheringStateForNativeState:new_state];
  [delegate peerConnection:peer_connection didChangeIceGatheringState:state];
}

void PeerConnectionDelegateAdapter::OnIceCandidate(
    const IceCandidate *candidate) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTC_OBJC_TYPE(RTCIceCandidate) *iceCandidate =
      [[RTC_OBJC_TYPE(RTCIceCandidate) alloc]
          initWithNativeCandidate:candidate];
  [delegate peerConnection:peer_connection
      didGenerateIceCandidate:iceCandidate];
}

void PeerConnectionDelegateAdapter::OnIceCandidateError(
    const std::string &address,
    int port,
    const std::string &url,
    int error_code,
    const std::string &error_text) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) *event =
      [[RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) alloc]
          initWithAddress:address
                     port:port
                      url:url
                errorCode:error_code
                errorText:error_text];
  if ([delegate respondsToSelector:@selector(peerConnection:
                                       didFailToGatherIceCandidate:)]) {
    [delegate peerConnection:peer_connection didFailToGatherIceCandidate:event];
  }
}

void PeerConnectionDelegateAdapter::OnIceCandidateRemoved(
    const webrtc::IceCandidate *c) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  NSMutableArray *ice_candidates = [NSMutableArray arrayWithCapacity:1];
  RTC_OBJC_TYPE(RTCIceCandidate) *ice_candidate =
      [[RTC_OBJC_TYPE(RTCIceCandidate) alloc] initWithNativeCandidate:c];
  [ice_candidates addObject:ice_candidate];
  [delegate peerConnection:peer_connection
      didRemoveIceCandidates:ice_candidates];
}

void PeerConnectionDelegateAdapter::OnIceSelectedCandidatePairChanged(
    const webrtc::CandidatePairChangeEvent &event) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  const auto &selected_pair = event.selected_candidate_pair;
  IceCandidate local_candidate_wrapper(
      event.transport_name, -1, selected_pair.local_candidate());
  RTC_OBJC_TYPE(RTCIceCandidate) *local_candidate =
      [[RTC_OBJC_TYPE(RTCIceCandidate) alloc]
          initWithNativeCandidate:&local_candidate_wrapper];
  IceCandidate remote_candidate_wrapper(
      event.transport_name, -1, selected_pair.remote_candidate());
  RTC_OBJC_TYPE(RTCIceCandidate) *remote_candidate =
      [[RTC_OBJC_TYPE(RTCIceCandidate) alloc]
          initWithNativeCandidate:&remote_candidate_wrapper];
  NSString *nsstr_reason = [NSString stringForStdString:event.reason];
  if ([delegate respondsToSelector:@selector
                (peerConnection:
                    didChangeLocalCandidate:remoteCandidate:lastReceivedMs
                                           :changeReason:)]) {
    [delegate peerConnection:peer_connection
        didChangeLocalCandidate:local_candidate
                remoteCandidate:remote_candidate
                 lastReceivedMs:event.last_data_received_ms
                   changeReason:nsstr_reason];
  }
}

void PeerConnectionDelegateAdapter::OnAddTrack(
    webrtc::scoped_refptr<RtpReceiverInterface> receiver,
    const std::vector<webrtc::scoped_refptr<MediaStreamInterface>> &streams) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }

  if ([delegate respondsToSelector:@selector(peerConnection:
                                             didAddReceiver:streams:)]) {
    NSMutableArray *mediaStreams =
        [NSMutableArray arrayWithCapacity:streams.size()];
    for (const auto &nativeStream : streams) {
      RTC_OBJC_TYPE(RTCMediaStream) *mediaStream =
          [[RTC_OBJC_TYPE(RTCMediaStream) alloc]
                initWithFactory:peer_connection.factory
              nativeMediaStream:nativeStream];
      [mediaStreams addObject:mediaStream];
    }
    RTC_OBJC_TYPE(RTCRtpReceiver) *rtpReceiver = [[RTC_OBJC_TYPE(RTCRtpReceiver)
        alloc] initWithFactory:peer_connection.factory
             nativeRtpReceiver:receiver];

    [delegate peerConnection:peer_connection
              didAddReceiver:rtpReceiver
                     streams:mediaStreams];
  }
}

void PeerConnectionDelegateAdapter::OnRemoveTrack(
    webrtc::scoped_refptr<RtpReceiverInterface> receiver) {
  RTC_OBJC_TYPE(RTCPeerConnection) *peer_connection = peer_connection_;
  if (peer_connection == nil) {
    return;
  }
  id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)> delegate =
      peer_connection.delegate;
  if (delegate == nil) {
    return;
  }
  if ([delegate respondsToSelector:@selector(peerConnection:
                                          didRemoveReceiver:)]) {
    RTC_OBJC_TYPE(RTCRtpReceiver) *rtpReceiver = [[RTC_OBJC_TYPE(RTCRtpReceiver)
        alloc] initWithFactory:peer_connection.factory
             nativeRtpReceiver:receiver];
    [delegate peerConnection:peer_connection didRemoveReceiver:rtpReceiver];
  }
}

}  // namespace webrtc

@implementation RTC_OBJC_TYPE (RTCPeerConnection) {
  RTC_OBJC_TYPE(RTCPeerConnectionFactory) * _factory;
  NSMutableArray<RTC_OBJC_TYPE(RTCMediaStream) *> *_localStreams;
  std::unique_ptr<webrtc::PeerConnectionDelegateAdapter> _observer;
  webrtc::scoped_refptr<webrtc::PeerConnectionInterface> _peerConnection;
  std::unique_ptr<webrtc::MediaConstraints> _nativeConstraints;
  BOOL _hasStartedRtcEventLog;
}

@synthesize delegate = _delegate;
@synthesize factory = _factory;

- (nullable instancetype)
        initWithFactory:(RTC_OBJC_TYPE(RTCPeerConnectionFactory) *)factory
          configuration:(RTC_OBJC_TYPE(RTCConfiguration) *)configuration
            constraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints
    certificateVerifier:(nullable id<RTC_OBJC_TYPE(RTCSSLCertificateVerifier)>)
                            certificateVerifier
               delegate:(id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)>)delegate {
  NSParameterAssert(factory);
  std::unique_ptr<webrtc::PeerConnectionDependencies> dependencies =
      std::make_unique<webrtc::PeerConnectionDependencies>(nullptr);
  if (certificateVerifier != nil) {
    dependencies->tls_cert_verifier =
        webrtc::ObjCToNativeCertificateVerifier(certificateVerifier);
  }
  return [self initWithDependencies:factory
                      configuration:configuration
                        constraints:constraints
                       dependencies:std::move(dependencies)
                           delegate:delegate];
}

- (nullable instancetype)
    initWithDependencies:(RTC_OBJC_TYPE(RTCPeerConnectionFactory) *)factory
           configuration:(RTC_OBJC_TYPE(RTCConfiguration) *)configuration
             constraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints
            dependencies:(std::unique_ptr<webrtc::PeerConnectionDependencies>)
                             dependencies
                delegate:
                    (id<RTC_OBJC_TYPE(RTCPeerConnectionDelegate)>)delegate {
  NSParameterAssert(factory);
  NSParameterAssert(dependencies.get());
  std::unique_ptr<webrtc::PeerConnectionInterface::RTCConfiguration> config(
      [configuration createNativeConfiguration]);
  if (!config) {
    return nil;
  }
  self = [super init];
  if (self) {
    _observer.reset(new webrtc::PeerConnectionDelegateAdapter(self));
    _nativeConstraints = constraints.nativeConstraints;
    CopyConstraintsIntoRtcConfiguration(_nativeConstraints.get(), config.get());

    webrtc::PeerConnectionDependencies deps = std::move(*dependencies);
    deps.observer = _observer.get();
    auto result = factory.nativeFactory->CreatePeerConnectionOrError(
        *config, std::move(deps));

    if (!result.ok()) {
      return nil;
    }
    _peerConnection = result.MoveValue();
    _factory = factory;
    _localStreams = [[NSMutableArray alloc] init];
    _delegate = delegate;
  }
  return self;
}

- (NSArray<RTC_OBJC_TYPE(RTCMediaStream) *> *)localStreams {
  return [_localStreams copy];
}

- (RTC_OBJC_TYPE(RTCSessionDescription) *)localDescription {
  // It's only safe to operate on SessionDescriptionInterface on the signaling
  // thread.
  return _peerConnection->signaling_thread()->BlockingCall([self] {
    const webrtc::SessionDescriptionInterface *description =
        _peerConnection->local_description();
    return description ? [[RTC_OBJC_TYPE(RTCSessionDescription) alloc]
                             initWithNativeDescription:description] :
                         nil;
  });
}

- (RTC_OBJC_TYPE(RTCSessionDescription) *)remoteDescription {
  // It's only safe to operate on SessionDescriptionInterface on the signaling
  // thread.
  return _peerConnection->signaling_thread()->BlockingCall([self] {
    const webrtc::SessionDescriptionInterface *description =
        _peerConnection->remote_description();
    return description ? [[RTC_OBJC_TYPE(RTCSessionDescription) alloc]
                             initWithNativeDescription:description] :
                         nil;
  });
}

- (RTCSignalingState)signalingState {
  return [[self class]
      signalingStateForNativeState:_peerConnection->signaling_state()];
}

- (RTCIceConnectionState)iceConnectionState {
  return [[self class]
      iceConnectionStateForNativeState:_peerConnection->ice_connection_state()];
}

- (RTCPeerConnectionState)connectionState {
  return [[self class]
      connectionStateForNativeState:_peerConnection->peer_connection_state()];
}

- (RTCIceGatheringState)iceGatheringState {
  return [[self class]
      iceGatheringStateForNativeState:_peerConnection->ice_gathering_state()];
}

- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCConfiguration) *)configuration {
  std::unique_ptr<webrtc::PeerConnectionInterface::RTCConfiguration> config(
      [configuration createNativeConfiguration]);
  if (!config) {
    return NO;
  }
  CopyConstraintsIntoRtcConfiguration(_nativeConstraints.get(), config.get());
  return _peerConnection->SetConfiguration(*config).ok();
}

- (RTC_OBJC_TYPE(RTCConfiguration) *)configuration {
  webrtc::PeerConnectionInterface::RTCConfiguration config =
      _peerConnection->GetConfiguration();
  return [[RTC_OBJC_TYPE(RTCConfiguration) alloc]
      initWithNativeConfiguration:config];
}

- (void)close {
  _peerConnection->Close();
}

- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate {
  std::unique_ptr<const webrtc::IceCandidate> iceCandidate(
      candidate.nativeCandidate);
  _peerConnection->AddIceCandidate(iceCandidate.get());
}
- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate
      completionHandler:(void (^)(NSError *_Nullable error))completionHandler {
  RTC_DCHECK(completionHandler != nil);
  _peerConnection->AddIceCandidate(
      candidate.nativeCandidate, [completionHandler](const auto &error) {
        if (error.ok()) {
          completionHandler(nil);
        } else {
          NSString *str = [NSString stringForStdString:error.message()];
          NSError *err =
              [NSError errorWithDomain:kRTCPeerConnectionErrorDomain
                                  code:static_cast<NSInteger>(error.type())
                              userInfo:@{NSLocalizedDescriptionKey : str}];
          completionHandler(err);
        }
      });
}
- (void)removeIceCandidates:
    (NSArray<RTC_OBJC_TYPE(RTCIceCandidate) *> *)iceCandidates {
  for (RTC_OBJC_TYPE(RTCIceCandidate) * iceCandidate in iceCandidates) {
    std::unique_ptr<webrtc::IceCandidate> candidate(
        iceCandidate.nativeCandidate);
    if (candidate) {
      _peerConnection->RemoveIceCandidate(candidate.get());
    }
  }
}

- (void)addStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  if (!_peerConnection->AddStream(stream.nativeMediaStream.get())) {
    RTCLogError(@"Failed to add stream: %@", stream);
    return;
  }
  [_localStreams addObject:stream];
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
}

- (void)removeStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  _peerConnection->RemoveStream(stream.nativeMediaStream.get());
  [_localStreams removeObject:stream];
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
}

- (nullable RTC_OBJC_TYPE(RTCRtpSender) *)
     addTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track
    streamIds:(NSArray<NSString *> *)streamIds {
  std::vector<std::string> nativeStreamIds;
  for (NSString *streamId in streamIds) {
    nativeStreamIds.push_back([streamId UTF8String]);
  }
  webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpSenderInterface>>
      nativeSenderOrError =
          _peerConnection->AddTrack(track.nativeTrack, nativeStreamIds);
  if (!nativeSenderOrError.ok()) {
    RTCLogError(@"Failed to add track %@: %s",
                track,
                nativeSenderOrError.error().message());
    return nil;
  }
  return [[RTC_OBJC_TYPE(RTCRtpSender) alloc]
      initWithFactory:self.factory
      nativeRtpSender:nativeSenderOrError.MoveValue()];
}

- (BOOL)removeTrack:(RTC_OBJC_TYPE(RTCRtpSender) *)sender {
  bool result =
      _peerConnection->RemoveTrackOrError(sender.nativeRtpSender).ok();
  if (!result) {
    RTCLogError(@"Failed to remote track %@", sender);
  }
  return result;
}

- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverWithTrack:
    (RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track {
  return [self addTransceiverWithTrack:track
                                  init:[[RTC_OBJC_TYPE(RTCRtpTransceiverInit)
                                           alloc] init]];
}

- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)
    addTransceiverWithTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track
                       init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init {
  webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>>
      nativeTransceiverOrError =
          _peerConnection->AddTransceiver(track.nativeTrack, init.nativeInit);
  if (!nativeTransceiverOrError.ok()) {
    RTCLogError(@"Failed to add transceiver %@: %s",
                track,
                nativeTransceiverOrError.error().message());
    return nil;
  }
  return [[RTC_OBJC_TYPE(RTCRtpTransceiver) alloc]
           initWithFactory:self.factory
      nativeRtpTransceiver:nativeTransceiverOrError.MoveValue()];
}

- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverOfType:
    (RTCRtpMediaType)mediaType {
  return [self
      addTransceiverOfType:mediaType
                      init:[[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]];
}

- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)
    addTransceiverOfType:(RTCRtpMediaType)mediaType
                    init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init {
  webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>>
      nativeTransceiverOrError = _peerConnection->AddTransceiver(
          [RTC_OBJC_TYPE(RTCRtpReceiver) nativeMediaTypeForMediaType:mediaType],
          init.nativeInit);
  if (!nativeTransceiverOrError.ok()) {
    RTCLogError(@"Failed to add transceiver %@: %s",
                [RTC_OBJC_TYPE(RTCRtpReceiver) stringForMediaType:mediaType],
                nativeTransceiverOrError.error().message());
    return nil;
  }
  return [[RTC_OBJC_TYPE(RTCRtpTransceiver) alloc]
           initWithFactory:self.factory
      nativeRtpTransceiver:nativeTransceiverOrError.MoveValue()];
}

- (void)restartIce {
  _peerConnection->RestartIce();
}

- (void)offerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints
          completionHandler:
              (RTCCreateSessionDescriptionCompletionHandler)completionHandler {
  RTC_DCHECK(completionHandler != nil);
  webrtc::scoped_refptr<webrtc::CreateSessionDescriptionObserverAdapter>
      observer = webrtc::make_ref_counted<
          webrtc::CreateSessionDescriptionObserverAdapter>(completionHandler);
  webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
  CopyConstraintsIntoOfferAnswerOptions(constraints.nativeConstraints.get(),
                                        &options);

  _peerConnection->CreateOffer(observer.get(), options);
}

- (void)answerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints
           completionHandler:
               (RTCCreateSessionDescriptionCompletionHandler)completionHandler {
  RTC_DCHECK(completionHandler != nil);
  webrtc::scoped_refptr<webrtc::CreateSessionDescriptionObserverAdapter>
      observer = webrtc::make_ref_counted<
          webrtc::CreateSessionDescriptionObserverAdapter>(completionHandler);
  webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
  CopyConstraintsIntoOfferAnswerOptions(constraints.nativeConstraints.get(),
                                        &options);

  _peerConnection->CreateAnswer(observer.get(), options);
}

- (void)setLocalDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp
          completionHandler:
              (RTCSetSessionDescriptionCompletionHandler)completionHandler {
  RTC_DCHECK(completionHandler != nil);
  webrtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface> observer =
      webrtc::make_ref_counted<::SetSessionDescriptionObserver>(
          completionHandler);
  _peerConnection->SetLocalDescription(sdp.nativeDescription, observer);
}

- (void)setLocalDescriptionWithCompletionHandler:
    (RTCSetSessionDescriptionCompletionHandler)completionHandler {
  RTC_DCHECK(completionHandler != nil);
  webrtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface> observer =
      webrtc::make_ref_counted<::SetSessionDescriptionObserver>(
          completionHandler);
  _peerConnection->SetLocalDescription(observer);
}

- (void)setRemoteDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp
           completionHandler:
               (RTCSetSessionDescriptionCompletionHandler)completionHandler {
  RTC_DCHECK(completionHandler != nil);
  webrtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>
      observer = webrtc::make_ref_counted<::SetSessionDescriptionObserver>(
          completionHandler);
  _peerConnection->SetRemoteDescription(sdp.nativeDescription, observer);
}

- (BOOL)setBweMinBitrateBps:(nullable NSNumber *)minBitrateBps
          currentBitrateBps:(nullable NSNumber *)currentBitrateBps
              maxBitrateBps:(nullable NSNumber *)maxBitrateBps {
  webrtc::BitrateSettings params;
  if (minBitrateBps != nil) {
    params.min_bitrate_bps = std::optional<int>(minBitrateBps.intValue);
  }
  if (currentBitrateBps != nil) {
    params.start_bitrate_bps = std::optional<int>(currentBitrateBps.intValue);
  }
  if (maxBitrateBps != nil) {
    params.max_bitrate_bps = std::optional<int>(maxBitrateBps.intValue);
  }
  return _peerConnection->SetBitrate(params).ok();
}

- (BOOL)startRtcEventLogWithFilePath:(NSString *)filePath
                      maxSizeInBytes:(int64_t)maxSizeInBytes {
  RTC_DCHECK(filePath.length);
  RTC_DCHECK_GT(maxSizeInBytes, 0);
  RTC_DCHECK(!_hasStartedRtcEventLog);
  if (_hasStartedRtcEventLog) {
    RTCLogError(@"Event logging already started.");
    return NO;
  }
  FILE *f = fopen(filePath.UTF8String, "wb");
  if (!f) {
    RTCLogError(@"Error opening file: %@. Error: %d", filePath, errno);
    return NO;
  }
  // TODO(eladalon): It would be better to not allow negative values into PC.
  const size_t max_size = (maxSizeInBytes < 0) ?
      webrtc::RtcEventLog::kUnlimitedOutput :
      webrtc::saturated_cast<size_t>(maxSizeInBytes);

  _hasStartedRtcEventLog = _peerConnection->StartRtcEventLog(
      std::make_unique<webrtc::RtcEventLogOutputFile>(f, max_size));
  return _hasStartedRtcEventLog;
}

- (void)stopRtcEventLog {
  _peerConnection->StopRtcEventLog();
  _hasStartedRtcEventLog = NO;
}

- (RTC_OBJC_TYPE(RTCRtpSender) *)senderWithKind:(NSString *)kind
                                       streamId:(NSString *)streamId {
  std::string nativeKind = [NSString stdStringForString:kind];
  std::string nativeStreamId = [NSString stdStringForString:streamId];
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  webrtc::scoped_refptr<webrtc::RtpSenderInterface> nativeSender(
      _peerConnection->CreateSender(nativeKind, nativeStreamId));
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
  return nativeSender ?
      [[RTC_OBJC_TYPE(RTCRtpSender) alloc] initWithFactory:self.factory
                                           nativeRtpSender:nativeSender] :
      nil;
}

- (NSArray<RTC_OBJC_TYPE(RTCRtpSender) *> *)senders {
  std::vector<webrtc::scoped_refptr<webrtc::RtpSenderInterface>> nativeSenders(
      _peerConnection->GetSenders());
  NSMutableArray *senders = [[NSMutableArray alloc] init];
  for (const auto &nativeSender : nativeSenders) {
    RTC_OBJC_TYPE(RTCRtpSender) *sender =
        [[RTC_OBJC_TYPE(RTCRtpSender) alloc] initWithFactory:self.factory
                                             nativeRtpSender:nativeSender];
    [senders addObject:sender];
  }
  return senders;
}

- (NSArray<RTC_OBJC_TYPE(RTCRtpReceiver) *> *)receivers {
  std::vector<webrtc::scoped_refptr<webrtc::RtpReceiverInterface>>
      nativeReceivers(_peerConnection->GetReceivers());
  NSMutableArray *receivers = [[NSMutableArray alloc] init];
  for (const auto &nativeReceiver : nativeReceivers) {
    RTC_OBJC_TYPE(RTCRtpReceiver) *receiver =
        [[RTC_OBJC_TYPE(RTCRtpReceiver) alloc] initWithFactory:self.factory
                                             nativeRtpReceiver:nativeReceiver];
    [receivers addObject:receiver];
  }
  return receivers;
}

- (NSArray<RTC_OBJC_TYPE(RTCRtpTransceiver) *> *)transceivers {
  std::vector<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>>
      nativeTransceivers(_peerConnection->GetTransceivers());
  NSMutableArray *transceivers = [[NSMutableArray alloc] init];
  for (const auto &nativeTransceiver : nativeTransceivers) {
    RTC_OBJC_TYPE(RTCRtpTransceiver) *transceiver =
        [[RTC_OBJC_TYPE(RTCRtpTransceiver) alloc]
                 initWithFactory:self.factory
            nativeRtpTransceiver:nativeTransceiver];
    [transceivers addObject:transceiver];
  }
  return transceivers;
}

#pragma mark - Private

+ (webrtc::PeerConnectionInterface::SignalingState)nativeSignalingStateForState:
    (RTCSignalingState)state {
  switch (state) {
    case RTCSignalingStateStable:
      return webrtc::PeerConnectionInterface::kStable;
    case RTCSignalingStateHaveLocalOffer:
      return webrtc::PeerConnectionInterface::kHaveLocalOffer;
    case RTCSignalingStateHaveLocalPrAnswer:
      return webrtc::PeerConnectionInterface::kHaveLocalPrAnswer;
    case RTCSignalingStateHaveRemoteOffer:
      return webrtc::PeerConnectionInterface::kHaveRemoteOffer;
    case RTCSignalingStateHaveRemotePrAnswer:
      return webrtc::PeerConnectionInterface::kHaveRemotePrAnswer;
    case RTCSignalingStateClosed:
      return webrtc::PeerConnectionInterface::kClosed;
  }
}

+ (RTCSignalingState)signalingStateForNativeState:
    (webrtc::PeerConnectionInterface::SignalingState)nativeState {
  switch (nativeState) {
    case webrtc::PeerConnectionInterface::kStable:
      return RTCSignalingStateStable;
    case webrtc::PeerConnectionInterface::kHaveLocalOffer:
      return RTCSignalingStateHaveLocalOffer;
    case webrtc::PeerConnectionInterface::kHaveLocalPrAnswer:
      return RTCSignalingStateHaveLocalPrAnswer;
    case webrtc::PeerConnectionInterface::kHaveRemoteOffer:
      return RTCSignalingStateHaveRemoteOffer;
    case webrtc::PeerConnectionInterface::kHaveRemotePrAnswer:
      return RTCSignalingStateHaveRemotePrAnswer;
    case webrtc::PeerConnectionInterface::kClosed:
      return RTCSignalingStateClosed;
  }
}

+ (NSString *)stringForSignalingState:(RTCSignalingState)state {
  switch (state) {
    case RTCSignalingStateStable:
      return @"STABLE";
    case RTCSignalingStateHaveLocalOffer:
      return @"HAVE_LOCAL_OFFER";
    case RTCSignalingStateHaveLocalPrAnswer:
      return @"HAVE_LOCAL_PRANSWER";
    case RTCSignalingStateHaveRemoteOffer:
      return @"HAVE_REMOTE_OFFER";
    case RTCSignalingStateHaveRemotePrAnswer:
      return @"HAVE_REMOTE_PRANSWER";
    case RTCSignalingStateClosed:
      return @"CLOSED";
  }
}

+ (webrtc::PeerConnectionInterface::PeerConnectionState)
    nativeConnectionStateForState:(RTCPeerConnectionState)state {
  switch (state) {
    case RTCPeerConnectionStateNew:
      return webrtc::PeerConnectionInterface::PeerConnectionState::kNew;
    case RTCPeerConnectionStateConnecting:
      return webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting;
    case RTCPeerConnectionStateConnected:
      return webrtc::PeerConnectionInterface::PeerConnectionState::kConnected;
    case RTCPeerConnectionStateFailed:
      return webrtc::PeerConnectionInterface::PeerConnectionState::kFailed;
    case RTCPeerConnectionStateDisconnected:
      return webrtc::PeerConnectionInterface::PeerConnectionState::
          kDisconnected;
    case RTCPeerConnectionStateClosed:
      return webrtc::PeerConnectionInterface::PeerConnectionState::kClosed;
  }
}

+ (RTCPeerConnectionState)connectionStateForNativeState:
    (webrtc::PeerConnectionInterface::PeerConnectionState)nativeState {
  switch (nativeState) {
    case webrtc::PeerConnectionInterface::PeerConnectionState::kNew:
      return RTCPeerConnectionStateNew;
    case webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting:
      return RTCPeerConnectionStateConnecting;
    case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
      return RTCPeerConnectionStateConnected;
    case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
      return RTCPeerConnectionStateFailed;
    case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
      return RTCPeerConnectionStateDisconnected;
    case webrtc::PeerConnectionInterface::PeerConnectionState::kClosed:
      return RTCPeerConnectionStateClosed;
  }
}

+ (NSString *)stringForConnectionState:(RTCPeerConnectionState)state {
  switch (state) {
    case RTCPeerConnectionStateNew:
      return @"NEW";
    case RTCPeerConnectionStateConnecting:
      return @"CONNECTING";
    case RTCPeerConnectionStateConnected:
      return @"CONNECTED";
    case RTCPeerConnectionStateFailed:
      return @"FAILED";
    case RTCPeerConnectionStateDisconnected:
      return @"DISCONNECTED";
    case RTCPeerConnectionStateClosed:
      return @"CLOSED";
  }
}

+ (webrtc::PeerConnectionInterface::IceConnectionState)
    nativeIceConnectionStateForState:(RTCIceConnectionState)state {
  switch (state) {
    case RTCIceConnectionStateNew:
      return webrtc::PeerConnectionInterface::kIceConnectionNew;
    case RTCIceConnectionStateChecking:
      return webrtc::PeerConnectionInterface::kIceConnectionChecking;
    case RTCIceConnectionStateConnected:
      return webrtc::PeerConnectionInterface::kIceConnectionConnected;
    case RTCIceConnectionStateCompleted:
      return webrtc::PeerConnectionInterface::kIceConnectionCompleted;
    case RTCIceConnectionStateFailed:
      return webrtc::PeerConnectionInterface::kIceConnectionFailed;
    case RTCIceConnectionStateDisconnected:
      return webrtc::PeerConnectionInterface::kIceConnectionDisconnected;
    case RTCIceConnectionStateClosed:
      return webrtc::PeerConnectionInterface::kIceConnectionClosed;
    case RTCIceConnectionStateCount:
      return webrtc::PeerConnectionInterface::kIceConnectionMax;
  }
}

+ (RTCIceConnectionState)iceConnectionStateForNativeState:
    (webrtc::PeerConnectionInterface::IceConnectionState)nativeState {
  switch (nativeState) {
    case webrtc::PeerConnectionInterface::kIceConnectionNew:
      return RTCIceConnectionStateNew;
    case webrtc::PeerConnectionInterface::kIceConnectionChecking:
      return RTCIceConnectionStateChecking;
    case webrtc::PeerConnectionInterface::kIceConnectionConnected:
      return RTCIceConnectionStateConnected;
    case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
      return RTCIceConnectionStateCompleted;
    case webrtc::PeerConnectionInterface::kIceConnectionFailed:
      return RTCIceConnectionStateFailed;
    case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
      return RTCIceConnectionStateDisconnected;
    case webrtc::PeerConnectionInterface::kIceConnectionClosed:
      return RTCIceConnectionStateClosed;
    case webrtc::PeerConnectionInterface::kIceConnectionMax:
      return RTCIceConnectionStateCount;
  }
}

+ (NSString *)stringForIceConnectionState:(RTCIceConnectionState)state {
  switch (state) {
    case RTCIceConnectionStateNew:
      return @"NEW";
    case RTCIceConnectionStateChecking:
      return @"CHECKING";
    case RTCIceConnectionStateConnected:
      return @"CONNECTED";
    case RTCIceConnectionStateCompleted:
      return @"COMPLETED";
    case RTCIceConnectionStateFailed:
      return @"FAILED";
    case RTCIceConnectionStateDisconnected:
      return @"DISCONNECTED";
    case RTCIceConnectionStateClosed:
      return @"CLOSED";
    case RTCIceConnectionStateCount:
      return @"COUNT";
  }
}

+ (webrtc::PeerConnectionInterface::IceGatheringState)
    nativeIceGatheringStateForState:(RTCIceGatheringState)state {
  switch (state) {
    case RTCIceGatheringStateNew:
      return webrtc::PeerConnectionInterface::kIceGatheringNew;
    case RTCIceGatheringStateGathering:
      return webrtc::PeerConnectionInterface::kIceGatheringGathering;
    case RTCIceGatheringStateComplete:
      return webrtc::PeerConnectionInterface::kIceGatheringComplete;
  }
}

+ (RTCIceGatheringState)iceGatheringStateForNativeState:
    (webrtc::PeerConnectionInterface::IceGatheringState)nativeState {
  switch (nativeState) {
    case webrtc::PeerConnectionInterface::kIceGatheringNew:
      return RTCIceGatheringStateNew;
    case webrtc::PeerConnectionInterface::kIceGatheringGathering:
      return RTCIceGatheringStateGathering;
    case webrtc::PeerConnectionInterface::kIceGatheringComplete:
      return RTCIceGatheringStateComplete;
  }
}

+ (NSString *)stringForIceGatheringState:(RTCIceGatheringState)state {
  switch (state) {
    case RTCIceGatheringStateNew:
      return @"NEW";
    case RTCIceGatheringStateGathering:
      return @"GATHERING";
    case RTCIceGatheringStateComplete:
      return @"COMPLETE";
  }
}

+ (webrtc::PeerConnectionInterface::StatsOutputLevel)
    nativeStatsOutputLevelForLevel:(RTCStatsOutputLevel)level {
  switch (level) {
    case RTCStatsOutputLevelStandard:
      return webrtc::PeerConnectionInterface::kStatsOutputLevelStandard;
    case RTCStatsOutputLevelDebug:
      return webrtc::PeerConnectionInterface::kStatsOutputLevelDebug;
  }
}

- (webrtc::scoped_refptr<webrtc::PeerConnectionInterface>)nativePeerConnection {
  return _peerConnection;
}

@end
