// Copyright 2012 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/host/ipc_video_frame_capturer.h"

#include "base/check.h"
#include "base/notimplemented.h"
#include "base/time/time.h"
#include "remoting/host/desktop_session_proxy.h"
#include "remoting/host/video_memory_utils.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/shared_memory.h"

namespace remoting {

IpcVideoFrameCapturer::IpcVideoFrameCapturer(
    scoped_refptr<DesktopSessionProxy> desktop_session_proxy)
    : desktop_session_proxy_(desktop_session_proxy) {}

IpcVideoFrameCapturer::~IpcVideoFrameCapturer() = default;

void IpcVideoFrameCapturer::OnCreateVideoCapturerResult(
    mojom::CreateVideoCapturerResultPtr result) {
  if (capturer_control_) {
    // Perform cleanup, just as if the previous endpoint became disconnected.
    // This replies to any pending frame requests, clears any shared-memory
    // buffers, and resets the Mojo endpoints.
    OnDisconnect();
  }

  capturer_control_.Bind(std::move(result->video_capturer));
  event_handler_.Bind(std::move(result->video_capturer_event_handler));

  capturer_control_.set_disconnect_handler(base::BindOnce(
      &IpcVideoFrameCapturer::OnDisconnect, base::Unretained(this)));
  if (callback_) {
    // Start() has been called.
    capturer_control_->Start();
  }
}

base::WeakPtr<IpcVideoFrameCapturer> IpcVideoFrameCapturer::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void IpcVideoFrameCapturer::Start(Callback* callback) {
  DCHECK(!callback_);
  DCHECK(callback);
  callback_ = callback;
  if (capturer_control_) {
    capturer_control_->Start();
  }
}

bool IpcVideoFrameCapturer::GetSourceList(SourceList* sources) {
  NOTIMPLEMENTED();
  return false;
}

bool IpcVideoFrameCapturer::SelectSource(SourceId id) {
  // This should only be called in single-stream mode. DesktopSessionProxy will
  // request a new capturer be created in the Desktop process. When the new Mojo
  // endpoints are received by OnCreateVideoCapturerResult(), the old endpoints
  // will be disconnected and the Desktop process will delete the old capturer.
  desktop_session_proxy_->RebindSingleVideoCapturer(id, GetWeakPtr());
  return true;
}

void IpcVideoFrameCapturer::SetComposeEnabled(bool enabled) {
  if (capturer_control_) {
    capturer_control_->SetComposeEnabled(enabled);
  }
}

void IpcVideoFrameCapturer::SetMaxFrameRate(uint32_t max_frame_rate) {
  if (capturer_control_) {
    capturer_control_->SetMaxFrameRate(max_frame_rate);
  }
}

void IpcVideoFrameCapturer::Pause(bool pause) {
  if (capturer_control_) {
    capturer_control_->Pause(pause);
  }
}

void IpcVideoFrameCapturer::BoostCaptureRate(base::TimeDelta capture_interval,
                                             base::TimeDelta duration) {
  if (capturer_control_) {
    capturer_control_->BoostCaptureRate(capture_interval, duration);
  }
}

void IpcVideoFrameCapturer::OnSharedMemoryRegionCreated(
    int id,
    base::ReadOnlySharedMemoryRegion region,
    uint32_t size) {
  auto shared_buffer =
      base::MakeRefCounted<IpcSharedBufferCore>(id, std::move(region));

  if (shared_buffer->memory() != nullptr &&
      !shared_buffers_.insert(std::make_pair(id, shared_buffer)).second) {
    LOG(ERROR) << "Duplicate shared buffer id " << id << " encountered";
  }
}

void IpcVideoFrameCapturer::OnSharedMemoryRegionReleased(int id) {
  // Drop the cached reference to the buffer.
  shared_buffers_.erase(id);
}

void IpcVideoFrameCapturer::OnFrameCaptureStart(base::TimeTicks start_time) {
  // TODO: crbug.com/475611769 - Pass `start_time` to the callback so that the
  // IPC latency is accounted for.
  callback_->OnFrameCaptureStart();
}

void IpcVideoFrameCapturer::OnCaptureResult(mojom::CaptureResultPtr result) {
  if (result->is_capture_error()) {
    callback_->OnCaptureResult(result->get_capture_error(), nullptr);
    return;
  }

  // Assume that |desktop_frame| is well-formed because it was received from a
  // more privileged process.
  mojom::DesktopFramePtr& desktop_frame = result->get_desktop_frame();
  scoped_refptr<IpcSharedBufferCore> shared_buffer_core =
      GetSharedBufferCore(desktop_frame->shared_buffer_id);
  CHECK(shared_buffer_core.get());

  std::unique_ptr<webrtc::DesktopFrame> frame =
      std::make_unique<webrtc::SharedMemoryDesktopFrame>(
          desktop_frame->size, desktop_frame->stride, webrtc::FOURCC_ARGB,
          std::make_unique<IpcSharedBuffer>(shared_buffer_core));
  frame->set_capture_time_ms(desktop_frame->capture_time_ms);
  frame->set_dpi(desktop_frame->dpi);
  frame->set_capturer_id(desktop_frame->capturer_id);

  for (const auto& rect : desktop_frame->dirty_region) {
    frame->mutable_updated_region()->AddRect(rect);
  }

  callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
                             std::move(frame));
}

void IpcVideoFrameCapturer::OnDisconnect() {
  shared_buffers_.clear();

  // Reset the endpoints so that any further calls do not try to send Mojo
  // commands.
  capturer_control_.reset();
  event_handler_.reset();
}

scoped_refptr<IpcSharedBufferCore> IpcVideoFrameCapturer::GetSharedBufferCore(
    int id) {
  SharedBuffers::const_iterator i = shared_buffers_.find(id);
  if (i != shared_buffers_.end()) {
    return i->second;
  } else {
    LOG(ERROR) << "Failed to find the shared buffer " << id;
    return nullptr;
  }
}

}  // namespace remoting
