/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <memory>

#include <api/peer_connection_interface.h>
#include <json/json.h>

#include "common/libs/utils/result.h"
#include "host/frontend/webrtc/libcommon/peer_signaling_handler.h"

namespace cuttlefish {
namespace webrtc_streaming {

// Creating the peer connection is different on the client and device, but for
// both the pc needs to be created during the signaling process.
class PeerConnectionBuilder {
 public:
  virtual ~PeerConnectionBuilder() = default;
  virtual Result<rtc::scoped_refptr<webrtc::PeerConnectionInterface>> Build(
      webrtc::PeerConnectionObserver& observer,
      const std::vector<webrtc::PeerConnectionInterface::IceServer>&
          per_connection_servers) = 0;
};

// Encapsulates the signaling protocol, which is mostly the same for client and
// device. Devices will create a connection controller for each new client and
// simply provide implementations of the callbacks in
// ConnectionController::Observer. Clients must additionally call RequestOffer
// to start the signaling process.
class ConnectionController : public webrtc::PeerConnectionObserver {
 public:
  // These callbacks will be called from the signaling thread. Implementations
  // should return as soon as possible, particularly not blocking on IO.
  // Implementations must never destroy the ConnectionController object from
  // inside these callbacks as that would lead to undefined behavior.
  // TODO (b/240578845): This avoids having to create an extra thread per
  // client just to monitor changes in the device side, but opens problems by
  // allowing it to react to state changes on a peer connection callback. The
  // device already has code to avoid these issues, but the ideal solution
  // would be for this callback to be posted to a thread or not to be used at
  // all.
  class Observer {
   public:
    virtual ~Observer() = default;
    virtual void OnConnectionStateChange(
        Result<webrtc::PeerConnectionInterface::PeerConnectionState>
            status) = 0;

    // Called when new media tracks are added to the peer connection.
    virtual void OnTrack(
        rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) = 0;
    // Called when media tracks are removed from the peer connection.
    virtual void OnRemoveTrack(
        rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) = 0;
    // Called when a data channel is added to the peer connection.
    virtual void OnDataChannel(
        rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) = 0;
  };

  ConnectionController(PeerSignalingHandler& sig_handler,
                       PeerConnectionBuilder& connection_builder,
                       Observer& observer);
  ~ConnectionController() override = default;

  // Sends a request-offer message to the peer to kickstart the signaling
  // process.
  Result<void> RequestOffer(
      const std::vector<webrtc::PeerConnectionInterface::IceServer>&
          ice_servers);

  // This class doesn't poll for signaling messages from the server, instead
  // users must do that themselves and provide them here for the connection
  // controller to process them. As the result of this processing some callbacks
  // may be called or new messages may be sent to the peer, most likely after
  // the function returns, but that's not guaranteed.
  void HandleSignalingMessage(const Json::Value& msg);

  rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection() {
    return peer_connection_;
  }

  // webrtc::PeerConnectionObserver implementation
  void OnSignalingChange(
      webrtc::PeerConnectionInterface::SignalingState new_state) override;
  void OnAddStream(
      rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
  void OnRemoveStream(
      rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
  void OnDataChannel(
      rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override;
  void OnRenegotiationNeeded() override;
  void OnStandardizedIceConnectionChange(
      webrtc::PeerConnectionInterface::IceConnectionState new_state) override;
  void OnConnectionChange(
      webrtc::PeerConnectionInterface::PeerConnectionState new_state) override;
  void OnIceGatheringChange(
      webrtc::PeerConnectionInterface::IceGatheringState new_state) override;
  void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
  void OnIceCandidateError(const std::string& address, int port,
                           const std::string& url, int error_code,
                           const std::string& error_text) override;
  void OnIceCandidatesRemoved(
      const std::vector<cricket::Candidate>& candidates) override;
  // The following can be overridden but are not needed by either the device or
  // client at the moement. void OnIceConnectionReceivingChange(bool receiving)
  // override; void OnIceSelectedCandidatePairChanged( const
  // cricket::CandidatePairChangeEvent& event) override; void OnAddTrack(
  // rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
  // const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
  // streams) override;
  // void OnInterestingUsage(int usage_pattern) override;
  void OnTrack(
      rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) override;
  void OnRemoveTrack(
      rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;

 private:
  friend class CreateSessionDescriptionObserverIntermediate;
  friend class SetSessionDescriptionObserverIntermediate;
  friend class SetRemoteDescriptionObserverIntermediate;
  void CreateOffer();
  void AddPendingIceCandidates();
  void FailConnection(const std::string& message);

  Result<void> HandleSignalingMessageInner(const Json::Value& msg);
  Result<void> OnOfferRequestMsg(
      const std::vector<webrtc::PeerConnectionInterface::IceServer>&
          ice_servers);
  Result<void> OnOfferMsg(
      std::unique_ptr<webrtc::SessionDescriptionInterface> offer);
  Result<void> OnAnswerMsg(
      std::unique_ptr<webrtc::SessionDescriptionInterface> offer);
  Result<void> OnIceCandidateMsg(
      std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate);
  Result<void> OnErrorMsg(const std::string& msg);

  webrtc::CreateSessionDescriptionObserver* ThisAsCreateSDPObserver();
  webrtc::SetSessionDescriptionObserver* ThisAsSetSDPObserver();
  rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>
  ThisAsSetRemoteSDPObserver();

  Result<void> OnCreateSDPSuccess(webrtc::SessionDescriptionInterface* desc);
  void OnCreateSDPFailure(const webrtc::RTCError& error);
  void OnSetLocalDescriptionSuccess();
  void OnSetLocalDescriptionFailure(const webrtc::RTCError& error);
  void OnSetRemoteDescriptionComplete(const webrtc::RTCError& error);

  PeerSignalingHandler& sig_handler_;
  PeerConnectionBuilder& connection_builder_;
  Observer& observer_;

  rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
  std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
      pending_ice_candidates_;

  // To await for a connection to be established:
  std::mutex status_mtx_;
  std::condition_variable status_cond_var_;
  Result<webrtc::PeerConnectionInterface::PeerConnectionState>
      connection_status_;
};

}  // namespace webrtc_streaming
}  // namespace cuttlefish
