#ifndef ITEM_ARRAY_HPP
#define ITEM_ARRAY_HPP

#include <mesh/IConnectivity.hpp>
#include <mesh/ItemId.hpp>
#include <mesh/ItemType.hpp>
#include <utils/PugsAssert.hpp>
#include <utils/Table.hpp>

#include <memory>

template <typename DataType, ItemType item_type, typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
class ItemArray
{
 public:
  static constexpr ItemType item_t{item_type};
  using data_type = DataType;

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

 private:
  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
  using ConnectivityWeakPtr   = std::weak_ptr<const IConnectivity>;

  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);

  ConnectivityPtr m_connectivity_ptr;

  Table<DataType> m_values;

  // Allow const std:shared_ptr version to access our data
  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivitySharedPtr>;

  // Allow const std:weak_ptr version to access our data
  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivityWeakPtr>;

  // Allow non-const std:shared_ptr version to access our data
  friend ItemArray<std::remove_const_t<DataType>, item_type, ConnectivitySharedPtr>;

  // Allow non-const std:weak_ptr version to access our data
  friend ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityWeakPtr>;

 public:
  // This is not the correct way to look at ItemArray, use with care
  Table<const DataType>
  tableView() const
  {
    return m_values;
  }

  [[nodiscard]] friend PUGS_INLINE ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
  copy(const ItemArray<DataType, item_type, ConnectivityPtr>& source)
  {
    ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image;

    image.m_connectivity_ptr = source.m_connectivity_ptr;
    image.m_values           = copy(source.m_values);
    return image;
  }

  template <typename ConnectivityPtr2>
  friend PUGS_INLINE void copy_to(const ItemArray<std::add_const_t<DataType>, item_type, ConnectivityPtr>& source,
                                  const ItemArray<DataType, item_type, ConnectivityPtr2>& destination);

  template <typename ConnectivityPtr2>
  friend PUGS_INLINE void
  copy_to(const ItemArray<DataType, item_type, ConnectivityPtr>& source,
          const ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr2>& destination)
  {
    Assert(destination.connectivity_ptr() == source.connectivity_ptr(), "different connectivities");
    Assert(source.sizeOfArrays() == destination.sizeOfArrays(), "incompatible size of arrays")
      copy_to(source.m_values, destination.m_values);
  }

  [[nodiscard]] PUGS_INLINE bool
  isBuilt() const noexcept
  {
    return m_connectivity_ptr.use_count() != 0;
  }

  [[nodiscard]] PUGS_INLINE std::shared_ptr<const IConnectivity>
  connectivity_ptr() const noexcept
  {
    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
      return m_connectivity_ptr;
    } else {
      return m_connectivity_ptr.lock();
    }
  }

  PUGS_INLINE
  void
  fill(const DataType& data) const noexcept
  {
    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemArray of const");
    m_values.fill(data);
  }

  template <ItemType item_t>
  [[nodiscard]] PUGS_INLINE typename Table<DataType>::UnsafeRowView
  operator[](const ItemIdT<item_t>& item_id) const noexcept(NO_ASSERT)
  {
    static_assert(item_t == item_type, "invalid ItemId type");
    Assert(this->isBuilt(), "ItemArray is not built");
    Assert(item_id < this->numberOfItems(), "invalid item_id");
    return m_values[item_id];
  }

  [[nodiscard]] PUGS_INLINE size_t
  numberOfItems() const noexcept(NO_ASSERT)
  {
    Assert(this->isBuilt(), "ItemArray is not built");
    return m_values.numberOfRows();
  }

  [[nodiscard]] PUGS_INLINE size_t
  sizeOfArrays() const
  {
    Assert(this->isBuilt(), "ItemArray is not built");
    return m_values.numberOfColumns();
  }

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

    m_values = array_per_item.m_values;

    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
      m_connectivity_ptr = array_per_item.m_connectivity_ptr.lock();
    } else {
      m_connectivity_ptr = array_per_item.m_connectivity_ptr;
    }

    return *this;
  }

  friend std::ostream&
  operator<<(std::ostream& os, const ItemArray& item_array)
  {
    os << item_array.m_values;
    return os;
  }

  template <typename DataType2, typename ConnectivityPtr2>
  PUGS_INLINE
  ItemArray(const ItemArray<DataType2, item_type, ConnectivityPtr2>& array_per_item) noexcept
  {
    this->operator=(array_per_item);
  }

  PUGS_INLINE
  ItemArray() = default;

  PUGS_INLINE
  ItemArray(const IConnectivity& connectivity, size_t size_of_array) noexcept
    : m_connectivity_ptr{connectivity.shared_ptr()}, m_values{connectivity.numberOf<item_type>(), size_of_array}
  {
    static_assert(not std::is_const_v<DataType>, "Cannot allocate ItemArray of const data: only view is "
                                                 "supported");
  }

  PUGS_INLINE
  ItemArray(const IConnectivity& connectivity, const Table<DataType>& table) noexcept(NO_ASSERT)
    : m_connectivity_ptr{connectivity.shared_ptr()}, m_values{table}
  {
    Assert(connectivity.numberOf<item_type>() == table.numberOfRows(), "invalid table, wrong number of rows");
  }

  PUGS_INLINE
  ~ItemArray() = default;
};

template <typename DataType>
using NodeArray = ItemArray<DataType, ItemType::node>;

template <typename DataType>
using EdgeArray = ItemArray<DataType, ItemType::edge>;

template <typename DataType>
using FaceArray = ItemArray<DataType, ItemType::face>;

template <typename DataType>
using CellArray = ItemArray<DataType, ItemType::cell>;

// Weak versions: should not be used outside of Connectivity

template <typename DataType, ItemType item_type>
using WeakItemArray = ItemArray<DataType, item_type, std::weak_ptr<const IConnectivity>>;

template <typename DataType>
using WeakNodeArray = WeakItemArray<DataType, ItemType::node>;

template <typename DataType>
using WeakEdgeArray = WeakItemArray<DataType, ItemType::edge>;

template <typename DataType>
using WeakFaceArray = WeakItemArray<DataType, ItemType::face>;

template <typename DataType>
using WeakCellArray = WeakItemArray<DataType, ItemType::cell>;

#endif   // ITEM_ARRAY_HPP