// 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.

#ifndef TOOLS_GN_IMMUTABLE_VECTOR_H_
#define TOOLS_GN_IMMUTABLE_VECTOR_H_

#include <cstdlib>
#include <type_traits>
#include <utility>

#include "util/aligned_alloc.h"

// An ImmutableVector<T> represents a fixed-size vector of constant items of
// type T. The in-memory representation is more efficient, only using one
// pointer to a single heap-allocated memory block that also contains the size.
//
// An ImmutableVectorView<T> acts as a copyable and movable reference to another
// ImmutableVector<T> instance. They both point to the same memory in the heap,
// but the view is not owning and is invalidated when the instance it points to
// is destroyed.
//
// Apart from that, they can be used with the same methods as a  const
// std::vector<T>.
//
template <typename T>
class ImmutableVector;

template <typename T>
class ImmutableVectorView {
 public:
  // Default constructor
  ImmutableVectorView() = default;

  // Constructor from an ImmutableVector.
  ImmutableVectorView(const ImmutableVector<T>& vector)
      : header_(vector.header_) {}

  // Usual methods to access items.
  const T* data() const { return begin(); }
  size_t size() const { return header_ ? header_->size : 0u; }
  bool empty() const { return size() == 0; }
  const T& operator[](size_t offset) const { return begin()[offset]; }

  const T* begin() const { return header_ ? &header_->item0 : nullptr; }
  const T* end() const {
    return header_ ? &header_->item0 + header_->size : nullptr;
  }

  const T& front() const { return begin()[0]; }
  const T& back() const { return end()[-1]; }

  // For use with standard algorithms and templates.
  using element_type = T;
  using value_type = std::remove_cv_t<T>;
  using size_type = std::size_t;
  using difference_type = std::ptrdiff_t;
  using pointer = T*;
  using const_pointer = const T*;
  using reference = T&;
  using const_reference = const T&;
  using iterator = const T*;
  using const_iterator = const T*;

  const_iterator find(const T& item) const {
    auto it = begin();
    auto it_end = end();
    for (; it != it_end; ++it) {
      if (*it == item)
        break;
    }
    return it;
  }

  bool contains(const T& item) const {
    for (const auto& cur_item : *this)
      if (cur_item == item)
        return true;

    return false;
  }

 protected:
  struct Header {
    size_t size;
    T item0;
  };

  ImmutableVectorView(Header* header) : header_(header) {}

  Header* header_ = nullptr;
};

template <typename T>
class ImmutableVector : public ImmutableVectorView<T> {
 public:
  // Default constructor.
  ImmutableVector() = default;

  // Range constructors.
  ImmutableVector(const T* begin, size_t size)
      : ImmutableVectorView<T>(AllocateAndCopyFrom(begin, size)) {}

  ImmutableVector(const T* begin, const T* end)
      : ImmutableVector(begin, end - begin) {}

  // In-place constructor
  // |producer| must be a callable that generates a T instance that will
  // be used to construct items in place in the allocated vector.
  template <typename P,
            typename = std::void_t<
                decltype(static_cast<const T&>(std::declval<P>()()))>>
  ImmutableVector(P producer, size_t size) {
    if (size) {
      this->header_ = AllocateFor(size);
      auto* dst = &(this->header_->item0);
      auto* dst_limit = dst + size;
      for (; dst != dst_limit; ++dst)
        new (dst) T(producer());
    }
  }

  // Container constructor
  //
  // This uses std::void_t<> to select container types whose values
  // are convertible to |const T&|, and which have |begin()| and |size()|
  // methods.
  //
  // This constructor copies items from the constructor into the
  // ImmutableVector.
  template <typename C,
            typename = std::void_t<
                decltype(static_cast<const T>(*std::declval<C>().begin())),
                decltype(std::declval<C>().size())>>
  ImmutableVector(const C& container) {
    size_t size = container.size();
    if (size) {
      this->header_ = AllocateFor(size);
      auto src = container.begin();
      auto* dst = &(this->header_->item0);
      auto* dst_limit = dst + size;
      for (; dst != dst_limit; ++dst, ++src) {
        new (dst) T(*src);
      }
    }
  }

  // Another container constructor where the items can be moved into
  // the resulting ImmutableVector.
  template <typename C,
            typename = std::void_t<
                decltype(static_cast<const T>(*std::declval<C>().begin())),
                decltype(std::declval<C>().size())>>
  ImmutableVector(C&& container) {
    size_t size = container.size();
    if (size) {
      this->header_ = AllocateFor(size);
      auto src = container.begin();
      auto* dst = &(this->header_->item0);
      auto* dst_limit = dst + size;
      for (; dst != dst_limit; ++dst, ++src)
        new (dst) T(std::move(*src));
    }
  }

  // Initializer-list container.
  ImmutableVector(std::initializer_list<T> list)
      : ImmutableVector(list.begin(), list.size()) {}

  // Copy operations
  ImmutableVector(const ImmutableVector& other)
      : ImmutableVectorView<T>(
            AllocateAndCopyFrom(other.begin(), other.size())) {}

  ImmutableVector& operator=(const ImmutableVector& other) {
    if (this != &other) {
      this->~ImmutableVector();
      new (this) ImmutableVector(other);
    }
    return *this;
  }

  // Move operations
  ImmutableVector(ImmutableVector&& other) noexcept {
    this->header_ = other.header_;
    other.header_ = nullptr;
  }

  ImmutableVector& operator=(ImmutableVector&& other) noexcept {
    if (this != &other) {
      this->~ImmutableVector();
      new (this) ImmutableVector(std::move(other));
    }
    return *this;
  }

  // Destructor
  ~ImmutableVector() {
    if (this->header_) {
      auto* cur = &(this->header_->item0);
      auto* limit = cur + this->size();
      while (cur != limit) {
        (*cur).~T();
        cur++;
      }
      Allocator::Free(this->header_);
    }
  }

 protected:
  friend class ImmutableVectorView<T>;

  using Header = typename ImmutableVectorView<T>::Header;

  // Don't use std::max() here to avoid including <algorithm> which is massive.
  static constexpr size_t kAlignment = alignof(T) > alignof(Header)
                                           ? alignof(T)
                                           : alignof(Header);

  using Allocator = AlignedAlloc<kAlignment>;

  static Header* AllocateFor(size_t count) {
    if (!count)
      return nullptr;

    auto* header = reinterpret_cast<Header*>(
        Allocator::Alloc(sizeof(Header) + (count - 1u) * sizeof(T)));
    header->size = count;
    return header;
  }

  static Header* AllocateAndCopyFrom(const T* begin, size_t size) {
    Header* header = AllocateFor(size);
    if (size) {
      T* dst = &(header->item0);
      T* limit = dst + size;
      for (; dst != limit; ++dst, ++begin)
        new (dst) T(*begin);
    }
    return header;
  }
};

#endif  // TOOLS_GN_IMMUTABLE_VECTOR_H_