#ifndef SUBITEM_VALUE_PER_ITEM_HPP
#define SUBITEM_VALUE_PER_ITEM_HPP

#include <Kokkos_StaticCrsGraph.hpp>
#include <TypeOfItem.hpp>
#include <PastisAssert.hpp>

#include <ConnectivityMatrix.hpp>
#include <IConnectivity.hpp>
#include <ConnectivityUtils.hpp>

template <typename DataType,
          TypeOfItem SubItemType,
          TypeOfItem ItemType>
class SubItemValuePerItem
{
 private:
  ConnectivityMatrix::HostRowType m_host_row_map;
  Kokkos::View<DataType*> m_values;

  friend SubItemValuePerItem<std::add_const_t<DataType>, SubItemType, ItemType>;

 public:
  class SubView
  {
   private:
    KOKKOS_RESTRICT DataType* const m_sub_values;
    const size_t m_size;
   public:
    KOKKOS_INLINE_FUNCTION
    const DataType& operator[](const size_t& i) const
    {
      Assert(i<m_size);
      return m_sub_values[i];
    }

    KOKKOS_FORCEINLINE_FUNCTION
    DataType& operator[](const size_t& i)
    {
      Assert(i<m_size);
      return m_sub_values[i];
    }

    KOKKOS_INLINE_FUNCTION
    const size_t& size() const
    {
      return m_size;
    }

    SubView(const SubView&) = delete;

    KOKKOS_INLINE_FUNCTION
    SubView(SubView&&) = default;

    KOKKOS_INLINE_FUNCTION
    SubView(const Kokkos::View<DataType*>& values,
            const size_t& begin,
            const size_t& end)
        : m_sub_values(&(values[begin])),
          m_size(end-begin)
    {
      Assert(begin<=end);
      Assert(end<=values.extent(0));
    }
  };

  KOKKOS_FORCEINLINE_FUNCTION
  DataType& operator()(const size_t& j, const size_t& r)
  {
    return m_values[m_host_row_map(j)+r];
  }

  // Following Kokkos logic, these classes are view and const view does allow
  // changes in data
  KOKKOS_FORCEINLINE_FUNCTION
  DataType& operator()(const size_t& j, const size_t& r) const
  {
    return m_values[m_host_row_map(j)+r];
  }

  KOKKOS_INLINE_FUNCTION
  size_t numberOfValues() const
  {
    return m_values.extent(0);
  }

  KOKKOS_FORCEINLINE_FUNCTION
  DataType& operator[](const size_t& i)
  {
    return m_values[i];
  }

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

  KOKKOS_INLINE_FUNCTION
  size_t numberOfItems() const
  {
    Assert(m_host_row_map.extent(0) != 0);
    return m_host_row_map.extent(0);
  }

  KOKKOS_INLINE_FUNCTION
  size_t numberOfSubValues(const size_t& i_cell) const
  {
    return m_host_row_map(i_cell+1)-m_host_row_map(i_cell);
  }

  KOKKOS_INLINE_FUNCTION
  SubView itemValues(const size_t& i_cell)
  {
    const auto& cell_begin = m_host_row_map(i_cell);
    const auto& cell_end = m_host_row_map(i_cell+1);
    return SubView(m_values, cell_begin, cell_end);
  }

  // Following Kokkos logic, these classes are view and const view does allow
  // changes in data
  KOKKOS_INLINE_FUNCTION
  SubView itemValues(const size_t& i_cell) const
  {
    const auto& cell_begin = m_host_row_map(i_cell);
    const auto& cell_end = m_host_row_map(i_cell+1);
    return SubView(m_values, cell_begin, cell_end);
  }

  template <typename DataType2>
  KOKKOS_INLINE_FUNCTION
  SubItemValuePerItem&
  operator=(const SubItemValuePerItem<DataType2, SubItemType, ItemType>& sub_item_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 SubItemValuePerItem 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 const  SubItemValuePerItem to a non const SubItemValuePerItem");
    m_host_row_map = sub_item_value_per_item.m_host_row_map;
    m_values = sub_item_value_per_item.m_values;
    return *this;
  }

  template <typename DataType2>
  KOKKOS_INLINE_FUNCTION
  SubItemValuePerItem(const SubItemValuePerItem<DataType2, SubItemType, ItemType>& sub_item_value_per_item)
  {
    this->operator=(sub_item_value_per_item);
  }

  SubItemValuePerItem() = default;

  SubItemValuePerItem(const IConnectivity& connectivity)
  {
    ConnectivityMatrix connectivity_matrix
        = connectivity.getMatrix(ItemType,SubItemType);

    m_host_row_map = connectivity_matrix.rowsMap();
    m_values = Kokkos::View<DataType*>("values", connectivity_matrix.numEntries());
  }

  ~SubItemValuePerItem() = default;
};

template <typename DataType>
using NodeValuePerCell = SubItemValuePerItem<DataType, TypeOfItem::node, TypeOfItem::cell>;

template <typename DataType>
using NodeValuePerFace = SubItemValuePerItem<DataType, TypeOfItem::node, TypeOfItem::face>;

template <typename DataType>
using FaceValuePerNode = SubItemValuePerItem<DataType, TypeOfItem::face, TypeOfItem::node>;

template <typename DataType>
using FaceValuePerCell = SubItemValuePerItem<DataType, TypeOfItem::face, TypeOfItem::cell>;

template <typename DataType>
using CellValuePerNode = SubItemValuePerItem<DataType, TypeOfItem::cell, TypeOfItem::node>;

template <typename DataType>
using CellValuePerFace = SubItemValuePerItem<DataType, TypeOfItem::cell, TypeOfItem::face>;

#endif // SUBITEM_VALUE_PER_ITEM_HPP