#pragma once

#include <cassert>
#include <mpi.h>

class MpiEnvironment { // wrapper for creating and destroying the environment
	std::size_t _worldRank{std::numeric_limits<std::size_t>::max()};
	std::size_t _worldSize{0};
	bool _isMaster{false};

  public:
	std::size_t worldRank() const { return _worldRank; }
	std::size_t worldSize() const { return _worldSize; }
	bool isMaster() const { return _isMaster; }

	friend void swap(MpiEnvironment& first, MpiEnvironment& second) noexcept {
		using std::swap;
		swap(first._worldRank, second._worldRank);
		swap(first._worldSize, second._worldSize);
		swap(first._isMaster, second._isMaster);
	}

	MpiEnvironment(int& argc, char* argv[], int required_thread_support) {
		int provided_thread_support;
		MPI_Init_thread(&argc, &argv, required_thread_support, &provided_thread_support);
		assert(required_thread_support == provided_thread_support);

		_worldRank = [] { int r;  MPI_Comm_rank(MPI_COMM_WORLD, &r); return static_cast<std::size_t>(r); }();
		_worldSize = [] {int s;  MPI_Comm_size(MPI_COMM_WORLD, &s); return static_cast<std::size_t>(s); }();
		_isMaster = {_worldRank == 0};
	}
	~MpiEnvironment() {
		if (_worldRank != std::numeric_limits<std::size_t>::max()) { MPI_Finalize(); }
	}
	MpiEnvironment(MpiEnvironment&) = delete;
	MpiEnvironment& operator=(MpiEnvironment&) = delete;
	MpiEnvironment(MpiEnvironment&& other) noexcept { swap(*this, other); }
	MpiEnvironment& operator=(MpiEnvironment&& other) noexcept {
		swap(*this, other);
		return *this;
	}
};
