// -*- C -*-
// TimeIntegers.c: time integer draws and permutations (ns/value).
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "getopt.h"
#include "TimeUtil.h"
#include "Util.h"
#include "randompack.h"
#include "randompack_config.h"
static void print_help(void) {
  printf("TimeIntegers - time integer draws and permutations (ns/value)\n");
  printf("Usage: TimeIntegers [options]\n\n");
  printf("Options:\n");
  printf("  -h            Show this help message\n");
  printf("  -e engine     RNG engine (default x256++simd)\n");
  printf("  -t seconds    Benchmark time per case (default 0.2)\n");
  printf("  -c chunk      Chunk size (values per call, default 4096)\n");
  printf("  -s seed       Fixed RNG seed (default random seed per case)\n");
}

static bool get_options(int argc, char **argv, char **engine, double *bench_time,
                        int *chunk, int *seed, bool *have_seed, bool *help) {
  opterr = 0;
  optind = 1;
  int opt;
  *engine = "x256++simd";
  *bench_time = 0.2;
  *chunk = 4096;
  *seed = 0;
  *have_seed = false;
  *help = false;
  while ((opt = getopt(argc, argv, "he:t:c:s:")) != -1) {
    switch (opt) {
      case 'h':
        *help = true;
        return true;
      case 'e':
        *engine = optarg;
        break;
      case 't':
        *bench_time = atof(optarg);
        if (*bench_time <= 0)
          return false;
        break;
      case 'c':
        *chunk = atoi(optarg);
        if (*chunk <= 0)
          return false;
        break;
      case 's':
        *seed = atoi(optarg);
        *have_seed = true;
        break;
      default:
        return false;
    }
  }
  if (optind < argc)
    return false;
  return true;
}

static void set_seed(randompack_rng *rng, int seed, bool have_seed) {
  bool ok;
  if (have_seed)
    ok = randompack_seed(seed, 0, 0, rng);
  else
    ok = randompack_randomize(rng);
  ASSERT(ok);
}

static inline void consume_u64(uint64_t x) {
  static volatile uint64_t sink;
  sink ^= x;
}

static double time_int_range(int chunk, double bench_time, int m, int n, randompack_rng
                             *rng) {
  int *buf;
  TEST_ALLOC(buf, chunk);
  int reps = 1000000/chunk;
  if (reps < 1)
    reps = 1;
  int calls = 0;
  uint64_t t0 = clock_nsec();
  uint64_t deadline = t0 + (uint64_t)(bench_time*1e9);
  uint64_t t = t0;
  while (t < deadline) {
    for (int i = 0; i < reps; i++) {
      ASSERT(randompack_int(buf, chunk, m, n, rng));
      consume_u64(buf[chunk - 1]);
    }
    calls += reps;
    t = clock_nsec();
  }
  FREE(buf);
  if (calls == 0)
    return 0;
  return (t - t0)/((double)calls*chunk);
}

static double time_perm(int n, double bench_time, randompack_rng *rng) {
  int *buf;
  TEST_ALLOC(buf, n);
  int reps = 100000/n;
  if (reps < 1)
    reps = 1;
  int calls = 0;
  uint64_t t0 = clock_nsec();
  uint64_t deadline = t0 + (uint64_t)(bench_time*1e9);
  uint64_t t = t0;
  while (t < deadline) {
    for (int i = 0; i < reps; i++) {
      ASSERT(randompack_perm(buf, n, rng));
      consume_u64(buf[n - 1]);
    }
    calls += reps;
    t = clock_nsec();
  }
  FREE(buf);
  if (calls == 0)
    return 0;
  return (double)(t - t0)/calls;
}

static double time_sample(int n, int k, double bench_time, randompack_rng *rng) {
  int *buf;
  TEST_ALLOC(buf, k);
  int reps = 100000/n;
  if (reps < 1)
    reps = 1;
  int calls = 0;
  uint64_t t0 = clock_nsec();
  uint64_t deadline = t0 + (uint64_t)(bench_time*1e9);
  uint64_t t = t0;
  while (t < deadline) {
    for (int i = 0; i < reps; i++) {
      ASSERT(randompack_sample(buf, n, k, rng));
      consume_u64(buf[k - 1]);
    }
    calls += reps;
    t = clock_nsec();
  }
  FREE(buf);
  if (calls == 0)
    return 0;
  return (double)(t - t0)/calls;
}

static double time_long_long_range(int chunk, double bench_time, long long m,
                                   long long n, randompack_rng *rng) {
  long long *buf;
  TEST_ALLOC(buf, chunk);
  int reps = 1000000/chunk;
  if (reps < 1)
    reps = 1;
  int calls = 0;
  uint64_t t0 = clock_nsec();
  uint64_t deadline = t0 + (uint64_t)(bench_time*1e9);
  uint64_t t = t0;
  while (t < deadline) {
    for (int i = 0; i < reps; i++) {
      ASSERT(randompack_long_long(buf, chunk, m, n, rng));
      consume_u64((uint64_t)buf[chunk - 1]);
    }
    calls += reps;
    t = clock_nsec();
  }
  FREE(buf);
  if (calls == 0)
    return 0;
  return (t - t0)/((double)calls*chunk);
}

int main(int argc, char **argv) {
  char *engine;
  double bench_time;
  int chunk, seed;
  bool have_seed;
  bool help;
  if (!get_options(argc, argv, &engine, &bench_time, &chunk, &seed, &have_seed,
      &help) || help) {
    print_help();
    return help ? 0 : 1;
  }
#if defined(__linux__)
#endif
  randompack_rng *rng = randompack_create(engine);
  if (!rng) {
    fprintf(stderr, "randompack_create failed: %s\n", engine);
    return 1;
  }
  warmup_cpu(0.1);
  struct { int n; char *label; } int_ranges[] = {
    { 10, "1-10" },
    { 100000, "1-1e5" },
    { 2000000000, "1-2e9" },
  };
  struct { long long n; char *label; } ll_ranges[] = {
    { 2000000000LL, "1-2e9" },
    { 6000000000000000000LL, "1-6e18" },
  };
  struct { int n; char *label; } perm_specs[] = {
    { 100, "100" },
    { 100000, "100000" },
  };
  struct { int n; int k; char *label; } sample_specs[] = {
    { 1000, 20, "1000/20" },
    { 1000, 500, "1000/500" },
    { 1000, 980, "1000/980" },
  };
  printf("engine:           %s\n", engine);
  printf("time per value:   ns/value\n");
  printf("bench_time:       %.3f s per case\n", bench_time);
  printf("chunk:            %d\n", chunk);
  printf("\n%-18s %10s\n", "Benchmark", "Randompack");
  for (int i = 0; i < LEN(int_ranges); i++) {
    char label[32];
    set_seed(rng, seed, have_seed);
    double ns = time_int_range(chunk, bench_time, 1, int_ranges[i].n, rng);
    STRSETF(label, "int %s", int_ranges[i].label);
    printf("%-18s %10.2f\n", label, ns);
  }
  for (int i = 0; i < LEN(ll_ranges); i++) {
    char label[32];
    set_seed(rng, seed, have_seed);
    double ns = time_long_long_range(chunk, bench_time, 1, ll_ranges[i].n, rng);
    STRSETF(label, "long long %s", ll_ranges[i].label);
    printf("%-18s %10.2f\n", label, ns);
  }
  for (int i = 0; i < LEN(perm_specs); i++) {
    char label[32];
    int n = perm_specs[i].n;
    set_seed(rng, seed, have_seed);
    double ns = time_perm(n, bench_time, rng);
    STRSETF(label, "perm %s", perm_specs[i].label);
    printf("%-18s %10.2f\n", label, ns/n);
  }
  for (int i = 0; i < LEN(sample_specs); i++) {
    char label[32];
    int n = sample_specs[i].n;
    int k = sample_specs[i].k;
    set_seed(rng, seed, have_seed);
    double ns = time_sample(n, k, bench_time, rng);
    STRSETF(label, "sample %d/%d", k, n);
    printf("%-18s %10.2f\n", label, ns/k);
  }
  randompack_free(rng);
  return 0;
}
