#include <stdbool.h>
#include <sys/resource.h>

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

static bool runTest(struct configuration* c, const char* message,
                    const char* expectedHash) {
  uint8_t out[16];
  double duration = 0.0;

  if (c->doBenchmark) {
    duration = run_benchmark(c->benchmarkingCycles, md2_hash, strlen(message),
                             (uint8_t*)message, out);
  } else {
    md2_hash(strlen(message), (uint8_t*)message, out);
  }

  char hash[32];
  md2_encode_hash(out, hash);

  bool ok = !strncmp(hash, expectedHash, 32);
  printf("%s: md2(%s) %s == %s", "not ok" + (4 * ok), message, hash,
         expectedHash);

  if (c->doBenchmark) {
    printf(" took: %f", duration);
  }
  printf("\n");
  return ok;
}

unsigned runTests(struct configuration* c) {
  unsigned failed = 0;
  // src: https://datatracker.ietf.org/doc/html/rfc1319#appendix-A.5
  failed += !runTest(c, "", "8350e5a3e24c153df2275c9f80692773");
  failed += !runTest(c, "a", "32ec01ec4a6dac72c0ab96fb34c0b5d1");
  failed += !runTest(c, "abc", "da853b0d3f88d99b30283a69e6ded6bb");
  failed += !runTest(c, "message digest", "ab4f496bfb2a530b219ff33031fe06b0");
  failed += !runTest(c, "jebdjcslfhwfdig", "e1b69085c6f6e36cb8fe8d98ed3f2c35");
  failed += !runTest(c, "0123456789abcde", "d95629645108a20ab4d70e8545e0723b");
  failed += !runTest(c, "0123456789abcdef", "12c8dfa285f14e1af8c5254e7092d0d3");
  failed +=
      !runTest(c, "0123456789abcdefg", "e4d0efded5ef7b6843a5ba47e1171347");
  failed += !runTest(c, "abcdefghijklmnopqrstuvwxyz",
                     "4e8ddff3650292ab5a4108c3aa47940b");
  failed += !runTest(
      c, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
      "da33def2a42df13975352846c30338cd");
  failed += !runTest(
      c,
      "123456789012345678901234567890123456789012345678901234567890123456789012"
      "34567890",
      "d5976f79d83d3a0dc9806c3c66f3efd8");

  if (failed)
    printf("%u tests FAILED\n", failed);
  else
    printf("All tests PASSED\n");

  return failed;
}

bool calculate_hash(struct configuration c, char* hash) {
  size_t len;
  uint8_t* data;

  if (c.implementationToUse != 2) {
    data = read_file(c.filename, &len);
  } else {
    data = (uint8_t*)c.filename;
    len = 0;
  }

  if (data == NULL) {
    printf("Error reading file %s!\n", c.filename);
    return false;
  }

  errno = 0;
  uint8_t out[16];
  if (c.doBenchmark) {
    double duration =
        run_benchmark(c.benchmarkingCycles, md2_hash, len, data, out);
    printf("Running %d cycles took %f seconds\n", c.benchmarkingCycles,
           duration);
  } else {
    md2_hash(len, data, out);
  }

  if (errno != 0) {
    return false;
  }

  md2_encode_hash(out, hash);

  if (c.implementationToUse != 2) free(data);
  return true;
}

int main(int argc, char** argv) {
  struct configuration c;
  enum argumentParseResult result = parseArguments(argc, argv, &c);

  switch (result) {
    case RESULT_EXIT_SUCCESS:
      return EXIT_SUCCESS;
    case RESULT_EXIT_FAILURE:
      return EXIT_FAILURE;
    default:
      break;
  }

  if (!md2_choose_implementation(c.implementationToUse)) {
    fprintf(stderr,
            "%s: invalid argument, implementation '%d' does not exist!\n",
            argv[0], c.implementationToUse);
    return EXIT_FAILURE;
  }

  printf(
      "Using implementation: %d, doing benchmark: %d, "
      "benchmark cycles: %d\n",
      c.implementationToUse, c.doBenchmark, c.benchmarkingCycles);

  if (c.runTests && c.implementationToUse == 2) {
    fprintf(stderr, "Cannot run tests on implementation 2!\n");
    return EXIT_FAILURE;
  }

  if (c.runTests) {
    printf("Running tests...\n\n");
    return runTests(&c);
  }

  printf("Hashing file %s...\n\n", c.filename);

  char hash[32];
  if (!calculate_hash(c, hash)) {
    if (errno != 0) {
      fprintf(stderr, "\n\033[1;31mAn error occured: %s\033[0m\n",
              strerror(errno));
      if (errno == ENOMEM)
        fprintf(stderr, "\033[1;36mPlease try to use -V2!\033[0m\n");
    }
    return EXIT_FAILURE;
  }
  printf("Hash: %s\n", hash);

  return EXIT_SUCCESS;
}
