#ifndef MESSENGER_HPP
#define MESSENGER_HPP

#include <PastisMacros.hpp>
#include <PastisAssert.hpp>

#include <Array.hpp>

class Messenger
{
 private:
  static Messenger* m_instance;
  Messenger(int& argc, char* argv[]);

  int m_rank{0};
  int m_size{1};

  Array<int> _broadcast(Array<int>& array, int root_rank) const;

 public:
  static void create(int& argc, char* argv[]);
  static void destroy();

  PASTIS_INLINE
  static Messenger& getInstance()
  {
    Assert(m_instance != nullptr);
    return *m_instance;
  }

  PASTIS_INLINE
  const int& rank() const
  {
    return m_rank;
  }

  PASTIS_INLINE
  const int& size() const
  {
    return m_size;
  }

  void barrier() const;

  template <typename DataType>
  PASTIS_INLINE
  Array<DataType> broadcast(const Array<DataType>& array, int root_rank) const
  {
    if constexpr(std::is_same<DataType, int>()) {
      Array<int> int_array = array;
      return _broadcast(int_array, root_rank);
    } else {
      static_assert(std::is_same<DataType, int>(), "unexpected type of data");
    }
  }

  Messenger(const Messenger&) = delete;
  ~Messenger();
};

PASTIS_INLINE
const Messenger& messenger()
{
  return Messenger::getInstance();
}

[[deprecated("use better name")]]
PASTIS_INLINE
const int& commRank()
{
  return messenger().rank();
}

[[deprecated("use better name")]]
PASTIS_INLINE
const int& commSize()
{
  return messenger().size();
}

PASTIS_INLINE
void barrier()
{
  return messenger().barrier();
}

template <typename DataType>
PASTIS_INLINE
Array<DataType> broadcast(const Array<DataType>& array, int root_rank)
{
  return messenger().broadcast(array, root_rank);
}

#endif // MESSENGER_HPP