//  Copyright (c) 2011-2012 Bryce Adelstein-Lelbach
//  Copyright (c) 2007-2012 Hartmut Kaiser
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/config/defines.hpp>   // avoid issues with Intel14/libstdc++4.4 nullptr
#include <hpx/modules/program_options.hpp>

#include <cstddef>
#include <cstdint>
#include <functional>
#include <iostream>
#include <numeric>
#include <random>
#include <stdexcept>
#include <vector>
#include <utility>

using hpx::program_options::variables_map;
using hpx::program_options::options_description;
using hpx::program_options::value;
using hpx::program_options::store;
using hpx::program_options::command_line_parser;
using hpx::program_options::notify;

///////////////////////////////////////////////////////////////////////////////
// Command-line variables.
std::uint64_t tasks = 500000;
std::uint64_t min_delay = 0;
std::uint64_t max_delay = 0;
std::uint64_t total_delay = 0;
std::uint64_t seed = 0;

///////////////////////////////////////////////////////////////////////////////
std::uint64_t shuffler(
    std::mt19937_64& prng
  , std::uint64_t high
    )
{
    if (high == 0)
        throw std::logic_error("high value was 0");

    // Our range is [0, x).
    std::uniform_int_distribution<std::uint64_t>
        dist(0, high - 1);

    return dist(prng);
}

///////////////////////////////////////////////////////////////////////////////
int app_main(
    variables_map&
    )
{
    ///////////////////////////////////////////////////////////////////////
    // Initialize the PRNG seed.
    if (!seed)
        seed = std::uint64_t(std::time(nullptr));

    ///////////////////////////////////////////////////////////////////////
    // Validate command-line arguments.
    if (0 == tasks)
        throw std::invalid_argument("count of 0 tasks specified\n");

    if (min_delay > max_delay)
        throw std::invalid_argument("minimum delay cannot be larger than "
                                    "maximum delay\n");

    if (min_delay > total_delay)
        throw std::invalid_argument("minimum delay cannot be larger than"
                                    "total delay\n");

    if (max_delay > total_delay)
        throw std::invalid_argument("maximum delay cannot be larger than "
                                    "total delay\n");

    if ((min_delay * tasks) > total_delay)
        throw std::invalid_argument("minimum delay is too small for the "
                                    "specified total delay and number of "
                                    "tasks\n");

    if ((max_delay * tasks) < total_delay)
        throw std::invalid_argument("maximum delay is too small for the "
                                    "specified total delay and number of "
                                    "tasks\n");

    ///////////////////////////////////////////////////////////////////////
    // Randomly generate a description of the heterogeneous workload.
    std::vector<std::uint64_t> payloads;
    payloads.reserve(tasks);

    // For random numbers, we use a 64-bit specialization of stdlib's
    // mersenne twister engine (good uniform distribution up to 311
    // dimensions, cycle length 2 ^ 19937 - 1)
    std::mt19937_64 prng(seed);

    std::uint64_t current_sum = 0;

    for (std::uint64_t i = 0; i < tasks; ++i)
    {
        // Credit to Spencer Ruport for putting this algorithm on
        // stackoverflow.
        std::uint64_t const low_calc
            = (total_delay - current_sum) - (max_delay * (tasks - 1 - i));

        bool const negative
            = (total_delay - current_sum) < (max_delay * (tasks - 1 - i));

        std::uint64_t const low
            = (negative || (low_calc < min_delay)) ? min_delay : low_calc;

        std::uint64_t const high_calc
            = (total_delay - current_sum) - (min_delay * (tasks - 1 - i));

        std::uint64_t const high
            = (high_calc > max_delay) ? max_delay : high_calc;

        // Our range is [low, high].
        std::uniform_int_distribution<std::uint64_t>
            dist(low, high);

        std::uint64_t const payload = dist(prng);

        if (payload < min_delay)
            throw std::logic_error("task delay is below minimum");

        if (payload > max_delay)
            throw std::logic_error("task delay is above maximum");

        current_sum += payload;
        payloads.push_back(payload);
    }

    // Randomly shuffle the entire sequence to deal with drift.
    std::random_device random_device;
    std::mt19937 generator(random_device());
    std::shuffle(payloads.begin(), payloads.end(), std::move(generator));

    ///////////////////////////////////////////////////////////////////////
    // Validate the payloads.
    if (payloads.size() != tasks)
        throw std::logic_error("incorrect number of tasks generated");

    std::uint64_t const payloads_sum =
        std::accumulate(payloads.begin(), payloads.end(), 0ULL);
    if (payloads_sum != total_delay)
        throw std::logic_error("incorrect total delay generated");

    for (std::size_t i = 0; i < payloads.size(); ++i)
        std::cout << payloads[i] << "\n";

    return 0;
}

///////////////////////////////////////////////////////////////////////////////
int main(
    int argc
  , char* argv[]
    )
{
    ///////////////////////////////////////////////////////////////////////////
    // Parse command line.
    variables_map vm;

    options_description cmdline("Usage: " HPX_APPLICATION_STRING " [options]");

    cmdline.add_options()
        ( "help,h"
        , "print out program usage (this message)")

        ( "tasks"
        , value<std::uint64_t>(&tasks)->default_value(500000)
        , "number of tasks to invoke")

        ( "min-delay"
        , value<std::uint64_t>(&min_delay)->default_value(0)
        , "minimum number of iterations in the delay loop")

        ( "max-delay"
        , value<std::uint64_t>(&max_delay)->default_value(0)
        , "maximum number of iterations in the delay loop")

        ( "total-delay"
        , value<std::uint64_t>(&total_delay)->default_value(0)
        , "total number of delay iterations to be executed")

        ( "seed"
        , value<std::uint64_t>(&seed)->default_value(0)
        , "seed for the pseudo random number generator (if 0, a seed is "
          "chosen based on the current system time)")
        ;

    store(command_line_parser(argc, argv).options(cmdline).run(), vm);

    notify(vm);

    // Print help screen.
    if (vm.count("help"))
    {
        std::cout << cmdline;
        return 0;
    }

    return app_main(vm);
}

