#ifndef IQS_MPI_ENV_HPP
#define IQS_MPI_ENV_HPP

/// \addtogroup util
/// @{

/// @file mpi_env.hpp
///
/// This header includes the MPI library if available and provides some
/// convenience functions that are implemented even if the MPI library is not provided.

#ifdef INTELQS_HAS_MPI
#include <mpi.h>
#endif

#include <string>
#include <unistd.h>
#include <stdexcept>

#ifdef INTELQS_HAS_MPI
#include "mpi_exception.hpp"
#endif

#ifdef _OPENMP
#include <omp.h>
#endif

/////////////////////////////////////////////////////////////////////////////////////////

namespace iqs {

namespace mpi {

/////////////////////////////////////////////////////////////////////////////////////////
/// \class Environment
/// A trimmed down version of the BOOST::MPI environment.  Its purpose is to initialize
/// the MPI library and partition the cluster or single threaded environment for parallel
/// operations.
/// In preparation of the Monte-Carlo simulations required for the noisy implementation,
/// we provide a communicator over the ranks involved in a single MC simulation.
///
/// Specifically, we store two communicators:
/// 1. pool_communicator: spanning all the useful ranks
/// 2. state_communicator: spanning those ranks used in a single MC simulation (i.e. state)

class Environment
{
  public:
  /// Intialize the MPI Environment.
  ///
  /// It receives the same argc and argv arguments passed to the main function.
  /// If MPI is present, but has not been initialized, then MPI_Init will be called.

  Environment(int& argc, char**& argv, bool is_verbose = true);
  Environment();

  /// Finalize the MPI Environment
  ///
  /// If MPI is present and has been initizlized in the constructor then
  /// MPI_Finalize will be called here.

  ~Environment();
  Environment(Environment const&) = delete;
  Environment& operator=(Environment const&) = delete;

  /////////////////////////////////////
  // One needs to know what he/she is doing to call this method.
  /// Update the state and pool communicators.
  /// @pre This can only be called when all ranks are still active.
  static void UpdateStateComm (int num_states);
  /////////////////////////////////////

  /// Check whether the rank is useful or not.
  static bool IsUsefulRank() {return useful_rank;}

  /// The rank of the current MPI process: pool or state.
  ///
  /// The PoolRank may not corresponds to that from MPI_COMM_WORLD due to dummy ranks.
  /// The rank is 0 if MPI is not present.
  /// @pre If MPI is present, this can only be called after intializing MPI.
  static int GetPoolRank();
  static int GetStateRank();
  static int GetRank() {return GetStateRank();}

  /// Number of MPI processes.
  ///
  /// The PoolSize may not corresponds to that from MPI_COMM_WORLD due to dummy ranks.
  /// The number of processes is 1 if MPI is not present.
  /// @pre If MPI is present, this can only be called after intializing MPI.
  static int GetPoolSize();
  static int GetStateSize();
  static int GetSize() {return GetStateSize();}

  /// Get incoherent average over all states of the pool.
  ///
  /// @param local_value the address of the value stored in the local rank.
  template <class Type>
  static Type IncoherentSumOverAllStatesOfPool(Type local_value);

  static int GetNumRanksPerNode();
  static int GetNumNodes();
  static int GetNodeId();
  static int GetStateId();
  static int GetNumStates();
  static void RemapStateRank(int newme);

#ifdef INTELQS_HAS_MPI
  static MPI_Comm pool_communicator;
  static MPI_Comm state_communicator;
  static MPI_Comm GetPoolComm();
  static MPI_Comm GetStateComm();
  static MPI_Comm GetComm();
#endif


/// The singleton interface that is used for global management of MPI resources.
  static void Init();
  static void Init(int &argc, char**&argv);
  static void Finalize();
  static Environment *GetSharedInstance() {return shared_instance;}

 private:
// Shared helper for constructor overload
#ifdef INTELQS_HAS_MPI
  void CommonInit(int flag);
#endif

  static Environment *shared_instance;

  bool inited_;
  static bool useful_rank;
//  int num_ranks_per_state;

  static int num_ranks_per_node;

  static int num_nodes;
  static int my_node_id;
  static int num_states;
  static int my_state_id;
  static bool is_verbose; // Enable description of MPI ranks, default: true

#ifdef INTELQS_HAS_MPI
  MPI_Request synch_request;
#endif
};

/////////////////////////////////////////////////////////////////////////////////////////

/// @brief An MPI barrier
///
/// It waits until all MPI processes have reached this call. This function does nothing if
/// MPI is not present.
void PoolBarrier();
void StateBarrier();
void Barrier();

/////////////////////////////////////////////////////////////////////////////////////////

/// @brief Print from all MPI processes
///
/// It prints a string from all processes if @c all is true or just from the master process
///with rank 0 if @c all is false. If MPI is not present it prints the string.
///
/// If @c all is set, the string is prefixed by the number of the MPI process.
///
/// @param s the string to be printed
/// @param all a flag to specify if all processes should print or just the master process
void PoolPrint(std::string s, bool all=false);
void StatePrint(std::string s, bool all=false);
void Print(std::string s, bool all=false);
#ifdef INTELQS_HAS_MPI
void Print(std::string s, MPI_Comm comm, bool all=true);
#endif



/////////////////////////////////////////////////////////////////////////////////////////

}	// end namespace mpi
}	// end namespace iqs

/// @}*/

#endif	// header guard IQS_MPI_ENV_HPP
