#include "../lib/helper.h"

int stringToInt(char *string) {
  char *leftover = "";
  errno = 0;
  int x = strtold(string, &leftover);
  if (*leftover != '\0' || leftover == string) {
    errno = EINVAL;
    return -1;
  }
  if (errno == ERANGE) {
    printf("Number is too large or too small!\n");
    errno = EINVAL;
    return -1;
  }
  errno = 0;
  return x;
}

void help(char *progname) {
  fprintf(stdout,
          "Usage: %s [options...] <file>\n\
  -V, --version <version>      use a specific implementation\n\
  -B, --benchmark [repetitons] benchmark the execution and optionally repeat it\n\
  -T, --test                   run self-test. If set, no file is needed\n\
  -h, --help                   print this help page\n\
  \n\
  <version> is an integer between 0 and 4.\n\
  0: naive implementation\n\
  1: optimized implementation\n\
  2: memory efficient implementation\n\
  3: threaded implementation\n\
  4: reference implementation\n\
  \n",
          progname);
}

enum argumentParseResult parseArguments(int argc, char **argv,
                                        struct configuration *c) {
  if (c == NULL) {
    return RESULT_EXIT_FAILURE;
  }

  c->implementationToUse = 0;
  c->doBenchmark = false;
  c->benchmarkingCycles = 1;
  c->runTests = false;

  int opt;

  while (1) {
    static struct option longOptions[] = {
        {"version", required_argument, NULL, 'V'},
        {"benchmark", optional_argument, NULL, 'B'},
        {"test", optional_argument, NULL, 'T'},
        {"help", no_argument, NULL, 'h'},
        {NULL}};
    int longOptionIndex = 0;

    opt = getopt_long(argc, argv, "V:B::Th", longOptions, &longOptionIndex);

    if (-1 == opt) break;

    switch (opt) {
      case 'B':
        c->doBenchmark = true;
        if (optarg != NULL) {
          c->benchmarkingCycles = stringToInt(optarg);
          if (errno == 0) break;
          fprintf(stderr,
                  "%s: invalid argument, has to be int -- 'B' got '%s'\n",
                  argv[0], optarg);
          return RESULT_EXIT_FAILURE;
        }
        break;
      case 'V':
        c->implementationToUse = stringToInt(optarg);
        if (errno == 0) break;
        fprintf(stderr, "%s: invalid argument, has to be int -- 'V' got '%s'\n",
                argv[0], optarg);
        return RESULT_EXIT_FAILURE;

      case 'T':
        c->runTests = true;
        break;

      case 'h':
        help(argv[0]);
        return RESULT_EXIT_SUCCESS;

      default:
        return RESULT_EXIT_FAILURE;
    }
  }

  if (argc > optind) {
    c->filename = argv[optind];
  } else if (!c->runTests) {
    fprintf(stderr, "%s: missing poisional argument -- 'file'\n", argv[0]);
    return RESULT_EXIT_FAILURE;
  }

  return RESULT_OK;
}

double current_time(void) {
  struct timespec t;
  clock_gettime(CLOCK_MONOTONIC, &t);
  return t.tv_sec + t.tv_nsec * 1e-9;
}

double run_benchmark(unsigned cycles, md2_hash_func func, size_t len,
                     uint8_t *buf, uint8_t *out) {
  double start = current_time();
  for (; cycles > 0; cycles--) {
    func(len, buf, out);
  }
  double end = current_time();

  return end - start;
}