#ifndef SUBITEM_VALUE_PER_ITEM_HPP
#define SUBITEM_VALUE_PER_ITEM_HPP

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

#include <ConnectivityMatrix.hpp>

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

 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));
    }
  };

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

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

    SubViewConst(const SubViewConst&) = delete;

    KOKKOS_INLINE_FUNCTION
    SubViewConst(SubViewConst&&) = default;

    KOKKOS_INLINE_FUNCTION
    SubViewConst(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));
    }
  };

  SubItemValuePerItem& operator=(const SubItemValuePerItem&) = default;

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

  KOKKOS_FORCEINLINE_FUNCTION
  const 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];
  }

  KOKKOS_FORCEINLINE_FUNCTION
  const 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>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);
  }

  KOKKOS_INLINE_FUNCTION
  SubViewConst 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 SubViewConst(m_values, cell_begin, cell_end);
  }

  SubItemValuePerItem() = default;

  template <typename ConnectivityType>
  SubItemValuePerItem(const ConnectivityType& connectivity)
  {
    ConnectivityMatrix connectivity_matrix
        = connectivity.template itemToItemMatrix<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>;

#endif // SUBITEM_VALUE_PER_ITEM_HPP