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

#include "quiche/balsa/simple_buffer.h"

#include <algorithm>
#include <cstring>
#include <memory>

#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"

namespace quiche {

constexpr int kMinimumSimpleBufferSize = 10;

SimpleBuffer::SimpleBuffer(int size) { Reserve(size); }

////////////////////////////////////////////////////////////////////////////////

int SimpleBuffer::Write(const char* bytes, int size) {
  if (size <= 0) {
    QUICHE_BUG_IF(simple_buffer_write_negative_size, size < 0)
        << "size must not be negative: " << size;
    return 0;
  }

  Reserve(size);
  memcpy(storage_ + write_idx_, bytes, size);
  AdvanceWritablePtr(size);
  return size;
}

////////////////////////////////////////////////////////////////////////////////

int SimpleBuffer::Read(char* bytes, int size) {
  if (size < 0) {
    QUICHE_BUG(simple_buffer_read_negative_size)
        << "size must not be negative: " << size;
    return 0;
  }

  char* read_ptr = nullptr;
  int read_size = 0;
  GetReadablePtr(&read_ptr, &read_size);
  read_size = std::min(read_size, size);
  if (read_size == 0) {
    return 0;
  }

  memcpy(bytes, read_ptr, read_size);
  AdvanceReadablePtr(read_size);
  return read_size;
}

////////////////////////////////////////////////////////////////////////////////

// Attempts to reserve a contiguous block of buffer space either by reclaiming
// consumed data or by allocating a larger buffer.
void SimpleBuffer::Reserve(int size) {
  if (size < 0) {
    QUICHE_BUG(simple_buffer_reserve_negative_size)
        << "size must not be negative: " << size;
    return;
  }

  if (size == 0 || storage_size_ - write_idx_ >= size) {
    return;
  }

  char* read_ptr = nullptr;
  int read_size = 0;
  GetReadablePtr(&read_ptr, &read_size);

  if (read_ptr == nullptr) {
    QUICHE_DCHECK_EQ(0, read_size);

    size = std::max(size, kMinimumSimpleBufferSize);
    storage_ = new char[size];
    storage_size_ = size;
    return;
  }

  if (read_size + size <= storage_size_) {
    // Can reclaim space from consumed bytes by shifting.
    memmove(storage_, read_ptr, read_size);
    read_idx_ = 0;
    write_idx_ = read_size;
    return;
  }

  // The new buffer needs to be at least `read_size + size` bytes.
  // At least double the buffer to amortize allocation costs.
  storage_size_ = std::max(2 * storage_size_, size + read_size);

  char* new_storage = new char[storage_size_];
  memcpy(new_storage, read_ptr, read_size);
  delete[] storage_;

  read_idx_ = 0;
  write_idx_ = read_size;
  storage_ = new_storage;
}

void SimpleBuffer::AdvanceReadablePtr(int amount_to_advance) {
  if (amount_to_advance < 0) {
    QUICHE_BUG(simple_buffer_advance_read_negative_arg)
        << "amount_to_advance must not be negative: " << amount_to_advance;
    return;
  }

  read_idx_ += amount_to_advance;
  if (read_idx_ > write_idx_) {
    QUICHE_BUG(simple_buffer_read_ptr_too_far)
        << "error: readable pointer advanced beyond writable one";
    read_idx_ = write_idx_;
  }

  if (read_idx_ == write_idx_) {
    // Buffer is empty, rewind `read_idx_` and `write_idx_` so that next write
    // happens at the beginning of buffer instead of cutting free space in two.
    Clear();
  }
}

void SimpleBuffer::AdvanceWritablePtr(int amount_to_advance) {
  if (amount_to_advance < 0) {
    QUICHE_BUG(simple_buffer_advance_write_negative_arg)
        << "amount_to_advance must not be negative: " << amount_to_advance;
    return;
  }

  write_idx_ += amount_to_advance;
  if (write_idx_ > storage_size_) {
    QUICHE_BUG(simple_buffer_write_ptr_too_far)
        << "error: writable pointer advanced beyond end of storage";
    write_idx_ = storage_size_;
  }
}

SimpleBuffer::ReleasedBuffer SimpleBuffer::Release() {
  if (write_idx_ == 0) {
    return ReleasedBuffer{nullptr, 0};
  }
  ReleasedBuffer buffer{std::unique_ptr<char[]>(storage_),
                        static_cast<size_t>(write_idx_)};
  Clear();
  storage_ = nullptr;
  storage_size_ = 0;
  return buffer;
}
}  // namespace quiche
