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

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

struct thread_args {
  size_t len;
  const uint8_t* buf;
};

void process_nothread_hash(size_t len, const uint8_t buf[len],
                           uint8_t messageDigestBuf[48]) {
  for (size_t i = 0; i < (len + 16) / 16 - 1; i++) {
    for (int j = 0; j < 16; j++) {
      messageDigestBuf[16 + j] = buf[i * 16 + j];
      messageDigestBuf[32 + j] =
          (messageDigestBuf[16 + j] ^ messageDigestBuf[j]);
    }

    u_int8_t t = 0;

    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) % 256;
    }
  }
}

void* process_hash(void* threadArgs) {
  struct thread_args* args = (struct thread_args*)threadArgs;

  uint8_t* messageDigestBuf = calloc(48, sizeof(uint8_t));
  if (messageDigestBuf == NULL) {
    return NULL;
  }

  process_nothread_hash(args->len, args->buf, messageDigestBuf);
  pthread_exit(messageDigestBuf);
}

void* process_checksum(void* threasdArgs) {
  struct thread_args* args = (struct thread_args*)threasdArgs;

  uint8_t* checksum = calloc(16, sizeof(uint8_t));
  if (checksum == NULL) {
    return NULL;
  }

  uint8_t l = 0;

  for (size_t i = 0; i < args->len / 16; i++) {
    for (int j = 0; j < 16; j++) {
      u_int8_t c = args->buf[i * 16 + 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];
    }
  }
  pthread_exit(checksum);
}

void md2_hash_3(size_t len, const uint8_t buf[len], uint8_t out[16]) {
  int paddingNeeded = 16 - (len % 16);
  uint8_t originalPadding = paddingNeeded;
  len += paddingNeeded;

  uint8_t* newBuf = calloc(len + 16, sizeof(uint8_t));
  if (newBuf == NULL) {
    return;
  }

  memcpy(newBuf, buf, len - paddingNeeded);

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

  pthread_t thread_1, thread_2;

  struct thread_args thread_args = {len, newBuf};

  if (pthread_create(&thread_1, NULL, process_hash, (void*)&thread_args) != 0) {
    printf("Error creating thread 1\n");
    if (errno == 0) errno = EAGAIN;
    return;
  }
  if (pthread_create(&thread_2, NULL, process_checksum, (void*)&thread_args) !=
      0) {
    printf("Error creating thread 2\n");
    if (errno == 0) errno = EAGAIN;
    return;
  }

  u_int8_t* messageDigestBuf;
  u_int8_t* checksum;

  if (pthread_join(thread_1, (void**)&messageDigestBuf) != 0) {
    printf("Error joining thread 1\n");
    if (errno == 0) errno = EINVAL;
    return;
  }
  if (pthread_join(thread_2, (void**)&checksum) != 0) {
    printf("Error joining thread 2\n");
    if (errno == 0) errno = EINVAL;
    return;
  }

  process_nothread_hash(16, checksum, messageDigestBuf);
  memcpy(out, messageDigestBuf, 16);

  free(messageDigestBuf);
  free(checksum);
  free(newBuf);
}
