/**
 * @brief Utilities for TBPM module.
 *
 * @author: Zewen Wu, Miao Zhang, Yunhai Li
 */

#ifndef TBPLAS_TBPM_UTILS_H
#define TBPLAS_TBPM_UTILS_H

#include <iostream>
#include <random>

#include <eigen3/Eigen/Dense>

namespace tbplas::tbpm {

/**
 * @brief Class for generating random numbers.
 *
 */
class RandomGenerator {
public:
    /**
     * @brief Construct a new Random Generator object
     *
     * @param[in] seed seed for generating random numbers
     * @param[in] r_min lower bound of random numbers
     * @param[in] r_max upper bound of random numbers
     */
    explicit RandomGenerator(int seed = 1337, double r_min = 0.0, double r_max = 1.0);

    /**
     * @brief Generate a random number without resetting seed
     *
     * @return random number
     */
    double random_number();

    /**
     * @brief Generate a random number after resetting seed
     *
     * @param[in] seed seed with which to reset the
     * @return random number
     */
    double random_number(int seed);

    /**
     * @brief Populate the Eigen vector with random complex numbers and normalize it after
     * without resetting the seed
     *
     * @param[inout] vec vector in which to put the random numbers
     */
    void random_state(Eigen::VectorXcd& vec);

    /**
     * @brief Populate the Eigen vector with random complex numbers and normalize it after
     * resetting the seed
     *
     * @param[inout] vec vector in which to put the random numbers
     * @param[in] seed_i seed value assigned to this process
     *
     * @note When MPI is enabled and there are more than one process, each process will have
     * a different seed.
     */
    void random_state(Eigen::VectorXcd& vec, int seed_i);

    /**
     * @brief Reset the random engine's seed
     *
     * @param[in] seed seed for the random engine
     */
    void reset_seed(int seed);

private:
    std::mt19937 engine_;
    std::uniform_real_distribution<double> uni_real_dist_;
};

/**
 * @brief Print the head and tail elements of a vector for debugging.
 *
 * @tparam vec_t datatype of vector
 * @param[in] msg message to print before the elements
 * @param[in] x vector to print
 * @param[in] num_element number of elements
 */
template <typename vec_t>
void dump(
    const std::string& msg,
    const Eigen::MatrixBase<vec_t>& x,
    int num_element = 5)
{
    std::streamsize prev_precision = std::cout.precision();
    std::cout.setf(std::ios::scientific);
    std::cout.precision(15);
    int i_min = 0;
    int i_max = num_element;
    std::cout << msg << "\n";
    for (int i = i_min; i < i_max; ++i) {
        std::cout << i << " " << x[i] << "\n";
    }
    std::cout << "... ...\n";
    i_min = x.size() - num_element;
    i_max = x.size();
    for (int i = i_min; i < i_max; ++i) {
        std::cout << i << " " << x[i] << "\n";
    }
    std::cout << "\n";
    std::cout.unsetf(std::ios::scientific);
    std::cout.precision(prev_precision);
}

// Macro for performing tasks on master process in parallel mode
#define PARA_EXEC(cmd) if(meta.is_master){cmd;}

} // namespace tbplas::tbpm
#endif