#ifndef ITEM_VALUE_HPP
#define ITEM_VALUE_HPP

#include <PastisAssert.hpp>

#include <Array.hpp>

#include <ItemType.hpp>
#include <ItemId.hpp>

#include <IConnectivity.hpp>

template <typename DataType,
          ItemType item_type>
class ItemValue
{
 public:
  static const ItemType item_t{item_type};
  using data_type = DataType;

  using ItemId = ItemIdT<item_type>;
  using index_type = ItemId;

 private:
  bool m_is_built{false};

  Array<DataType> m_values;

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

 public:
  PASTIS_FORCEINLINE
  const bool& isBuilt() const
  {
    return m_is_built;
  }

  PASTIS_INLINE
  size_t size() const
  {
    Assert(m_is_built);
    return m_values.size();
  }

  // Following Kokkos logic, these classes are view and const view does allow
  // changes in data
  PASTIS_FORCEINLINE
  DataType& operator[](const ItemId& i) const
  {
    Assert(m_is_built);
    return m_values[i];
  }

  template <typename IndexType>
  DataType& operator[](const IndexType& i) const
  {
    static_assert(std::is_same<IndexType,ItemId>(),
                  "ItemValue must be indexed by ItemId");
    return m_values[i];
  }

  PASTIS_INLINE
  size_t numberOfItems() const
  {
    Assert(m_is_built);
    return m_values.size();
  }

  template <typename DataType2>
  PASTIS_INLINE
  ItemValue&
  operator=(const ItemValue<DataType2, item_type>& value_per_item)
  {
    // 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 ItemValue 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  ItemValue of const to ItemValue of non-const");

    m_values = value_per_item.m_values;
    m_is_built = value_per_item.m_is_built;

    return *this;
  }

  template <typename DataType2>
  PASTIS_INLINE
  ItemValue(const ItemValue<DataType2, item_type>& value_per_item)
  {
    this->operator=(value_per_item);
  }

  ItemValue() = default;

  ItemValue(const IConnectivity& connectivity)
      : m_is_built{true},
        m_values(connectivity.numberOf<item_type>())
  {
    static_assert(not std::is_const<DataType>(),
                  "Cannot allocate ItemValue of const data: only view is supported"); ;
  }

  ~ItemValue() = default;
};

template <typename DataType>
using NodeValue = ItemValue<DataType, ItemType::node>;

template <typename DataType>
using EdgeValue = ItemValue<DataType, ItemType::edge>;

template <typename DataType>
using FaceValue = ItemValue<DataType, ItemType::face>;

template <typename DataType>
using CellValue = ItemValue<DataType, ItemType::cell>;

#endif // ITEM_VALUE_HPP