/** @file main.cpp
 *
 *  @author Cory Alexander Balaton (coryab)
 *  @author Janita Ovidie Sandtrøen Willumsen (janitaws)
 *
 *  @version 0.1
 *
 *  @brief The main program
 *
 *  @bug No known bugs
 * */
#include "data_type.hpp"
#include "monte_carlo.hpp"
#include "utils.hpp"

#include <getopt.h>

/** @brief Create the data for the burn-in time for temperatures 1.0 and 2.4
 *  for both unordered and ordered initial states.
 * */
void create_burn_in_time_data()
{
    // Test burn-in time
    montecarlo::progression(1.0, 20, 20000,
                            "./data/hp/burn_in_time/unordered_1_0.txt");
    montecarlo::progression(1.0, 20, 20000, 1,
                            "./data/hp/burn_in_time/ordered_1_0.txt");
    montecarlo::progression(2.4, 20, 20000,
                            "./data/hp/burn_in_time/unordered_2_4.txt");
    montecarlo::progression(2.4, 20, 20000, 1,
                            "./data/hp/burn_in_time/ordered_2_4.txt");
}

/** @brief Create the data used to estimate the probability distribution
 *  for tempratures 1.0 and 2.4.
 * */
void create_pd_estimate_data()
{
    // Estimate pd
    montecarlo::pd_estimate(1.0, 20, 1000000,
                            "./data/hp/pd_estimate/estimate_1_0.txt");
    montecarlo::pd_estimate(2.4, 20, 1000000,
                            "./data/hp/pd_estimate/estimate_2_4.txt");
}

/** @brief Create data using the same parameters except one uses burn-in time,
 *  while the other doesn't.
 * */
void test_burn_in_time()
{
    montecarlo::phase_transition(
        100, 2.1, 2.4, 40, 1e5, montecarlo::mcmc_serial,
        "./data/hp/test_burn_in_time/burn_in.txt", 5000);
    montecarlo::phase_transition(
        100, 2.1, 2.4, 40, 1e5, montecarlo::mcmc_serial,
        "./data/hp/test_burn_in_time/no_burn_in.txt", 0);
}

/** @brief Test how much Openmp speeds up.*/
void test_parallel_speedup()
{
    // Test the openmp speedup
    data_t data;
    double t0, t1, t2;
    int tries = 5;
    t0 = omp_get_wtime();
    for (size_t i = 0; i < tries; i++)
        montecarlo::mcmc_serial(20, 1.0, 10000);
    t1 = omp_get_wtime();
    for (size_t i = 0; i < tries; i++)
        montecarlo::mcmc_parallel(20, 1.0, 10000);
    t2 = omp_get_wtime();

    std::cout << "Time serial     : " << (t1 - t0) / tries << " seconds"
              << '\n';
    std::cout << "Time parallel   : " << (t2 - t1) / tries << " seconds"
              << '\n';
    std::cout << "Speedup parallel: " << (t1 - t0) / (t2 - t1) << '\n';
}

/** @brief Create data for studying phase transition.
 * */
void create_phase_transition_data()
{
    double t0, t1;

    t0 = omp_get_wtime();
    // Phase transition
    montecarlo::phase_transition(20, 2.1, 2.4, 40, 1e4,
                                 montecarlo::mcmc_parallel,
                                 "./data/hp/phase_transition/size_20.txt");
    montecarlo::phase_transition(40, 2.1, 2.4, 40, 1e4,
                                 montecarlo::mcmc_parallel,
                                 "./data/hp/phase_transition/size_40.txt");
    montecarlo::phase_transition(60, 2.1, 2.4, 40, 1e4,
                                 montecarlo::mcmc_parallel,
                                 "./data/hp/phase_transition/size_60.txt");
    montecarlo::phase_transition(80, 2.1, 2.4, 40, 1e4,
                                 montecarlo::mcmc_parallel,
                                 "./data/hp/phase_transition/size_80.txt");
    montecarlo::phase_transition(100, 2.1, 2.4, 40, 1e4,
                                 montecarlo::mcmc_parallel,
                                 "./data/hp/phase_transition/size_100.txt");
    t1 = omp_get_wtime();

    std::cout << "Time: " << t1 - t0 << std::endl;
}

/** @brief A function that displays how to use the program and quits.*/
void usage(std::string filename)
{
    std::cout << "Usage: " << filename << " OPTION ...\n"
              << "At least one option should be used.\n\n"
              << "\t[ -h | --help ]\n"
              << "\t[ --all ]\n"
              << "\t[ --create-burn-in-data ]\n"
              << "\t[ --create-pd-estimate-data ]\n"
              << "\t[ --create-phase-transition-data ]\n"
              << "\t[ --test-parallel-speedup ]\n"
              << "\t[ --test-burn-in-time ]\n";
    exit(-1);
}

/** @brief The main function.*/
int main(int argc, char **argv)
{
    static struct option long_options[] = {
        {"all", no_argument, 0, 0},
        {"create-burn-in-data", no_argument, 0, 0},
        {"create-pd-estimate-data", no_argument, 0, 0},
        {"test-parallel-speedup", no_argument, 0, 0},
        {"create-phase-transition-data", no_argument, 0, 0},
        {"test-burn-in-time", no_argument, 0, 0},
        {"help", no_argument, 0, 0}};

    int option_index = -1;
    int c;

    while (true) {
        c = getopt_long(argc, argv, "h", long_options, &option_index);

        if (c == -1)
            break;
        else if (c == 'h')
            usage(argv[0]);

        switch (option_index) {
        case 0:
            create_burn_in_time_data();
            create_pd_estimate_data();
            test_parallel_speedup();
            create_phase_transition_data();
            test_burn_in_time();
            break;
        case 1:
            create_burn_in_time_data();
            break;
        case 2:
            create_pd_estimate_data();
            break;
        case 3:
            test_parallel_speedup();
            break;
        case 4:
            create_phase_transition_data();
            break;
        case 5:
            test_burn_in_time();
            break;
        case 6: // Not a mistake. This just goes to the default.
        default:
            usage(argv[0]);
        }
    }

    return 0;
}
