#include "../../lib/md2_impls/md2_common.h"

// Source: https://datatracker.ietf.org/doc/html/rfc1319
unsigned char MD2_PI_SUBST[256] = {
    41,  46,  67,  201, 162, 216, 124, 1,   61,  54,  84,  161, 236, 240, 6,
    19,  98,  167, 5,   243, 192, 199, 115, 140, 152, 147, 43,  217, 188, 76,
    130, 202, 30,  155, 87,  60,  253, 212, 224, 22,  103, 66,  111, 24,  138,
    23,  229, 18,  190, 78,  196, 214, 218, 158, 222, 73,  160, 251, 245, 142,
    187, 47,  238, 122, 169, 104, 121, 145, 21,  178, 7,   63,  148, 194, 16,
    137, 11,  34,  95,  33,  128, 127, 93,  154, 90,  144, 50,  39,  53,  62,
    204, 231, 191, 247, 151, 3,   255, 25,  48,  179, 72,  165, 181, 209, 215,
    94,  146, 42,  172, 86,  170, 198, 79,  184, 56,  210, 150, 164, 125, 182,
    118, 252, 107, 226, 156, 116, 4,   241, 69,  157, 112, 89,  100, 113, 135,
    32,  134, 91,  207, 101, 230, 45,  168, 2,   27,  96,  37,  173, 174, 176,
    185, 246, 28,  70,  97,  105, 52,  64,  126, 15,  85,  71,  163, 35,  221,
    81,  175, 58,  195, 92,  249, 206, 186, 197, 234, 38,  44,  83,  13,  110,
    133, 40,  132, 9,   211, 223, 205, 244, 65,  129, 77,  82,  106, 220, 55,
    200, 108, 193, 171, 250, 36,  225, 123, 8,   12,  189, 177, 74,  120, 136,
    149, 139, 227, 99,  232, 109, 233, 203, 213, 254, 59,  0,   29,  57,  242,
    239, 183, 14,  102, 88,  208, 228, 166, 119, 114, 248, 235, 117, 75,  10,
    49,  68,  80,  180, 143, 237, 31,  26,  219, 153, 141, 51,  159, 17,  131,
    20};

void md2_print_buf(size_t len, uint8_t buf[len]) {
  for (size_t i = 0; i < len; i++) {
    printf("'%02x',", buf[i]);
  }
  printf("\n");
}

static double detailed_benchmark_timestamps[6] = {0, 0, 0, 0, 0, 0};
static double detailed_benchmark_times[3] = {0, 0, 0};

void md2_process_detailed_benchmark_step(
    enum md2_detailed_benchmark_step step) {
  switch (step) {
    case CHECKSUM_START:
    case FIRST_LOOP_START:
    case SECOND_LOOP_START:
      detailed_benchmark_timestamps[step] = current_time();
      return;

    case CHECKSUM_END:
    case FIRST_LOOP_END:
    case SECOND_LOOP_END:
      detailed_benchmark_timestamps[step] = current_time();
      detailed_benchmark_times[step / 2] +=
          detailed_benchmark_timestamps[step] -
          detailed_benchmark_timestamps[step - 1];
      return;
  }
}

void md2_print_detailed_benchmark_result() {
  printf("Detailed benchmarking results:\n");
  printf("  Checksum: %f\n", detailed_benchmark_times[0]);
  printf("  First loop: %f\n", detailed_benchmark_times[1]);
  printf("  Second loop: %f\n", detailed_benchmark_times[2]);
  printf("\n");
}

uint8_t* md2_add_padding_and_space_for_checksum(const uint8_t* buf,
                                                size_t* len) {
  int paddingNeeded = 16 - ((*len) % 16);
  uint8_t originalPadding = paddingNeeded;
  (*len) += paddingNeeded;

  // +16 for the checksum
  uint8_t* newBuf = calloc((*len) + 16, sizeof(uint8_t));
  if (newBuf == NULL) {
    return NULL;
  }
  memcpy(newBuf, buf, (*len) - paddingNeeded);

  while (paddingNeeded > 0) {
    newBuf[(*len) - paddingNeeded] = originalPadding;
    paddingNeeded--;
  }

  return newBuf;
}

void md2_process_block_checksum(uint8_t const block[16], uint8_t checksum[16],
                                uint8_t* l) {
  for (int j = 0; j < 16; j++) {
    u_int8_t c = block[j];
    // reference is wrong. It says: Set C[j] to S[c xor L]. But it should be:
    (*l) = checksum[j] ^= MD2_PI_SUBST[c ^ (*l)];
  }
}

void md2_checksum(size_t len, uint8_t* buf) {
  md2_checksum_with_target(len, buf, buf + len);
}

void md2_checksum_with_target(size_t len, const uint8_t* buf,
                              uint8_t checksum[16]) {
  CHECKSUM_START_MARK
  uint8_t l = 0;

  for (size_t i = 0; i < len / 16; i++) {
    md2_process_block_checksum(buf + i * 16, checksum, &l);
  }
  CHECKSUM_END_MARK
}

void md2_second_loop(uint8_t* messageDigestBuf) {
  u_int8_t t = 0;

  SECOND_LOOP_START_MARK
  for (int j = 0; j < 18; j++) {
    for (int k = 0; k < 48; k++) {
      t = messageDigestBuf[k] = messageDigestBuf[k] ^ MD2_PI_SUBST[t];
    }
    t = (t + j) & 255;
  }
  SECOND_LOOP_END_MARK
}

void md2_first_loop(const uint8_t* buf, uint8_t* messageDigestBuf, size_t i) {
  FIRST_LOOP_START_MARK
  for (int j = 0; j < 16; j++) {
    messageDigestBuf[16 + j] = buf[i * 16 + j];
    messageDigestBuf[32 + j] = (messageDigestBuf[16 + j] ^ messageDigestBuf[j]);
  }
  FIRST_LOOP_END_MARK
}