/*!
 * \file
 * \brief   Main application file for PDS HW2 (MPI)
 *
 * \author
 *    Christos Choutouridis AEM:8997
 *    <cchoutou@ece.auth.gr>
 */

#include <exception>
#include <iostream>
#include <algorithm>
#include <random>

#include "utils.hpp"
#include "config.h"
#include "distsort.hpp"


// Global session data
session_t       session;
MPI_t<>         mpi;
distBuffer_t    Data;
Log             logger;
Timing          timer;

/*!
 * A small command line argument parser
 * \return  The status of the operation
 */
bool get_options(int argc, char* argv[]){
    bool status =true;

    // iterate over the passed arguments
    for (int i=1 ; i<argc ; ++i) {
        std::string arg(argv[i]);     // get current argument

        if (arg == "-q" || arg == "--array-size") {
            if (i+1 < argc) {
                session.arraySize = 1 << atoi(argv[++i]);
            }
            else {
                status = false;
            }
        }
        else if (arg == "--ndebug") {
            session.ndebug = true;
        }
        else if (arg == "-t" || arg == "--timing") {
            session.timing = true;
        }
        else if (arg == "-v" || arg == "--verbose") {
            session.verbose = true;
        }
        else if (arg == "-h" || arg == "--help") {
            std::cout << "distbitonic/distbubbletonic - A distributed bitonic sort\n\n";
            std::cout << "distbitonic -q <> [--ndebug] [-v]\n";
            std::cout << "distbitonic -h\n";
            std::cout << "distbubbletonic -q <> [--ndebug] [-v]\n";
            std::cout << "distbubbletonic -h\n";
            std::cout << '\n';
            std::cout << "Options:\n\n";
            std::cout << "   -q | --array-size <size>\n";
            std::cout << "      Selects the array size according to size = 2^q\n\n";
            std::cout << "   --ndebug\n";
            std::cout << "      Skip debug breakpoint when on debug build.\n\n";
            std::cout << "   -t | --timing\n";
            std::cout << "      Request timing measurements output to stdout.\n\n";
            std::cout << "   -v | --verbose\n";
            std::cout << "      Request a more verbose output to stdout.\n\n";
            std::cout << "   -h | --help\n";
            std::cout << "      Prints this and exit.\n\n";
            std::cout << "Examples:\n\n";
            std::cout << "   mpirun -np 4 distbitonic -q 24\n";
            std::cout << "      Runs distbitonic in 4 MPI processes with 2^24 array points each\n\n";
            std::cout << "   mpirun -np 16 distbubbletonic -q 20\n";
            std::cout << "      Runs distbubbletonic in 16 MPI processes with 2^20 array points each\n\n";

            exit(0);
        }
        else {   // parse error
            std::cout << "Invocation error. Try -h for details.\n";
            status = false;
        }
    }

    return status;
}


#if !defined TESTING
int main(int argc, char* argv[]) try {
    // Initialize MPI environment
    mpi.init(&argc, &argv);

    // try to read command line (after MPI parsing)
    if (!get_options(argc, argv))
        exit(1);

    logger << "MPI environment initialized." <<
              " Rank: " << mpi.rank() <<
              " Size: " << mpi.size() <<
              logger.endl;

#if defined DEBUG
#if defined TESTING
    /*
     * In case of a debug build we will wait here until sleep_wait
     * will reset via debugger. In order to do that the user must attach
     * debugger to all processes. For example:
     *  $> mpirun -np 2 ./<program path>
     *  $> ps aux | grep <program>
     *  $> gdb <program> <PID1>
     *  $> gdb <program> <PID2>
     */
     volatile bool sleep_wait = false;
#else
    volatile bool sleep_wait = true;
#endif
    while (sleep_wait && !session.ndebug)
        sleep(1);
#endif

    logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl;
    std::random_device rd;      // Mersenne seeded from hw if possible. range: [type_min, type_max]
    std::mt19937 gen(rd());
    std::uniform_int_distribution<distValue_t > dis(
            std::numeric_limits<distValue_t>::min(),
            std::numeric_limits<distValue_t>::max()
        );
    // Fill vector
    Data.resize(session.arraySize);
    std::generate(Data.begin(), Data.end(), [&]() { return dis(gen); });

    if (mpi.rank() == 0)
        logger << "Starting distributed sorting ... ";
    timer.start();
    #if CODE_VERSION == BUBBLETONIC
      distBubbletonic(Data, mpi.size());
    #else
      distBitonic (Data, mpi.size());
    #endif
    timer.stop();
    if (mpi.rank() == 0)
        logger << " Done." << logger.endl;
    std::string timeMsg = "rank " + std::to_string(mpi.rank());
    timer.print_dt(timeMsg.c_str());

    std::cout << "[Data]: Rank " << mpi.rank() << ": [" << +Data.front() << " .. " << +Data.back() << "]" << std::endl;
    mpi.finalize();
    return 0;
}
catch (std::exception& e) {
    //we probably pollute the user's screen. Comment `cerr << ...` if you don't like it.
    std::cerr << "Error: " << e.what() << '\n';
    exit(1);
}

#else

#include <gtest/gtest.h>
#include <exception>

GTEST_API_ int main(int argc, char **argv) try {
   testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
}
catch (std::exception& e) {
    std::cout << "Exception: " << e.what() << '\n';
}

#endif