#ifndef SUB_ARRAY_HPP
#define SUB_ARRAY_HPP

#include <utils/Array.hpp>
#include <utils/PugsAssert.hpp>
#include <utils/PugsMacros.hpp>
#include <utils/PugsUtils.hpp>

#include <algorithm>

template <typename DataType>
class [[nodiscard]] SubArray
{
 public:
  using data_type  = DataType;
  using index_type = size_t;

 private:
  // underlying array
  Array<DataType> m_array;

  DataType* m_sub_values;
  size_t m_size;

  // Allows const version to access our data
  friend SubArray<std::add_const_t<DataType>>;

 public:
  PUGS_INLINE size_t size() const noexcept
  {
    return m_size;
  }

  PUGS_INLINE DataType& operator[](index_type i) const noexcept(NO_ASSERT)
  {
    Assert(i < m_size);
    return m_sub_values[i];
  }

  PUGS_INLINE
  void fill(const DataType& data) const
  {
    static_assert(not std::is_const<DataType>(), "Cannot modify SubArray of const");

    // could consider to use std::fill
    parallel_for(
      this->size(), PUGS_LAMBDA(index_type i) { m_sub_values[i] = data; });
  }

  template <typename DataType2>
  PUGS_INLINE SubArray& operator=(const SubArray<DataType2>& sub_array) noexcept
  {
    // ensures that DataType is the same as source DataType2
    static_assert(std::is_same<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>(),
                  "Cannot assign SubArray of different type");
    // ensures that const is not lost through copy
    static_assert(((std::is_const<DataType2>() and std::is_const<DataType>()) or not std::is_const<DataType2>()),
                  "Cannot assign SubArray of const to SubArray of non-const");

    m_array      = sub_array.m_array;
    m_size       = sub_array.m_size;
    m_sub_values = sub_array.m_sub_values;

    return *this;
  }

  PUGS_INLINE
  SubArray& operator=(const SubArray&) = default;

  PUGS_INLINE
  SubArray& operator=(SubArray&&) = default;

  PUGS_INLINE
  explicit SubArray(Array<DataType> array, size_t begin, size_t size)
    : m_array{array}, m_sub_values{&array[0] + begin}, m_size{size}
  {
    Assert(begin + size <= array.size(), "SubView is not contained in the source Array");
    static_assert(not std::is_const<DataType>(), "Cannot allocate SubArray of const data: only view is "
                                                 "supported");
  }

  PUGS_INLINE
  SubArray() = default;

  PUGS_INLINE
  SubArray(const SubArray&) = default;

  template <typename DataType2>
  PUGS_INLINE SubArray(const SubArray<DataType2>& sub_array) noexcept
  {
    this->operator=(sub_array);
  }

  PUGS_INLINE
  SubArray(SubArray &&) = default;

  PUGS_INLINE
  ~SubArray() = default;
};

#endif   // SUB_ARRAY_HPP