#include "parallel.h"

#include <cstdlib>
#include <iostream>
#include <vector>

namespace tbplas::base {

/// @brief Initialize GLOBAL MPI environment
/// @param[in] argc number of arguments of main function
/// @param[in] argv arguments of main function
/// @note This function should be called at the beginning of the main function.
void MPIENV_INIT(int argc, char* argv[])
{
#ifdef WITH_MPI
    int status = MPI_Init(&argc, &argv);
    if (status != MPI_SUCCESS) {
        std::cout << "MPI initialization failed\n";
        std::exit(-1);
    }
#endif
}

/// @brief Finalizing GLOBAL MPI environment
/// @note This function should be called before returning from the main
/// function.
void MPIENV_FINALIZE()
{
#ifdef WITH_MPI
    int status = MPI_Finalize();
    if (status != MPI_SUCCESS) {
        std::cout << "MPI finalization failed\n";
        std::exit(-1);
    }
#endif
}

/// @brief Default constructor
/// @note If MPI is enabled during compilation, the world communicator will be
/// used by default.
BaseMPIEnv::BaseMPIEnv()
{
#ifdef WITH_MPI
    int status, flag;
    status = MPI_Initialized(&flag);
    if (status != MPI_SUCCESS || flag != true) {
        std::cout << "MPI not initialized\n";
        std::exit(-1);
    }
    m_Comm = MPI_COMM_WORLD;
#else
    m_Comm = 0;
#endif
    updateRankSize();
}

/// @brief Explicit constructor taking communicator as argument
/// @param[in] comm communicator assigned to this BaseMPIEnv object
BaseMPIEnv::BaseMPIEnv(const MPI_Comm& comm)
{
#ifdef WITH_MPI
    m_Comm = comm;
#else
    m_Comm = 0;
#endif
    updateRankSize();
}

/// @brief Update rank of this process and total number of processes in the
/// communicator
void BaseMPIEnv::updateRankSize()
{
#ifdef WITH_MPI
    int status = MPI_Comm_rank(m_Comm, &m_Rank);
    if (status != MPI_SUCCESS) {
        std::cout << "MPI getting rank failed\n";
        std::exit(-1);
    }
    status = MPI_Comm_size(m_Comm, &m_Size);
    if (status != MPI_SUCCESS) {
        std::cout << "MPI getting size failed\n";
        std::exit(-1);
    }
#else
    m_Rank = 0;
    m_Size = 1;
#endif
}

/// @brief Get rank of this process
/// @return rank of this process
int BaseMPIEnv::getRank() const { return m_Rank; }

/// @brief Get number of processes in the communicator
/// @return number of of processes in the communicator
int BaseMPIEnv::getSize() const { return m_Size; }

/// @brief Get the reference to the communicator
/// @return reference to the communicator
const MPI_Comm& BaseMPIEnv::getComm() const { return m_Comm; }

/// @brief Check if this process is the master process in the communicator
/// @return True if this process is the master process, otherwise false.
bool BaseMPIEnv::isMaster() const { return m_Rank == 0; }

/// @brief Check if there is only one process in the communicator
/// @return True if there is only one process, otherwise false.
bool BaseMPIEnv::isSPMode() const { return m_Size == 1; }

/// @brief Check if MPI support has been enabled during compilation
/// @return True if enabled, otherwise false.
bool BaseMPIEnv::isMPIEnabled() const
{
#ifdef WITH_MPI
    return true;
#else
    return false;
#endif
}

/// @brief Wrapper over MPI_Barrier to make all processes waiting
void BaseMPIEnv::barrier() const
{
#ifdef WITH_MPI
    int status = MPI_Barrier(m_Comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Barrier call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Wrapper over MPI_Comm_free to free the communicator
void BaseMPIEnv::free()
{
#ifdef WITH_MPI
    int status = MPI_Comm_free(&m_Comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Barrier call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Distribute range [0, n_max-1] into num_group groups and return the
/// lower and upper bounds assigned to this process.
/// @note The range is both closed on both bounds, i.e., [i_min, i_max].
RangeInfo BaseMPIEnv::distRange(int n_max, int num_group) const
{
    assert(num_group > 0);
    assert(n_max >= num_group);
    assert((m_Rank < num_group) && (m_Rank >= 0));
    RangeInfo info;
    info.k_min = (m_Rank * n_max) / num_group;
    info.k_max = ((m_Rank + 1) * n_max) / num_group - 1;
    return info;
}

// ----- Generated with macro expansion -----
/* begin func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void AllreduceScalar_(const MPI_Comm& comm, const int& source,
    int& dest)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(&source, &dest, 1, MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source scalar in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @return None
void InplaceAllreduceScalar_(const MPI_Comm& comm, int& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, &source, 1, MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastScalar_(const MPI_Comm& comm, int& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(&source, 1, MPI_INT, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source scalar to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendScalar_(const MPI_Comm& comm, const int& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(&source, 1, MPI_INT, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive scalar from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvScalar_(const MPI_Comm& comm, int& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(&dest, 1, MPI_INT, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void AllreduceScalar_(const MPI_Comm& comm, const double& source,
    double& dest)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(&source, &dest, 1, MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source scalar in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @return None
void InplaceAllreduceScalar_(const MPI_Comm& comm, double& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, &source, 1, MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastScalar_(const MPI_Comm& comm, double& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(&source, 1, MPI_DOUBLE, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source scalar to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendScalar_(const MPI_Comm& comm, const double& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(&source, 1, MPI_DOUBLE, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive scalar from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvScalar_(const MPI_Comm& comm, double& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(&dest, 1, MPI_DOUBLE, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void AllreduceScalar_(const MPI_Comm& comm, const std::complex<double>& source,
    std::complex<double>& dest)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(&source, &dest, 1, MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source scalar in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @return None
void InplaceAllreduceScalar_(const MPI_Comm& comm, std::complex<double>& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, &source, 1, MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastScalar_(const MPI_Comm& comm, std::complex<double>& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(&source, 1, MPI_DOUBLE_COMPLEX, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source scalar to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendScalar_(const MPI_Comm& comm, const std::complex<double>& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(&source, 1, MPI_DOUBLE_COMPLEX, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive scalar from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvScalar_(const MPI_Comm& comm, std::complex<double>& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(&dest, 1, MPI_DOUBLE_COMPLEX, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceMatrixXi_(const MPI_Comm& comm, const Eigen::MatrixXi& source,
    Eigen::MatrixXi& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceMatrixXi_(const MPI_Comm& comm, Eigen::MatrixXi& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastMatrixXi_(const MPI_Comm& comm, Eigen::MatrixXi& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_INT, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendMatrixXi_(const MPI_Comm& comm, const Eigen::MatrixXi& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_INT, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvMatrixXi_(const MPI_Comm& comm, Eigen::MatrixXi& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_INT, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceMatrixXd_(const MPI_Comm& comm, const Eigen::MatrixXd& source,
    Eigen::MatrixXd& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceMatrixXd_(const MPI_Comm& comm, Eigen::MatrixXd& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastMatrixXd_(const MPI_Comm& comm, Eigen::MatrixXd& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_DOUBLE, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendMatrixXd_(const MPI_Comm& comm, const Eigen::MatrixXd& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_DOUBLE, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvMatrixXd_(const MPI_Comm& comm, Eigen::MatrixXd& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_DOUBLE, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceMatrixXcd_(const MPI_Comm& comm, const Eigen::MatrixXcd& source,
    Eigen::MatrixXcd& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceMatrixXcd_(const MPI_Comm& comm, Eigen::MatrixXcd& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastMatrixXcd_(const MPI_Comm& comm, Eigen::MatrixXcd& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_DOUBLE_COMPLEX, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendMatrixXcd_(const MPI_Comm& comm, const Eigen::MatrixXcd& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_DOUBLE_COMPLEX, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvMatrixXcd_(const MPI_Comm& comm, Eigen::MatrixXcd& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_DOUBLE_COMPLEX, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceVectorXi_(const MPI_Comm& comm, const Eigen::VectorXi& source,
    Eigen::VectorXi& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceVectorXi_(const MPI_Comm& comm, Eigen::VectorXi& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_INT,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastVectorXi_(const MPI_Comm& comm, Eigen::VectorXi& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_INT, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendVectorXi_(const MPI_Comm& comm, const Eigen::VectorXi& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_INT, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvVectorXi_(const MPI_Comm& comm, Eigen::VectorXi& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_INT, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceVectorXd_(const MPI_Comm& comm, const Eigen::VectorXd& source,
    Eigen::VectorXd& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceVectorXd_(const MPI_Comm& comm, Eigen::VectorXd& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_DOUBLE,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastVectorXd_(const MPI_Comm& comm, Eigen::VectorXd& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_DOUBLE, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendVectorXd_(const MPI_Comm& comm, const Eigen::VectorXd& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_DOUBLE, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvVectorXd_(const MPI_Comm& comm, Eigen::VectorXd& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_DOUBLE, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */
/* begin func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void AllreduceVectorXcd_(const MPI_Comm& comm, const Eigen::VectorXcd& source,
    Eigen::VectorXcd& dest)
{
    dest.resize(source.rows(), source.cols());
#ifdef WITH_MPI
    int status = MPI_Allreduce(source.data(), dest.data(), source.size(), MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#else
    dest = source;
#endif
}

/// @brief Reduce source matrix in-place
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @return None
void InplaceAllreduceVectorXcd_(const MPI_Comm& comm, Eigen::VectorXcd& source)
{
#ifdef WITH_MPI
    int status = MPI_Allreduce(MPI_IN_PLACE, source.data(), source.size(), MPI_DOUBLE_COMPLEX,
        MPI_SUM, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Allreduce call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in] comm MPI communicator
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BcastVectorXcd_(const MPI_Comm& comm, Eigen::VectorXcd& source, int root)
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source.data(), source.size(), MPI_DOUBLE_COMPLEX, root, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Send source matrix to destination process
/// @param[in] comm MPI communicator
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void SendVectorXcd_(const MPI_Comm& comm, const Eigen::VectorXcd& source, int destID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Send(source.data(), source.size(), MPI_DOUBLE_COMPLEX, destID, tag, comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Send call failed!\n";
        std::exit(-1);
    }
#endif
}

/// @brief Receive matrix from source process and save to destination
/// @param[in] comm MPI communicator
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void RecvVectorXcd_(const MPI_Comm& comm, Eigen::VectorXcd& dest, int sourceID, int tag)
{
#ifdef WITH_MPI
    int status = MPI_Recv(dest.data(), dest.size(), MPI_DOUBLE_COMPLEX, sourceID, tag, comm, MPI_STATUS_IGNORE);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Recv call failed!\n";
        std::exit(-1);
    }
#endif
}
/* end func_def */

/* begin class_func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void BaseMPIEnv::AllreduceScalar(const int& source,
    int& dest) const
{
    AllreduceScalar_(m_Comm, source, dest);
}

/// @brief Reduce source scalar in-place
/// @param[in,out] source source scalar
/// @return None
void BaseMPIEnv::InplaceAllreduceScalar(int& source) const
{
    InplaceAllreduceScalar_(m_Comm, source);
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastScalar(int& source, int root) const
{
    BcastScalar_(m_Comm, source, root);
}

/// @brief Send source scalar to destination process
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendScalar(const int& source, int destID, int tag) const
{
    SendScalar_(m_Comm, source, destID, tag);
}

/// @brief Receive scalar from source process and save to destination
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvScalar(int& dest, int sourceID, int tag) const
{
    RecvScalar_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void BaseMPIEnv::AllreduceScalar(const double& source,
    double& dest) const
{
    AllreduceScalar_(m_Comm, source, dest);
}

/// @brief Reduce source scalar in-place
/// @param[in,out] source source scalar
/// @return None
void BaseMPIEnv::InplaceAllreduceScalar(double& source) const
{
    InplaceAllreduceScalar_(m_Comm, source);
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastScalar(double& source, int root) const
{
    BcastScalar_(m_Comm, source, root);
}

/// @brief Send source scalar to destination process
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendScalar(const double& source, int destID, int tag) const
{
    SendScalar_(m_Comm, source, destID, tag);
}

/// @brief Receive scalar from source process and save to destination
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvScalar(double& dest, int sourceID, int tag) const
{
    RecvScalar_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source scalar to dest scalar
/// @param[in] source source scalar
/// @param[out] dest dest scalar
/// @return None
void BaseMPIEnv::AllreduceScalar(const std::complex<double>& source,
    std::complex<double>& dest) const
{
    AllreduceScalar_(m_Comm, source, dest);
}

/// @brief Reduce source scalar in-place
/// @param[in,out] source source scalar
/// @return None
void BaseMPIEnv::InplaceAllreduceScalar(std::complex<double>& source) const
{
    InplaceAllreduceScalar_(m_Comm, source);
}

/// @brief Broadcast source scalar from root process to other processes
/// @param[in,out] source source scalar
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastScalar(std::complex<double>& source, int root) const
{
    BcastScalar_(m_Comm, source, root);
}

/// @brief Send source scalar to destination process
/// @param[in] source source scalar
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendScalar(const std::complex<double>& source, int destID, int tag) const
{
    SendScalar_(m_Comm, source, destID, tag);
}

/// @brief Receive scalar from source process and save to destination
/// @param[out] dest destination scalar
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvScalar(std::complex<double>& dest, int sourceID, int tag) const
{
    RecvScalar_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceMatrixXi(const Eigen::MatrixXi& source,
    Eigen::MatrixXi& dest) const
{
    AllreduceMatrixXi_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceMatrixXi(Eigen::MatrixXi& source) const
{
    InplaceAllreduceMatrixXi_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastMatrixXi(Eigen::MatrixXi& source, int root) const
{
    BcastMatrixXi_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendMatrixXi(const Eigen::MatrixXi& source, int destID, int tag) const
{
    SendMatrixXi_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvMatrixXi(Eigen::MatrixXi& dest, int sourceID, int tag) const
{
    RecvMatrixXi_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceMatrixXd(const Eigen::MatrixXd& source,
    Eigen::MatrixXd& dest) const
{
    AllreduceMatrixXd_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceMatrixXd(Eigen::MatrixXd& source) const
{
    InplaceAllreduceMatrixXd_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastMatrixXd(Eigen::MatrixXd& source, int root) const
{
    BcastMatrixXd_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendMatrixXd(const Eigen::MatrixXd& source, int destID, int tag) const
{
    SendMatrixXd_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvMatrixXd(Eigen::MatrixXd& dest, int sourceID, int tag) const
{
    RecvMatrixXd_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceMatrixXcd(const Eigen::MatrixXcd& source,
    Eigen::MatrixXcd& dest) const
{
    AllreduceMatrixXcd_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceMatrixXcd(Eigen::MatrixXcd& source) const
{
    InplaceAllreduceMatrixXcd_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastMatrixXcd(Eigen::MatrixXcd& source, int root) const
{
    BcastMatrixXcd_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendMatrixXcd(const Eigen::MatrixXcd& source, int destID, int tag) const
{
    SendMatrixXcd_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvMatrixXcd(Eigen::MatrixXcd& dest, int sourceID, int tag) const
{
    RecvMatrixXcd_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceVectorXi(const Eigen::VectorXi& source,
    Eigen::VectorXi& dest) const
{
    AllreduceVectorXi_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceVectorXi(Eigen::VectorXi& source) const
{
    InplaceAllreduceVectorXi_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastVectorXi(Eigen::VectorXi& source, int root) const
{
    BcastVectorXi_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendVectorXi(const Eigen::VectorXi& source, int destID, int tag) const
{
    SendVectorXi_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvVectorXi(Eigen::VectorXi& dest, int sourceID, int tag) const
{
    RecvVectorXi_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceVectorXd(const Eigen::VectorXd& source,
    Eigen::VectorXd& dest) const
{
    AllreduceVectorXd_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceVectorXd(Eigen::VectorXd& source) const
{
    InplaceAllreduceVectorXd_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastVectorXd(Eigen::VectorXd& source, int root) const
{
    BcastVectorXd_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendVectorXd(const Eigen::VectorXd& source, int destID, int tag) const
{
    SendVectorXd_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvVectorXd(Eigen::VectorXd& dest, int sourceID, int tag) const
{
    RecvVectorXd_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */ /* begin class_func_def */
/// @brief Reduce source matrix to dest matrix
/// @param[in] source source matrix
/// @param[out] dest dest matrix
/// @return None
void BaseMPIEnv::AllreduceVectorXcd(const Eigen::VectorXcd& source,
    Eigen::VectorXcd& dest) const
{
    AllreduceVectorXcd_(m_Comm, source, dest);
}

/// @brief Reduce source matrix in-place
/// @param[in,out] source source matrix
/// @return None
void BaseMPIEnv::InplaceAllreduceVectorXcd(Eigen::VectorXcd& source) const
{
    InplaceAllreduceVectorXcd_(m_Comm, source);
}

/// @brief Broadcast source matrix from root process to other processes
/// @param[in,out] source source matrix
/// @param[in] root rank of root process in the communicator
/// @return None
void BaseMPIEnv::BcastVectorXcd(Eigen::VectorXcd& source, int root) const
{
    BcastVectorXcd_(m_Comm, source, root);
}

/// @brief Send source matrix to destination process
/// @param[in] source source matrix
/// @param[in] destID rank of destination process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::SendVectorXcd(const Eigen::VectorXcd& source, int destID, int tag) const
{
    SendVectorXcd_(m_Comm, source, destID, tag);
}

/// @brief Receive matrix from source process and save to destination
/// @param[out] dest destination matrix
/// @param[in] sourceID rank of source process in the communicator
/// @param[in] tag tag to distinguish the send/recv process
/// @return None
void BaseMPIEnv::RecvVectorXcd(Eigen::VectorXcd& dest, int sourceID, int tag) const
{
    RecvVectorXcd_(m_Comm, dest, sourceID, tag);
}
/* end class_func_def */

/**
 * @brief Temporary patch for Python interface.
 *
 */
void BaseMPIEnv::BcastRawDouble(double* source, size_t num_data, int root) const
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source, num_data, MPI_DOUBLE, root, m_Comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}

void BaseMPIEnv::BcastRawComplex(std::complex<double>* source, size_t num_data, int root) const
{
#ifdef WITH_MPI
    int status = MPI_Bcast(source, num_data, MPI_DOUBLE_COMPLEX, root, m_Comm);
    if (status != MPI_SUCCESS) {
        std::cerr << "MPI_Bcast call failed!\n";
        std::exit(-1);
    }
#endif
}
} // namespace tbplas::base
