// Copyright 2020 The Pigweed Authors
//
// 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
//
//     https://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.

#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"

#include <algorithm>
#include <cstring>

#include "pw_assert/assert.h"
#include "pw_assert/check.h"
#include "pw_status/try.h"
#include "pw_varint/varint.h"

namespace pw {
namespace ring_buffer {

using std::byte;
using Entry = PrefixedEntryRingBufferMulti::Entry;
using Reader = PrefixedEntryRingBufferMulti::Reader;
using iterator = PrefixedEntryRingBufferMulti::iterator;

void PrefixedEntryRingBufferMulti::Clear() {
  write_idx_ = 0;
  for (Reader& reader : readers_) {
    reader.read_idx_ = 0;
    reader.entry_count_ = 0;
  }
}

Status PrefixedEntryRingBufferMulti::SetBuffer(std::span<byte> buffer) {
  if ((buffer.data() == nullptr) ||  //
      (buffer.size_bytes() == 0) ||  //
      (buffer.size_bytes() > kMaxBufferBytes)) {
    return Status::InvalidArgument();
  }

  buffer_ = buffer.data();
  buffer_bytes_ = buffer.size_bytes();

  Clear();
  return OkStatus();
}

Status PrefixedEntryRingBufferMulti::AttachReader(Reader& reader) {
  if (reader.buffer_ != nullptr) {
    return Status::InvalidArgument();
  }
  reader.buffer_ = this;

  if (readers_.empty()) {
    reader.read_idx_ = write_idx_;
    reader.entry_count_ = 0;
  } else {
    const Reader& slowest_reader = GetSlowestReader();
    reader.read_idx_ = slowest_reader.read_idx_;
    reader.entry_count_ = slowest_reader.entry_count_;
  }

  readers_.push_back(reader);
  return OkStatus();
}

Status PrefixedEntryRingBufferMulti::DetachReader(Reader& reader) {
  if (reader.buffer_ != this) {
    return Status::InvalidArgument();
  }
  reader.buffer_ = nullptr;
  reader.read_idx_ = 0;
  reader.entry_count_ = 0;
  readers_.remove(reader);
  return OkStatus();
}

Status PrefixedEntryRingBufferMulti::InternalPushBack(
    std::span<const byte> data,
    uint32_t user_preamble_data,
    bool pop_front_if_needed) {
  if (buffer_ == nullptr) {
    return Status::FailedPrecondition();
  }

  // Prepare a single buffer that can hold both the user preamble and entry
  // length.
  byte preamble_buf[varint::kMaxVarint32SizeBytes * 2];
  size_t user_preamble_bytes = 0;
  if (user_preamble_) {
    user_preamble_bytes =
        varint::Encode<uint32_t>(user_preamble_data, preamble_buf);
  }
  size_t length_bytes = varint::Encode<uint32_t>(
      data.size_bytes(), std::span(preamble_buf).subspan(user_preamble_bytes));
  size_t total_write_bytes =
      user_preamble_bytes + length_bytes + data.size_bytes();
  if (buffer_bytes_ < total_write_bytes) {
    return Status::OutOfRange();
  }

  if (pop_front_if_needed) {
    // PushBack() case: evict items as needed.
    // Drop old entries until we have space for the new entry.
    while (RawAvailableBytes() < total_write_bytes) {
      InternalPopFrontAll();
    }
  } else if (RawAvailableBytes() < total_write_bytes) {
    // TryPushBack() case: don't evict items.
    return Status::ResourceExhausted();
  }

  // Write the new entry into the ring buffer.
  RawWrite(std::span(preamble_buf, user_preamble_bytes + length_bytes));
  RawWrite(data);

  // Update all readers of the new count.
  for (Reader& reader : readers_) {
    reader.entry_count_++;
  }
  return OkStatus();
}

auto GetOutput(std::span<byte> data_out, size_t* write_index) {
  return [data_out, write_index](std::span<const byte> src) -> Status {
    size_t copy_size = std::min(data_out.size_bytes(), src.size_bytes());

    memcpy(data_out.data() + *write_index, src.data(), copy_size);
    *write_index += copy_size;

    return (copy_size == src.size_bytes()) ? OkStatus()
                                           : Status::ResourceExhausted();
  };
}

Status PrefixedEntryRingBufferMulti::InternalPeekFront(
    const Reader& reader, std::span<byte> data, size_t* bytes_read_out) const {
  *bytes_read_out = 0;
  return InternalRead(reader, GetOutput(data, bytes_read_out), false);
}

Status PrefixedEntryRingBufferMulti::InternalPeekFront(
    const Reader& reader, ReadOutput output) const {
  return InternalRead(reader, output, false);
}

Status PrefixedEntryRingBufferMulti::InternalPeekFrontWithPreamble(
    const Reader& reader, std::span<byte> data, size_t* bytes_read_out) const {
  *bytes_read_out = 0;
  return InternalRead(reader, GetOutput(data, bytes_read_out), true);
}

Status PrefixedEntryRingBufferMulti::InternalPeekFrontWithPreamble(
    const Reader& reader, ReadOutput output) const {
  return InternalRead(reader, output, true);
}

Status PrefixedEntryRingBufferMulti::InternalPeekFrontPreamble(
    const Reader& reader, uint32_t& user_preamble_out) const {
  if (reader.entry_count_ == 0) {
    return Status::OutOfRange();
  }
  // Figure out where to start reading (wrapped); accounting for preamble.
  EntryInfo info = FrontEntryInfo(reader);
  user_preamble_out = info.user_preamble;
  return OkStatus();
}

// TODO(pwbug/339): Consider whether this internal templating is required, or if
// we can simply promote GetOutput to a static function and remove the template.
// T should be similar to Status (*read_output)(std::span<const byte>)
template <typename T>
Status PrefixedEntryRingBufferMulti::InternalRead(
    const Reader& reader,
    T read_output,
    bool include_preamble_in_output,
    uint32_t* user_preamble_out) const {
  if (buffer_ == nullptr) {
    return Status::FailedPrecondition();
  }
  if (reader.entry_count_ == 0) {
    return Status::OutOfRange();
  }

  // Figure out where to start reading (wrapped); accounting for preamble.
  EntryInfo info = FrontEntryInfo(reader);
  size_t read_bytes = info.data_bytes;
  size_t data_read_idx = reader.read_idx_;
  if (user_preamble_out) {
    *user_preamble_out = info.user_preamble;
  }
  if (include_preamble_in_output) {
    read_bytes += info.preamble_bytes;
  } else {
    data_read_idx = IncrementIndex(data_read_idx, info.preamble_bytes);
  }

  // Read bytes, stopping at the end of the buffer if this entry wraps.
  size_t bytes_until_wrap = buffer_bytes_ - data_read_idx;
  size_t bytes_to_copy = std::min(read_bytes, bytes_until_wrap);
  Status status =
      read_output(std::span(buffer_ + data_read_idx, bytes_to_copy));

  // If the entry wrapped, read the remaining bytes.
  if (status.ok() && (bytes_to_copy < read_bytes)) {
    status = read_output(std::span(buffer_, read_bytes - bytes_to_copy));
  }
  return status;
}

void PrefixedEntryRingBufferMulti::InternalPopFrontAll() {
  // Forcefully pop all readers. Find the slowest reader, which must have
  // the highest entry count, then pop all readers that have the same count.
  //
  // It is expected that InternalPopFrontAll is called only when there is
  // something to pop from at least one reader. If no readers exist, or all
  // readers are caught up, this function will assert.
  size_t entry_count = GetSlowestReader().entry_count_;
  PW_DCHECK_INT_NE(entry_count, 0);
  // Otherwise, pop the readers that have the largest value.
  for (Reader& reader : readers_) {
    if (reader.entry_count_ == entry_count) {
      reader.PopFront()
          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
    }
  }
}

const Reader& PrefixedEntryRingBufferMulti::GetSlowestReader() const {
  PW_DCHECK_INT_GT(readers_.size(), 0);
  const Reader* slowest_reader = &(*readers_.begin());
  for (const Reader& reader : readers_) {
    if (reader.entry_count_ > slowest_reader->entry_count_) {
      slowest_reader = &reader;
    }
  }
  return *slowest_reader;
}

Status PrefixedEntryRingBufferMulti::Dering() {
  if (buffer_ == nullptr || readers_.empty()) {
    return Status::FailedPrecondition();
  }

  // Check if by luck we're already deringed.
  Reader& slowest_reader = GetSlowestReaderWritable();
  if (slowest_reader.read_idx_ == 0) {
    return OkStatus();
  }

  return InternalDering(slowest_reader);
}

Status PrefixedEntryRingBufferMulti::InternalDering(Reader& dering_reader) {
  if (buffer_ == nullptr || readers_.empty()) {
    return Status::FailedPrecondition();
  }

  auto buffer_span = std::span(buffer_, buffer_bytes_);
  std::rotate(buffer_span.begin(),
              buffer_span.begin() + dering_reader.read_idx_,
              buffer_span.end());

  // If the new index is past the end of the buffer,
  // alias it back (wrap) to the start of the buffer.
  if (write_idx_ < dering_reader.read_idx_) {
    write_idx_ += buffer_bytes_;
  }
  write_idx_ -= dering_reader.read_idx_;

  for (Reader& reader : readers_) {
    if (&reader == &dering_reader) {
      continue;
    }
    if (reader.read_idx_ < dering_reader.read_idx_) {
      reader.read_idx_ += buffer_bytes_;
    }
    reader.read_idx_ -= dering_reader.read_idx_;
  }

  dering_reader.read_idx_ = 0;
  return OkStatus();
}

Status PrefixedEntryRingBufferMulti::InternalPopFront(Reader& reader) {
  if (buffer_ == nullptr) {
    return Status::FailedPrecondition();
  }
  if (reader.entry_count_ == 0) {
    return Status::OutOfRange();
  }

  // Advance the read pointer past the front entry to the next one.
  EntryInfo info = FrontEntryInfo(reader);
  size_t entry_bytes = info.preamble_bytes + info.data_bytes;
  size_t prev_read_idx = reader.read_idx_;
  reader.read_idx_ = IncrementIndex(prev_read_idx, entry_bytes);
  reader.entry_count_--;
  return OkStatus();
}

size_t PrefixedEntryRingBufferMulti::InternalFrontEntryDataSizeBytes(
    const Reader& reader) const {
  if (reader.entry_count_ == 0) {
    return 0;
  }
  return FrontEntryInfo(reader).data_bytes;
}

size_t PrefixedEntryRingBufferMulti::InternalFrontEntryTotalSizeBytes(
    const Reader& reader) const {
  if (reader.entry_count_ == 0) {
    return 0;
  }
  EntryInfo info = FrontEntryInfo(reader);
  return info.preamble_bytes + info.data_bytes;
}

PrefixedEntryRingBufferMulti::EntryInfo
PrefixedEntryRingBufferMulti::FrontEntryInfo(const Reader& reader) const {
  Result<PrefixedEntryRingBufferMulti::EntryInfo> entry_info =
      RawFrontEntryInfo(reader.read_idx_);
  PW_CHECK_OK(entry_info.status());
  return entry_info.value();
}

Result<PrefixedEntryRingBufferMulti::EntryInfo>
PrefixedEntryRingBufferMulti::RawFrontEntryInfo(size_t source_idx) const {
  // Entry headers consists of: (optional prefix byte, varint size, data...)

  // If a preamble exists, extract the varint and it's bytes in bytes.
  size_t user_preamble_bytes = 0;
  uint64_t user_preamble_data = 0;
  byte varint_buf[varint::kMaxVarint32SizeBytes];
  if (user_preamble_) {
    RawRead(varint_buf, source_idx, varint::kMaxVarint32SizeBytes);
    user_preamble_bytes = varint::Decode(varint_buf, &user_preamble_data);
    if (user_preamble_bytes == 0u) {
      return Status::DataLoss();
    }
  }

  // Read the entry header; extract the varint and it's bytes in bytes.
  RawRead(varint_buf,
          IncrementIndex(source_idx, user_preamble_bytes),
          varint::kMaxVarint32SizeBytes);
  uint64_t entry_bytes;
  size_t length_bytes = varint::Decode(varint_buf, &entry_bytes);
  if (length_bytes == 0u) {
    return Status::DataLoss();
  }

  EntryInfo info = {};
  info.preamble_bytes = user_preamble_bytes + length_bytes;
  info.user_preamble = static_cast<uint32_t>(user_preamble_data);
  info.data_bytes = entry_bytes;
  return info;
}

// Comparisons ordered for more probable early exits, assuming the reader is
// not far behind the writer compared to the size of the ring.
size_t PrefixedEntryRingBufferMulti::RawAvailableBytes() const {
  // Compute slowest reader. If no readers exist, the entire buffer can be
  // written.
  if (readers_.empty()) {
    return buffer_bytes_;
  }

  size_t read_idx = GetSlowestReader().read_idx_;
  // Case: Not wrapped.
  if (read_idx < write_idx_) {
    return buffer_bytes_ - (write_idx_ - read_idx);
  }
  // Case: Wrapped
  if (read_idx > write_idx_) {
    return read_idx - write_idx_;
  }
  // Case: Matched read and write heads; empty or full.
  for (const Reader& reader : readers_) {
    if (reader.read_idx_ == read_idx && reader.entry_count_ != 0) {
      return 0;
    }
  }
  return buffer_bytes_;
}

void PrefixedEntryRingBufferMulti::RawWrite(std::span<const std::byte> source) {
  if (source.size_bytes() == 0) {
    return;
  }

  // Write until the end of the source or the backing buffer.
  size_t bytes_until_wrap = buffer_bytes_ - write_idx_;
  size_t bytes_to_copy = std::min(source.size(), bytes_until_wrap);
  memcpy(buffer_ + write_idx_, source.data(), bytes_to_copy);

  // If there wasn't space in the backing buffer, wrap to the front.
  if (bytes_to_copy < source.size()) {
    memcpy(
        buffer_, source.data() + bytes_to_copy, source.size() - bytes_to_copy);
  }
  write_idx_ = IncrementIndex(write_idx_, source.size());
}

void PrefixedEntryRingBufferMulti::RawRead(byte* destination,
                                           size_t source_idx,
                                           size_t length_bytes) const {
  if (length_bytes == 0) {
    return;
  }

  // Read the pre-wrap bytes.
  size_t bytes_until_wrap = buffer_bytes_ - source_idx;
  size_t bytes_to_copy = std::min(length_bytes, bytes_until_wrap);
  memcpy(destination, buffer_ + source_idx, bytes_to_copy);

  // Read the post-wrap bytes, if needed.
  if (bytes_to_copy < length_bytes) {
    memcpy(destination + bytes_to_copy, buffer_, length_bytes - bytes_to_copy);
  }
}

size_t PrefixedEntryRingBufferMulti::IncrementIndex(size_t index,
                                                    size_t count) const {
  // Note: This doesn't use modulus (%) since the branch is cheaper, and we
  // guarantee that count will never be greater than buffer_bytes_.
  index += count;
  if (index > buffer_bytes_) {
    index -= buffer_bytes_;
  }
  return index;
}

Status PrefixedEntryRingBufferMulti::Reader::PeekFrontWithPreamble(
    std::span<byte> data,
    uint32_t& user_preamble_out,
    size_t& entry_bytes_read_out) const {
  entry_bytes_read_out = 0;
  return buffer_->InternalRead(
      *this, GetOutput(data, &entry_bytes_read_out), false, &user_preamble_out);
}

iterator& iterator::operator++() {
  PW_DCHECK_OK(iteration_status_);
  PW_DCHECK_INT_NE(entry_count_, 0);

  Result<EntryInfo> info = ring_buffer_->RawFrontEntryInfo(read_idx_);
  if (!info.status().ok()) {
    SkipToEnd(info.status());
    return *this;
  }

  // It is guaranteed that the buffer is deringed at this point.
  read_idx_ += info.value().preamble_bytes + info.value().data_bytes;
  entry_count_--;

  if (entry_count_ == 0) {
    SkipToEnd(OkStatus());
    return *this;
  }

  if (read_idx_ >= ring_buffer_->TotalUsedBytes()) {
    SkipToEnd(Status::DataLoss());
    return *this;
  }

  info = ring_buffer_->RawFrontEntryInfo(read_idx_);
  if (!info.status().ok()) {
    SkipToEnd(info.status());
    return *this;
  }
  return *this;
}

const Entry& iterator::operator*() const {
  PW_DCHECK_OK(iteration_status_);
  PW_DCHECK_INT_NE(entry_count_, 0);

  Result<EntryInfo> info = ring_buffer_->RawFrontEntryInfo(read_idx_);
  PW_DCHECK_OK(info.status());

  entry_ = {
      .buffer = std::span<const byte>(
          ring_buffer_->buffer_ + read_idx_ + info.value().preamble_bytes,
          info.value().data_bytes),
      .preamble = info.value().user_preamble,
  };
  return entry_;
}

}  // namespace ring_buffer
}  // namespace pw