#ifndef TESTS_TEST_VECTOR_TYPE_H_
#define TESTS_TEST_VECTOR_TYPE_H_

#include <cstddef>
#include <new>
#include <utility>

namespace flatbuffers {
namespace tests {

// A minimal stand-in for a custom vector-like container (e.g.
// eastl::vector), used to exercise the --cpp-vector-type flatc option.
//
// This is deliberately its own contiguous container rather than a wrapper
// around std::vector, so that:
//  - generated code can't silently keep depending on any of the
//    std::vector-specific overloads in flatbuffer_builder.h (e.g.
//    CreateVector(const std::vector<T,Alloc>&)), and
//  - CustomVector<bool> stores plain contiguous bools accessible via
//    data(), unlike std::vector<bool>'s bit-packed specialization, matching
//    how real alternative containers such as eastl::vector behave.
template<typename T>
class CustomVector {
 public:
  using value_type = T;
  using iterator = T*;
  using const_iterator = const T*;

  CustomVector() = default;
  CustomVector(const CustomVector& o) { assign_copy(o); }
  CustomVector(CustomVector&& o) noexcept { steal(o); }

  CustomVector& operator=(const CustomVector& o) {
    if (this != &o) {
      destroy_all();
      deallocate();
      assign_copy(o);
    }
    return *this;
  }

  CustomVector& operator=(CustomVector&& o) noexcept {
    if (this != &o) {
      destroy_all();
      deallocate();
      steal(o);
    }
    return *this;
  }

  ~CustomVector() {
    destroy_all();
    deallocate();
  }

  void reserve(size_t n) {
    if (n > capacity_) grow_to(n);
  }

  void resize(size_t n) {
    if (n > capacity_) grow_to(n);
    for (size_t i = n; i < size_; ++i) data_[i].~T();
    for (size_t i = size_; i < n; ++i) new (&data_[i]) T();
    size_ = n;
  }

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

  T* data() { return data_; }
  const T* data() const { return data_; }

  T& operator[](size_t i) { return data_[i]; }
  const T& operator[](size_t i) const { return data_[i]; }

  template<typename... Args>
  void emplace_back(Args&&... args) {
    if (size_ == capacity_) grow_to(capacity_ == 0 ? 1 : capacity_ * 2);
    new (&data_[size_]) T(std::forward<Args>(args)...);
    ++size_;
  }
  void push_back(const T& v) { emplace_back(v); }

  iterator begin() { return data_; }
  iterator end() { return data_ + size_; }
  const_iterator begin() const { return data_; }
  const_iterator end() const { return data_ + size_; }
  const_iterator cbegin() const { return data_; }
  const_iterator cend() const { return data_ + size_; }

 private:
  void grow_to(size_t n) {
    T* new_data = static_cast<T*>(::operator new(n * sizeof(T)));
    for (size_t i = 0; i < size_; ++i) {
      new (&new_data[i]) T(std::move(data_[i]));
      data_[i].~T();
    }
    ::operator delete(data_);
    data_ = new_data;
    capacity_ = n;
  }

  void assign_copy(const CustomVector& o) {
    data_ = o.size_ ? static_cast<T*>(::operator new(o.size_ * sizeof(T)))
                     : nullptr;
    capacity_ = o.size_;
    for (size_t i = 0; i < o.size_; ++i) new (&data_[i]) T(o.data_[i]);
    size_ = o.size_;
  }

  void steal(CustomVector& o) {
    data_ = o.data_;
    size_ = o.size_;
    capacity_ = o.capacity_;
    o.data_ = nullptr;
    o.size_ = o.capacity_ = 0;
  }

  void destroy_all() {
    for (size_t i = 0; i < size_; ++i) data_[i].~T();
    size_ = 0;
  }

  void deallocate() {
    ::operator delete(data_);
    data_ = nullptr;
    capacity_ = 0;
  }

  T* data_ = nullptr;
  size_t size_ = 0;
  size_t capacity_ = 0;
};

template<typename T>
bool operator==(const CustomVector<T>& lhs, const CustomVector<T>& rhs) {
  if (lhs.size() != rhs.size()) return false;
  for (size_t i = 0; i < lhs.size(); ++i) {
    if (!(lhs[i] == rhs[i])) return false;
  }
  return true;
}

template<typename T>
bool operator!=(const CustomVector<T>& lhs, const CustomVector<T>& rhs) {
  return !(lhs == rhs);
}

}  // namespace tests
}  // namespace flatbuffers

#endif  // TESTS_TEST_VECTOR_TYPE_H_
