#include "crc.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

uint32_t crc_sym_table[256];

bool crc_sym_table_computed = false;

void crc_make_table(void) {
  for (size_t n = 0; n < 256; n++) {
    uint32_t c = n;

    for (size_t k = 0; k < 8; k++) {
      if (c & 1)
        c = 0xEDB88320 ^ (c >> 1);
      else
        c >>= 1;
    }

    crc_sym_table[n] = c;
  }

  crc_sym_table_computed = true;
}

uint32_t crc_update(uint32_t crc, uint8_t *buf, size_t size) {
  uint32_t c = crc ^ 0xFFFFFFFF;

  if (!crc_sym_table_computed)
    crc_make_table();

  for (size_t n = 0; n < size; n++)
    c = crc_sym_table[(c ^ buf[n]) & 0xFF] ^ (c >> 8);

  return c ^ 0xFFFFFFFF;
}

uint32_t crc_compute(uint8_t *buf, size_t size) {
  return crc_update(0, buf, size);
}

bool crc_append(char *path) {
  FILE *input_file = fopen(path, "rb");
  if (!input_file) {
    fprintf(stderr, "Error: Can't open file %s to read.\n", path);

    return false;
  }

  fseek(input_file, 0, SEEK_END);
  size_t file_size = ftell(input_file);
  fseek(input_file, 0, SEEK_SET);

  uint8_t *buf = (uint8_t *)malloc(file_size);
  if (!buf) {
    fprintf(stderr, "Error: Can't allocate memory for buffer.\n");

    fclose(input_file);
    return false;
  }

  size_t read_size = fread(buf, 1, file_size, input_file);
  if (read_size != file_size) {
    fprintf(stderr,
            "Error: File size (%zu) is differ from expected size (%zu).\n",
            read_size, file_size);

    free(buf);
    fclose(input_file);
    return false;
  }

  fclose(input_file);

  uint32_t computed = crc_compute(buf, file_size);
  free(buf);

  fprintf(stderr, "Info: Computed RSA is %u\n", computed);

  FILE *output_file = fopen(path, "ab");
  if (!output_file) {
    fprintf(stderr, "Error: Can't open file %s to write.\n", path);

    return false;
  }

  if (fwrite(&computed, sizeof(computed), 1, output_file) != 1) {
    fprintf(stderr, "Error: Can't write computed crc to file.\n");

    fclose(output_file);
    return false;
  }

  fclose(output_file);
  return true;
}

bool crc_verify(char *path) {
  FILE *input_file = fopen(path, "rb");
  if (!input_file) {
    fprintf(stderr, "Error: Can't open file %s to read.\n", path);

    return false;
  }

  fseek(input_file, 0, SEEK_END);
  size_t file_size = ftell(input_file);
  if (file_size < 4) {
    fprintf(stderr,
            "Error: File size is less then 4 bytes, can't verify checksum.\n");

    fclose(input_file);
    return false;
  }

  size_t data_size = file_size - 4;
  fseek(input_file, 0, SEEK_SET);

  uint8_t *buf = (uint8_t *)malloc(data_size);
  if (!buf) {
    fprintf(stderr, "Error: Can't allocate memory for buffer.\n");

    fclose(input_file);
    return false;
  }

  size_t read_size = fread(buf, 1, data_size, input_file);
  if (read_size != data_size) {
    fprintf(stderr,
            "Error: File size (%zu) is differ from expected size (%zu).\n",
            read_size, data_size);

    free(buf);
    fclose(input_file);
    return false;
  }

  uint32_t computed = crc_compute(buf, data_size);
  uint32_t stored = 0;

  if (fread(&stored, sizeof(stored), 1, input_file) != 1) {
    fprintf(stderr, "Error: Can't read stored checksum from the file.\n");

    free(buf);
    fclose(input_file);
    return false;
  }

  fclose(input_file);
  free(buf);

  fprintf(stderr, "Info: Computed RSA is %u\n", computed);
  fprintf(stderr, "Info: Stored RSA is %u\n", stored);

  return computed == stored;
}
