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

#ifndef BASE_CONTAINERS_HEAP_ARRAY_H_
#define BASE_CONTAINERS_HEAP_ARRAY_H_

#include <stddef.h>

#include <memory>
#include <type_traits>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "third_party/abseil-cpp/absl/base/attributes.h"

namespace base {

// HeapArray<T> is a replacement for std::unique_ptr<T[]> that keeps track
// of its size. It is intended to provide easy conversion to span<T> for most
// usage, but it also provides bounds-checked indexing.
//
// By default, elements in the array are either value-initialized (i.e. zeroed
// for primitive types) when the array is created using the WithSize()
// static method, or uninitialized when the array is created via the Uninit()
// static method.
template <typename T, typename Deleter = void>
class TRIVIAL_ABI GSL_OWNER HeapArray {
 public:
  static_assert(!std::is_const_v<T>, "HeapArray cannot hold const types");
  static_assert(!std::is_reference_v<T>,
                "HeapArray cannot hold reference types");

  using iterator = base::span<T>::iterator;
  using const_iterator = base::span<const T>::iterator;
  // We don't put this default value in the template parameter list to allow the
  // static_assert on is_reference_v to give a nicer error message.
  using deleter_type = std::
      conditional_t<std::is_void_v<Deleter>, std::default_delete<T[]>, Deleter>;

  // Allocates initialized memory capable of holding `size` elements. No memory
  // is allocated for zero-sized arrays.
  static HeapArray WithSize(size_t size)
    requires(std::constructible_from<T>)
  {
    if (!size) {
      return HeapArray();
    }
    return HeapArray(std::unique_ptr<T[], deleter_type>(new T[size]()), size);
  }

  // Allocates uninitialized memory capable of holding `size` elements. T must
  // be trivially constructible and destructible. No memory is allocated for
  // zero-sized arrays.
  static HeapArray Uninit(size_t size)
    requires(std::is_trivially_constructible_v<T> &&
             std::is_trivially_destructible_v<T>)
  {
    if (!size) {
      return HeapArray();
    }
    return HeapArray(std::unique_ptr<T[], deleter_type>(new T[size]), size);
  }

  static HeapArray CopiedFrom(base::span<const T> that) {
    auto result = HeapArray::Uninit(that.size());
    result.copy_from(that);
    return result;
  }

  // Constructs a HeapArray from an existing pointer, taking ownership of the
  // pointer.
  //
  // # Safety
  // The pointer must be correctly aligned for type `T` and able to be deleted
  // through the `deleter_type`, which defaults to the `delete[]` operation. The
  // `ptr` must point to an array of at least `size` many elements. If these are
  // not met, then Undefined Behaviour can result.
  //
  // # Checks
  // When the `size` is zero, the `ptr` must be null.
  UNSAFE_BUFFER_USAGE static HeapArray FromOwningPointer(T* ptr, size_t size) {
    if (!size) {
      CHECK_EQ(ptr, nullptr);
      return HeapArray();
    }
    return HeapArray(std::unique_ptr<T[], deleter_type>(ptr), size);
  }

  // Constructs an empty array and does not allocate any memory.
  HeapArray()
    requires(std::constructible_from<T>)
  = default;

  // Move-only type since the memory is owned.
  HeapArray(const HeapArray&) = delete;
  HeapArray& operator=(const HeapArray&) = delete;

  // Move-construction leaves the moved-from object empty and containing
  // no allocated memory.
  HeapArray(HeapArray&& that)
      : data_(std::move(that.data_)), size_(std::exchange(that.size_, 0u)) {}

  // Move-assigment leaves the moved-from object empty and containing
  // no allocated memory.
  HeapArray& operator=(HeapArray&& that) {
    data_ = std::move(that.data_);
    size_ = std::exchange(that.size_, 0u);
    return *this;
  }
  ~HeapArray() = default;

  bool empty() const { return size_ == 0u; }
  size_t size() const { return size_; }

  // Prefer span-based methods below over data() where possible. The data()
  // method exists primarily to allow implicit constructions of spans.
  // Returns nullptr for a zero-sized (or moved-from) array.
  T* data() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data_.get(); }
  const T* data() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return data_.get(); }

  iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return as_span().begin(); }
  const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().begin();
  }

  iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return as_span().end(); }
  const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().end();
  }

  T& operator[](size_t idx) ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span()[idx];
  }
  const T& operator[](size_t idx) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span()[idx];
  }

  // Access the HeapArray via spans. Note that span<T> is implicilty
  // constructible from HeapArray<T>, so an explicit call to .as_span() is
  // most useful, say, when the compiler can't deduce a template
  // argument type.
  base::span<T> as_span() ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return base::span<T>(data_.get(), size_);
  }
  base::span<const T> as_span() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return base::span<const T>(data_.get(), size_);
  }

  // Convenience method to copy the contents of the entire array from a
  // span<>. Hard CHECK occurs in span<>::copy_from() if the HeapArray and
  // the span have different sizes.
  void copy_from(base::span<const T> other) { as_span().copy_from(other); }

  // Convenience methods to slice the vector into spans.
  // Returns a span over the HeapArray starting at `offset` of `count` elements.
  // If `count` is unspecified, all remaining elements are included. A CHECK()
  // occurs if any of the parameters results in an out-of-range position in
  // the HeapArray.
  base::span<T> subspan(size_t offset, size_t count = base::dynamic_extent)
      ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().subspan(offset, count);
  }
  base::span<const T> subspan(size_t offset,
                              size_t count = base::dynamic_extent) const
      ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().subspan(offset, count);
  }

  // Returns a span over the first `count` elements of the HeapArray. A CHECK()
  // occurs if the `count` is larger than size of the HeapArray.
  base::span<T> first(size_t count) ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().first(count);
  }
  base::span<const T> first(size_t count) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().first(count);
  }

  // Returns a span over the last `count` elements of the HeapArray. A CHECK()
  // occurs if the `count` is larger than size of the HeapArray.
  base::span<T> last(size_t count) ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().last(count);
  }
  base::span<const T> last(size_t count) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return as_span().last(count);
  }

  // Leaks the memory in the HeapArray so that it will never be freed, and
  // consumes the HeapArray, returning an unowning span that points to the
  // memory.
  base::span<T> leak() && {
    HeapArray<T> dropped = std::move(*this);
    T* leaked = dropped.data_.release();
    return make_span(leaked, dropped.size_);
  }

  // Delete the memory previously obtained from leak(). Argument is a pointer
  // rather than a span to facilitate use by callers that have lost track of
  // size information, as may happen when being passed through a C-style
  // function callback. The void* argument type makes its signature compatible
  // with typical void (*cb)(void*) C-style deletion callback.
  static void DeleteLeakedData(void* ptr) {
    // Memory is freed by unique ptr going out of scope.
    std::unique_ptr<T[], deleter_type> deleter(static_cast<T*>(ptr));
  }

 private:
  HeapArray(std::unique_ptr<T[], deleter_type> data, size_t size)
      : data_(std::move(data)), size_(size) {}

  std::unique_ptr<T[], deleter_type> data_;
  size_t size_ = 0u;
};

}  // namespace base

#endif  // BASE_CONTAINERS_HEAP_ARRAY_H_
