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

#ifndef COMPONENTS_ZUCCHINI_BUFFER_VIEW_H_
#define COMPONENTS_ZUCCHINI_BUFFER_VIEW_H_

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <algorithm>
#include <type_traits>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/memory/raw_span.h"
#include "components/zucchini/algorithm.h"

namespace zucchini {

// Describes a region within a buffer, with starting offset and size.
struct BufferRegion {
  bool operator==(const BufferRegion& other) const = default;

  // The region data are stored as |offset| and |size|, but often it is useful
  // to represent it as an interval [lo(), hi()) = [offset, offset + size).
  size_t lo() const { return offset; }
  size_t hi() const { return offset + size; }

  // Returns whether the Region fits in |[0, container_size)|. Special case:
  // a size-0 region starting at |container_size| fits.
  bool FitsIn(size_t container_size) const {
    return offset <= container_size && container_size - offset >= size;
  }

  // Returns |v| clipped to the inclusive range |[lo(), hi()]|.
  size_t InclusiveClamp(size_t v) const {
    return zucchini::InclusiveClamp(v, lo(), hi());
  }

  // Region data use size_t to match BufferViewBase::size_type, to make it
  // convenient to index into buffer view.
  size_t offset;
  size_t size;
};

namespace internal {

// TODO(huangs): Rename to BasicBufferView.
// BufferViewBase should not be used directly; it is an implementation used for
// both BufferView and MutableBufferView.
template <class ElementType>
class GSL_POINTER BufferViewBase {
 public:
  using element_type = ElementType;
  using span_type = base::raw_span<element_type, DanglingUntriaged>;

  using pointer = span_type::pointer;
  using reference = span_type::reference;
  using const_reference = span_type::const_reference;
  using size_type = span_type::size_type;

  // TODO(crbug.com/439964610): `iterator` and `const_iterator` should be
  // `span_type::iterator` and `span_type::const_iterator` which are backed by
  // `base::CheckedContiguousIterator` hence safe, however for backwards
  // compatibility the code using BufferViewBase assumes iterator is always a
  // pointer.
  using iterator = span_type::pointer;
  using const_iterator = span_type::const_pointer;

  static BufferViewBase FromRange(pointer first, pointer last) {
    return BufferViewBase(UNSAFE_TODO(span_type(first, last)));
  }

  // NOLINTNEXTLINE(google-explicit-constructor)
  constexpr BufferViewBase(base::span<element_type> other) noexcept
      : span_(other) {}

  constexpr BufferViewBase() noexcept = default;
  // Support a conversion from BufferViewBase<T> to BufferViewBase<const T>.
  template <typename T>
    requires(std::same_as<std::remove_const_t<element_type>, T> &&
             !std::same_as<element_type, T>)
  // NOLINTNEXTLINE(google-explicit-constructor)
  constexpr BufferViewBase(const BufferViewBase<T>& other)
      : span_(other.span_) {}
  // TODO(crbug.com/439964610): The second argument type should be
  // `base::StrictNumeric<size_type>`.
  constexpr BufferViewBase(pointer first, size_type count)
      : UNSAFE_TODO(span_(first, count)) {}

  constexpr BufferViewBase(const BufferViewBase& other) noexcept = default;
  constexpr BufferViewBase(BufferViewBase&& other) noexcept = default;
  constexpr BufferViewBase& operator=(const BufferViewBase& other) noexcept =
      default;
  constexpr BufferViewBase& operator=(BufferViewBase&& other) noexcept =
      default;

  // Iterators

  iterator begin() const { return base::to_address(span_.begin()); }
  iterator end() const { return base::to_address(span_.end()); }
  const_iterator cbegin() const { return base::to_address(span_.cbegin()); }
  const_iterator cend() const { return base::to_address(span_.cend()); }

  // Capacity

  constexpr size_type size() const noexcept { return span_.size(); }
  [[nodiscard]] constexpr bool empty() const noexcept { return span_.empty(); }

  // Returns whether the buffer is large enough to cover |region|.
  bool covers(const BufferRegion& region) const {
    return region.FitsIn(size());
  }

  // Returns whether the buffer is large enough to cover an array starting at
  // |offset| with |num| elements, each taking |elt_size| bytes.
  bool covers_array(size_type offset, size_type num, size_type elt_size) const {
    DCHECK_GT(elt_size, 0U);
    // Use subtraction and division to avoid overflow.
    return offset <= size() && (size() - offset) / elt_size >= num;
  }

  // Element access

  // Returns a reference to the raw value at specified location |pos|.
  // If |pos| is not within the range of the buffer, the process is terminated.
  constexpr const_reference operator[](size_type index) const {
    return span_[index];
  }
  constexpr reference operator[](size_type index) { return span_[index]; }

  // Returns a sub-buffer described by |region|.
  BufferViewBase operator[](BufferRegion region) const {
    return span_.subspan(region.offset, region.size);
  }

  // TODO(crbug.com/439964610): The argument type should be
  // `base::StrictNumeric<size_type>`.
  template <class T>
  T read(size_type pos) const {
    T value;
    base::byte_span_from_ref(value).copy_from_nonoverlapping(
        base::as_bytes(span_.subspan(pos)).first(sizeof value));
    return value;
  }

  // TODO(crbug.com/439964610): The first argument type should be
  // `base::StrictNumeric<size_type>`.
  template <class T>
  void write(size_type pos, const T& value) {
    base::as_writable_bytes(span_.subspan(pos))
        .copy_prefix_from(base::byte_span_from_ref(value));
  }

  // TODO(crbug.com/439964610): The argument type should be
  // `base::StrictNumeric<size_type>`.
  template <class T>
  bool can_access(size_type pos) const {
    return pos < size() && size() - pos >= sizeof(T);
  }

  // Returns a BufferRegion describing the full view, with offset = 0. If the
  // BufferViewBase is derived from another, this does *not* return the
  // original region used for its definition (hence "local").
  BufferRegion local_region() const { return BufferRegion{0, size()}; }

  base::span<const element_type> as_span() const { return span_; }

  bool equals(const BufferViewBase& other) const {
    return span_ == other.span_;
  }

  // Modifiers

  // TODO(crbug.com/439964610): The argument type should be
  // `base::StrictNumeric<size_type>`.
  void shrink(size_type new_size) { span_ = span_.first(new_size); }

  // Moves the start of the view forward by n bytes.
  // TODO(crbug.com/439964610): The argument type should be
  // `base::StrictNumeric<size_type>`.
  void remove_prefix(size_type n) { span_ = span_.subspan(n); }

  // Moves the start of the view to |it|, which is in range [begin(), end()).
  void seek(pointer it) {
    CHECK_LE(span_.data(), it);
    const size_type offset = it - span_.data();
    span_ = span_.subspan(offset);
  }

  // Given |origin| that contains |*this|, minimally increase |first_| (possibly
  // by 0) so that |first_ <= last_|, and |first_ - origin.first_| is a multiple
  // of |alignment|. On success, updates |first_| and returns true. Otherwise
  // returns false.
  bool AlignOn(const BufferViewBase& origin,
               base::StrictNumeric<size_type> alignment_arg) {
    static_assert(sizeof(element_type) == 1u);

    const size_type alignment = static_cast<size_type>(alignment_arg);

    DCHECK_GT(alignment, 0u);
    CHECK_LE(origin.begin(), begin());
    CHECK_LE(end(), origin.end());

    const size_type offset =
        static_cast<size_type>(span_.data() - origin.span_.data());
    const size_type aligned_size = AlignCeil(offset, alignment);
    if (aligned_size - offset > span_.size_bytes()) {
      return false;
    }
    span_ = span_.subspan(aligned_size - offset);
    return true;
  }

 private:
  template <typename T>
  friend class BufferViewBase;

  span_type span_;
};

}  // namespace internal

// Classes to encapsulate a contiguous sequence of raw data, without owning the
// encapsulated memory regions. These are intended to be used as value types.

using ConstBufferView = internal::BufferViewBase<const uint8_t>;
using MutableBufferView = internal::BufferViewBase<uint8_t>;

}  // namespace zucchini

#endif  // COMPONENTS_ZUCCHINI_BUFFER_VIEW_H_
