// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/protocol/webrtc_connection_to_host.h"

#include <memory>
#include <utility>

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "components/webrtc/thread_wrapper.h"
#include "remoting/base/constants.h"
#include "remoting/protocol/client_control_dispatcher.h"
#include "remoting/protocol/client_event_dispatcher.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/message_pipe.h"
#include "remoting/protocol/transport_context.h"
#include "remoting/protocol/video_renderer.h"
#include "remoting/protocol/webrtc_audio_module.h"
#include "remoting/protocol/webrtc_audio_sink_adapter.h"
#include "remoting/protocol/webrtc_transport.h"
#include "remoting/protocol/webrtc_video_renderer_adapter.h"

namespace remoting::protocol {

WebrtcConnectionToHost::WebrtcConnectionToHost() = default;
WebrtcConnectionToHost::~WebrtcConnectionToHost() = default;

void WebrtcConnectionToHost::Connect(
    std::unique_ptr<Session> session,
    scoped_refptr<TransportContext> transport_context,
    HostEventCallback* event_callback) {
  DCHECK(client_stub_);
  DCHECK(clipboard_stub_);

  transport_ = std::make_unique<WebrtcTransport>(
      webrtc::ThreadWrapper::current(), transport_context, nullptr, this);

  if (audio_decode_task_runner_) {
    transport_->audio_module()->SetAudioTaskRunner(audio_decode_task_runner_);
  }

  session_ = std::move(session);
  session_->SetEventHandler(this);
  session_->SetTransport(transport_.get());

  event_callback_ = event_callback;

  SetState(CONNECTING, ErrorCode::OK);
}

void WebrtcConnectionToHost::Disconnect(ErrorCode error) {
  session_->Close(error, /* error_details= */ {}, FROM_HERE);
}

void WebrtcConnectionToHost::ApplyNetworkSettings(
    const NetworkSettings& settings) {
  transport_->ApplyNetworkSettings(settings);
}

ClipboardStub* WebrtcConnectionToHost::clipboard_forwarder() {
  return &clipboard_forwarder_;
}

HostStub* WebrtcConnectionToHost::host_stub() {
  return control_dispatcher_.get();
}

InputStub* WebrtcConnectionToHost::input_stub() {
  return &event_forwarder_;
}

void WebrtcConnectionToHost::set_client_stub(ClientStub* client_stub) {
  client_stub_ = client_stub;
}

void WebrtcConnectionToHost::set_clipboard_stub(ClipboardStub* clipboard_stub) {
  clipboard_stub_ = clipboard_stub;
}

void WebrtcConnectionToHost::set_video_renderer(VideoRenderer* video_renderer) {
  video_renderer_ = video_renderer;
}

void WebrtcConnectionToHost::InitializeAudio(
    scoped_refptr<base::SingleThreadTaskRunner> audio_decode_task_runner,
    base::WeakPtr<AudioStub> audio_consumer) {
  audio_decode_task_runner_ = audio_decode_task_runner;
  audio_consumer_ = audio_consumer;
}

void WebrtcConnectionToHost::OnSessionStateChange(Session::State state) {
  DCHECK(event_callback_);

  switch (state) {
    case Session::INITIALIZING:
    case Session::CONNECTING:
    case Session::ACCEPTING:
    case Session::ACCEPTED:
    case Session::AUTHENTICATING:
      // Don't care about these events.
      break;

    case Session::AUTHENTICATED:
      SetState(AUTHENTICATED, ErrorCode::OK);
      break;

    case Session::CLOSED:
      CloseChannels();
      SetState(CLOSED, ErrorCode::OK);
      break;

    case Session::FAILED:
      CloseChannels();
      SetState(FAILED, session_->error());
      break;
  }
}

void WebrtcConnectionToHost::OnWebrtcTransportConnecting() {
  event_dispatcher_ = std::make_unique<ClientEventDispatcher>();
  event_dispatcher_->Init(
      transport_->CreateOutgoingChannel(event_dispatcher_->channel_name()),
      this);
}

void WebrtcConnectionToHost::OnWebrtcTransportConnected() {}

void WebrtcConnectionToHost::OnWebrtcTransportError(
    ErrorCode error,
    std::string_view error_details,
    const base::Location& error_location) {
  CloseChannels();
  SetState(FAILED, error);
}

void WebrtcConnectionToHost::OnWebrtcTransportProtocolChanged() {}

void WebrtcConnectionToHost::OnWebrtcTransportIncomingDataChannel(
    const std::string& name,
    std::unique_ptr<MessagePipe> pipe) {
  if (!control_dispatcher_) {
    control_dispatcher_ = std::make_unique<ClientControlDispatcher>();
  }

  if (name == control_dispatcher_->channel_name() &&
      !control_dispatcher_->is_connected()) {
    control_dispatcher_->set_client_stub(client_stub_);
    control_dispatcher_->set_clipboard_stub(clipboard_stub_);
    control_dispatcher_->Init(std::move(pipe), this);
  } else if (base::StartsWith(name, kVideoStatsChannelNamePrefix,
                              base::CompareCase::SENSITIVE)) {
    std::string video_stream_label =
        name.substr(strlen(kVideoStatsChannelNamePrefix));
    GetOrCreateVideoAdapter(video_stream_label)
        ->SetVideoStatsChannel(std::move(pipe));
  } else {
    LOG(WARNING) << "Received unknown incoming data channel " << name;
  }
}

void WebrtcConnectionToHost::OnWebrtcTransportMediaStreamAdded(
    webrtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
  if (stream->GetVideoTracks().size() > 0) {
    GetOrCreateVideoAdapter(stream->id())->SetMediaStream(stream);
  } else if (stream->GetAudioTracks().size() > 0) {
    audio_adapter_ =
        std::make_unique<WebrtcAudioSinkAdapter>(stream, audio_consumer_);
  } else {
    LOG(ERROR) << "Received MediaStream with no video or audio tracks.";
  }
}

void WebrtcConnectionToHost::OnWebrtcTransportMediaStreamRemoved(
    webrtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
  if (video_adapter_ && video_adapter_->label() == stream->id()) {
    video_adapter_.reset();
  }
}

void WebrtcConnectionToHost::OnWebrtcTransportRouteChanged(
    const TransportRoute& route) {}

void WebrtcConnectionToHost::OnChannelInitialized(
    ChannelDispatcherBase* channel_dispatcher) {
  NotifyIfChannelsReady();
}

void WebrtcConnectionToHost::OnChannelClosed(
    ChannelDispatcherBase* channel_dispatcher) {
  LOG(ERROR) << "Channel " << channel_dispatcher->channel_name()
             << " was closed unexpectedly.";
  SetState(FAILED, ErrorCode::CHANNEL_CONNECTION_ERROR);
}

ConnectionToHost::State WebrtcConnectionToHost::state() const {
  return state_;
}

void WebrtcConnectionToHost::NotifyIfChannelsReady() {
  if (!control_dispatcher_.get() || !control_dispatcher_->is_connected()) {
    return;
  }
  if (!event_dispatcher_.get() || !event_dispatcher_->is_connected()) {
    return;
  }

  // Start forwarding clipboard and input events.
  clipboard_forwarder_.set_clipboard_stub(control_dispatcher_.get());
  event_forwarder_.set_input_stub(event_dispatcher_.get());
  SetState(CONNECTED, ErrorCode::OK);
}

WebrtcVideoRendererAdapter* WebrtcConnectionToHost::GetOrCreateVideoAdapter(
    const std::string& label) {
  if (!video_adapter_ || video_adapter_->label() != label) {
    if (video_adapter_) {
      LOG(WARNING) << "Received multiple media streams. Ignoring all except "
                      "the last one.";
    }
    video_adapter_ =
        std::make_unique<WebrtcVideoRendererAdapter>(label, video_renderer_);
  }
  return video_adapter_.get();
}

void WebrtcConnectionToHost::CloseChannels() {
  clipboard_forwarder_.set_clipboard_stub(nullptr);
  control_dispatcher_.reset();
  event_forwarder_.set_input_stub(nullptr);
  event_dispatcher_.reset();
}

void WebrtcConnectionToHost::SetState(State state, ErrorCode error) {
  // |error| should be specified only when |state| is set to FAILED.
  DCHECK(state == FAILED || error == ErrorCode::OK);

  if (state != state_) {
    state_ = state;
    error_ = error;
    event_callback_->OnConnectionState(state_, error_);
  }
}

}  // namespace remoting::protocol
