// Copyright 2026 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/base/ipc_fifo_buffer.h"

#include <limits>

#include "base/check.h"
#include "base/containers/span.h"
#include "base/logging.h"

namespace remoting {

// =============================================================================
// IpcFifoBufferWriter
// =============================================================================

IpcFifoBufferWriter::IpcFifoBufferWriter(
    mojo::ScopedDataPipeProducerHandle producer_handle)
    : producer_handle_(std::move(producer_handle)) {
  CHECK(producer_handle_.is_valid());
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

IpcFifoBufferWriter::~IpcFifoBufferWriter() = default;

mojo::ScopedDataPipeProducerHandle IpcFifoBufferWriter::TakeProducerHandle() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::move(producer_handle_);
}

FifoBufferWriter::Result IpcFifoBufferWriter::Write(
    base::span<const uint8_t> data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (data.empty()) {
    return FifoBufferWriter::Result::kSuccess;
  }

  size_t bytes_written = 0;
  MojoResult result = producer_handle_->WriteData(
      data, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE, bytes_written);

  if (result == MOJO_RESULT_OK) {
    return FifoBufferWriter::Result::kSuccess;
  }

  if (result == MOJO_RESULT_SHOULD_WAIT || result == MOJO_RESULT_OUT_OF_RANGE) {
    return FifoBufferWriter::Result::kFull;
  }

  if (result == MOJO_RESULT_FAILED_PRECONDITION) {
    // Peer closed. Fail gracefully.
    return FifoBufferWriter::Result::kFailed;
  }

  return FifoBufferWriter::Result::kFailed;
}

// =============================================================================
// IpcFifoBufferReader
// =============================================================================

IpcFifoBufferReader::IpcFifoBufferReader(
    mojo::ScopedDataPipeConsumerHandle consumer_handle)
    : consumer_handle_(std::move(consumer_handle)) {
  CHECK(consumer_handle_.is_valid());
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

IpcFifoBufferReader::~IpcFifoBufferReader() = default;

mojo::ScopedDataPipeConsumerHandle IpcFifoBufferReader::TakeConsumerHandle() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::move(consumer_handle_);
}

std::optional<size_t> IpcFifoBufferReader::Read(
    base::span<uint8_t> destination) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (destination.empty()) {
    return 0;
  }

  size_t bytes_read = 0;
  MojoResult result = consumer_handle_->ReadData(MOJO_READ_DATA_FLAG_NONE,
                                                 destination, bytes_read);

  if (result == MOJO_RESULT_OK) {
    return bytes_read;
  }

  if (result == MOJO_RESULT_SHOULD_WAIT) {
    return 0;
  }

  if (result == MOJO_RESULT_FAILED_PRECONDITION) {
    // Peer closed. Fail gracefully.
    return std::nullopt;
  }

  LOG(ERROR) << "Failed to read from Mojo Data Pipe: " << result;
  return std::nullopt;
}

std::optional<size_t> IpcFifoBufferReader::Skip(size_t bytes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (bytes == 0) {
    return 0;
  }

  size_t bytes_discarded = 0;
  MojoResult result = consumer_handle_->DiscardData(bytes, bytes_discarded);

  if (result == MOJO_RESULT_OK) {
    return bytes_discarded;
  }

  if (result == MOJO_RESULT_SHOULD_WAIT) {
    return 0;
  }

  if (result == MOJO_RESULT_FAILED_PRECONDITION) {
    // Peer closed. Fail gracefully.
    return std::nullopt;
  }

  LOG(ERROR) << "Failed to discard data from Mojo Data Pipe: " << result;
  return std::nullopt;
}

void IpcFifoBufferReader::Clear() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  [[maybe_unused]] size_t discarded = 0;
  consumer_handle_->DiscardData(std::numeric_limits<size_t>::max(), discarded);
}

std::optional<size_t> IpcFifoBufferReader::GetBufferedBytes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  size_t bytes_available = 0;
  MojoResult result = consumer_handle_->ReadData(
      MOJO_READ_DATA_FLAG_QUERY, base::span<uint8_t>(), bytes_available);

  if (result == MOJO_RESULT_OK) {
    if (bytes_available > 0) {
      return bytes_available;
    }
    // If 0 bytes are available, the pipe might be empty or closed.
    MojoHandleSignalsState signals = consumer_handle_->QuerySignalsState();
    if (signals.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED) {
      return std::nullopt;
    }
    return 0;
  }

  if (result == MOJO_RESULT_SHOULD_WAIT) {
    // Pipe is empty.
    return 0;
  }

  if (result == MOJO_RESULT_FAILED_PRECONDITION) {
    // Peer closed.
    return std::nullopt;
  }

  LOG(ERROR) << "Failed to query Mojo Data Pipe: " << result;
  return std::nullopt;
}

bool CreateIpcFifoBuffer(size_t capacity,
                         std::unique_ptr<IpcFifoBufferWriter>& writer,
                         std::unique_ptr<IpcFifoBufferReader>& reader) {
  mojo::ScopedDataPipeProducerHandle producer;
  mojo::ScopedDataPipeConsumerHandle consumer;
  MojoResult result = mojo::CreateDataPipe(capacity, producer, consumer);
  if (result != MOJO_RESULT_OK) {
    LOG(ERROR) << "Failed to create Mojo Data Pipe: " << result;
    return false;
  }

  writer = std::make_unique<IpcFifoBufferWriter>(std::move(producer));
  reader = std::make_unique<IpcFifoBufferReader>(std::move(consumer));
  return true;
}

}  // namespace remoting
