// SPDX-License-Identifier: Apache-2.0
// Copyright Apache Software Foundation 2019
/** @file

    Container that acts like a vector but has static storage to avoid memory allocation for
    some specified number of elements.
*/

#pragma once

#include <array>
#include <vector>
#include <variant>
#include <new>
#include <cstddef>

#include <swoc/MemSpan.h>
#include <swoc/swoc_meta.h>

namespace swoc { inline namespace SWOC_VERSION_NS {

/** Vectray provides a combination of static and dynamic storage modeled as an array.
 *
 * @tparam T Type of elements in the array.
 * @tparam N Number of statically allocated elements.
 * @tparam A Allocator.
 *
 * The goal is to provide static storage for the common case, avoiding memory allocation, while
 * still handling exceptional cases that need more storage. A common case is for @a N == 1 where
 * there is almost always a single value, but it is possible to have multiple values. @c Vectray
 * makes the single value case require no allocation while transparently handling the multiple
 * value case.
 *
 * The interface is designed to mimic that of @c std::vector.
 */
template <typename T, size_t N, class A = std::allocator<T>> class Vectray {
  using self_type   = Vectray;           ///< Self reference type.
  using vector_type = std::vector<T, A>; ///< Internal dynamic storage type.

public:                                                              // STL compliance types.
  using value_type      = T;                                         ///< Element type for container.
  using reference       = std::remove_reference<T> &;                ///< Reference to element.
  using const_reference = std::remove_reference<T> const &;          ///< Reference to constant element.
  using pointer         = std::remove_reference<T> *;                ///< Pointer to element.
  using const_pointer   = std::remove_reference<T> const *;          ///< Pointer to constant element.
  using allocator_type  = A;                                         ///< Dynamic storage allocator.
  using size_type       = typename vector_type::size_type;           ///< Type for element count.
  using difference_type = typename vector_type::difference_type;     ///< Iterator difference.
  using iterator        = typename swoc::MemSpan<T>::iterator;       ///< Element iteration.
  using const_iterator  = typename swoc::MemSpan<const T>::iterator; ///< Constant element iteration.
  // Need to add reverse iterators - @c reverse_iterator and @c const_reverse_iterator

  /// Internal (fixed) storage.
  struct FixedStore {
    std::array<std::byte, sizeof(T) * N> _raw; ///< Raw memory for element storage.
    size_t _count = 0;                         ///< Number of valid elements.
    allocator_type _a;                         ///< Allocator instance - used for construction.

    FixedStore() = default; ///< Default construct - empty.

    /** Construct with specific allocator @a a.
     *
     * @param a Allocator.
     */
    explicit FixedStore(allocator_type const &a) : _a(a) {}

    ~FixedStore();

    /// @return A span containing the valid elements.
    MemSpan<T> span();

    /// @return A pointer to the data.
    T *data();

    /// @return A pointer to the data.
    T const *data() const;
  };

  using DynamicStore = vector_type; ///< Dynamic (heap) storage.

  /// Element access.
  using span = swoc::MemSpan<T>;
  /// Constant element access.
  using const_span = swoc::MemSpan<T const>;

public:
  /// Default constructor, construct an empty container.
  Vectray();
  /// Destructor - destructs all contained elements.
  /// @internal The internal store takes care of the details.
  ~Vectray() = default;

  /// Construct empty instance with allocator.
  constexpr explicit Vectray(allocator_type const &a) : _store(std::in_place_type_t<FixedStore>{}, a) {}

  /** Construct with @a n default constructed elements.
   *
   * @param n Number of elements.
   * @param alloc Allocator (optional - default constructed if not a parameter).
   */
  explicit Vectray(size_type n, allocator_type const &alloc = allocator_type{});

  /// Move constructor for difference static sized instance.
  template <size_t M> Vectray(Vectray<T, M, A> &&that);

  /// Move constructor.
  Vectray(self_type &&that, allocator_type const &a);

  /// @return The number of elements in the container.
  size_type size() const;

  /// @return A pointer to the data.
  T *data();

  /// @return A pointer to the data.
  T const *data() const;

  /// @return @c true if no valid elements, @c false if at least one valid element.
  bool empty() const;

  /// Implicitly convert to a @c MemSpan.
  operator span() { return this->items(); }
  /// Implicitly convert to a @c MemSpan.
  operator const_span() const { return this->items(); }

  /** Index operator.
   *
   * @param idx Index of element.
   * @return A reference to the element.
   */
  T &operator[](size_type idx);

  /** Index operator (const).
   *
   * @param idx Index of element.
   * @return A @c const reference to the element.
   */
  T const &operator[](size_type idx) const;

  /// @return A reference to the first element.
  T const &
  front() const {
    return (*this)[0];
  }

  /// @return A reference to the first element.
  T &
  front() {
    return (*this)[0];
  }

  /// @return A reference to the last element.
  T const &
  back() const {
    return (*this)[this->size() - 1];
  }

  /// @return A reference to the last element.
  T &
  back() {
    return (*this)[this->size() - 1];
  }
  /** Append an element by copy.
   *
   * @param t Element to add.
   * @return @a this.
   */
  self_type &push_back(T const &t);

  /** Append an element by move.
   *
   * @param t Element to add.
   * @return @a this.
   */
  self_type &push_back(T &&t);

  /** Append an element by direct construction.
   *
   * @tparam Args Constructor parameter types.
   * @param args Constructor arguments.
   * @return @a this
   */
  template <typename... Args> self_type &emplace_back(Args &&...args);

  /** Remove an element from the end of the current elements.
   *
   * @return @a this.
   */
  self_type &pop_back();

  /// Iterator for first element.
  const_iterator begin() const;

  /// Iterator past last element.
  const_iterator end() const;

  /// Iterator for last element.
  iterator begin();

  /// Iterator past last element.
  iterator end();

  /// Force at internal storage to hold at least @a n items.
  void reserve(size_type n);

protected:
  /// Content storage.
  /// @note This is constructed as fixed but can change to dynamic. It can never change back.
  std::variant<FixedStore, DynamicStore> _store;

  static constexpr auto FIXED   = 0; ///< Variant index for fixed storage.
  static constexpr auto DYNAMIC = 1; ///< Variant index for dynamic storage.

  /// Get the span of the valid items.
  span items();
  /// Get the span of the valid items.
  const_span items() const;

  /// Default size to reserve in the vector when switching to dynamic.
  static constexpr size_type BASE_DYNAMIC_SIZE = (7 * N) / 5;

  /** Transfer from fixed storage to dynamic storage.
   *
   * @param rN Numer of elements of storage to reserve in the vector.
   *
   * @note Must be called at most once for any instance.
   */
  void transfer(size_type rN = BASE_DYNAMIC_SIZE);
};

// --- Implementation ---

template <typename T, size_t N, typename A> Vectray<T, N, A>::Vectray() {}

template <typename T, size_t N, class A> Vectray<T, N, A>::Vectray(Vectray::size_type n, allocator_type const &) : Vectray() {
  this->reserve(n);
  while (n-- > 0) {
    this->emplace_back();
  }
}

template <typename T, size_t N, class A> template <size_t M> Vectray<T, N, A>::Vectray(Vectray<T, M, A> &&that) {
  // If @a that is already a vector, always move that here.
  if (DYNAMIC == that._store.index()) {
    _store = std::move(std::get<DYNAMIC>(that._store));
  } else {
    auto span = std::get<FIXED>(that._store).span();
    if (span.size() > N) {
    } else {
      for (auto &&item : span) {
        this->template emplace_back<T, N, A>(std::move(item));
      }
    }
  }
}

template <typename T, size_t N, class A> Vectray<T, N, A>::FixedStore::~FixedStore() {
  for (auto &item : this->span()) {
    std::destroy_at(std::addressof(item));
  }
}

template <typename T, size_t N, class A>
MemSpan<T>
Vectray<T, N, A>::FixedStore::span() {
  return MemSpan<std::byte>(_raw).template rebind<T>();
}

template <typename T, size_t N, class A>
T *
Vectray<T, N, A>::FixedStore::data() {
  return reinterpret_cast<T *>(_raw.data());
}

template <typename T, size_t N, class A>
T const *
Vectray<T, N, A>::FixedStore::data() const {
  return reinterpret_cast<T *>(_raw.data());
}

template <typename T, size_t N, typename A>
T &
Vectray<T, N, A>::operator[](size_type idx) {
  return this->items()[idx];
}

template <typename T, size_t N, typename A>
T const &
Vectray<T, N, A>::operator[](size_type idx) const {
  return this->items()[idx];
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::push_back(const T &t) -> self_type & {
  std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void {
                                if (fs._count < N) {
                                  new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(t); // A::traits ?
                                } else {
                                  this->transfer();
                                  std::get<DYNAMIC>(_store).push_back(t);
                                }
                              },
                              [&](DynamicStore &ds) -> void { ds.push_back(t); }},
             _store);
  return *this;
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::push_back(T &&t) -> self_type & {
  std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void {
                                if (fs._count < N) {
                                  new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(std::move(t)); // A::traits ?
                                } else {
                                  this->transfer();
                                  std::get<DYNAMIC>(_store).push_back(std::move(t));
                                }
                              },
                              [&](DynamicStore &ds) -> void { ds.push_back(std::move(t)); }},
             _store);
  return *this;
}

template <typename T, size_t N, class A>
template <typename... Args>
typename Vectray<T, N, A>::self_type &
Vectray<T, N, A>::emplace_back(Args &&...args) {
  if (_store.index() == FIXED) {
    auto &fs{std::get<FIXED>(_store)};
    if (fs._count < N) {
      new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(std::forward<Args>(args)...); // A::traits ?
      return *this;
    }
    this->transfer(); // transfer to dynamic and fall through to add item.
  }
  std::get<DYNAMIC>(_store).emplace_back(std::forward<Args>(args)...);
  return *this;
}

template <typename T, size_t N, class A>
auto
Vectray<T, N, A>::pop_back() -> self_type & {
  std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void { std::destroy_at(fs.span()[--fs._count]); },
                              [&](DynamicStore &ds) -> void { ds.pop_back(); }},
             _store);
  return *this;
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::size() const -> size_type {
  return std::visit(
    swoc::meta::vary{[](FixedStore const &fs) { return fs._count; }, [](DynamicStore const &ds) { return ds.size(); }}, _store);
}

template <typename T, size_t N, typename A>
bool
Vectray<T, N, A>::empty() const {
  return std::visit(
    swoc::meta::vary{[](FixedStore const &fs) { return fs._count == 0; }, [](DynamicStore const &ds) { return ds.empty(); }},
    _store);
}

// --- iterators
template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::begin() const -> const_iterator {
  return this->items().begin();
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::end() const -> const_iterator {
  return this->items().end();
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::begin() -> iterator {
  return this->items().begin();
}

template <typename T, size_t N, typename A>
auto
Vectray<T, N, A>::end() -> iterator {
  return this->items().end();
}
// --- iterators

template <typename T, size_t N, class A>
void
Vectray<T, N, A>::transfer(size_type rN) {
  DynamicStore tmp{std::get<FIXED>(_store)._a};
  tmp.reserve(rN);

  for (auto &&item : this->items()) {
    tmp.emplace_back(std::move(item)); // move if supported, copy if not.
  }
  // Fixed elements destroyed here, by variant.
  _store = std::move(tmp);
}

template <typename T, size_t N, class A>
auto
Vectray<T, N, A>::items() const -> const_span {
  return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.span(); },
                                     [](DynamicStore const &ds) { return const_span(ds.data(), ds.size()); }},
                    _store);
}

template <typename T, size_t N, class A>
T *
Vectray<T, N, A>::data() {
  return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.data(); }, [](DynamicStore const &ds) { return ds.data(); }},
                    _store);
}

template <typename T, size_t N, class A>
T const *
Vectray<T, N, A>::data() const {
  return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.data(); }, [](DynamicStore const &ds) { return ds.data(); }},
                    _store);
}

template <typename T, size_t N, class A>
auto
Vectray<T, N, A>::items() -> span {
  return std::visit(
    swoc::meta::vary{[](FixedStore &fs) { return fs.span(); }, [](DynamicStore &ds) { return span(ds.data(), ds.size()); }},
    _store);
}

template <typename T, size_t N, class A>
void
Vectray<T, N, A>::reserve(Vectray::size_type n) {
  if (DYNAMIC == _store.index()) {
    std::get<DYNAMIC>(_store).reserve(n);
  } else if (n > N) {
    this->transfer(n);
  }
}

}} // namespace swoc::SWOC_VERSION_NS
