#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>

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

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

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

  SubItemValuePerItem() = default;

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

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

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