#include "vkz_format.h"

// ── CRC32 lookup table ─────────────────────────────────────────────
static uint32_t crc32_table[256];
static bool crc32_table_init = false;

static void init_crc32_table(void) {
  if (crc32_table_init)
    return;
  for (uint32_t i = 0; i < 256; i++) {
    uint32_t c = i;
    for (int j = 0; j < 8; j++) {
      if (c & 1)
        c = 0xEDB88320 ^ (c >> 1);
      else
        c >>= 1;
    }
    crc32_table[i] = c;
  }
  crc32_table_init = true;
}

uint32_t vkz_crc32(const uint8_t *data, size_t length) {
  init_crc32_table();
  uint32_t crc = 0xFFFFFFFF;
  for (size_t i = 0; i < length; i++) {
    crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
  }
  return crc ^ 0xFFFFFFFF;
}

// ── Create archive ─────────────────────────────────────────────────
VkzArchive *vkz_create(uint64_t original_size, uint32_t block_size,
                       const char *original_filename) {
  VkzArchive *archive = (VkzArchive *)calloc(1, sizeof(VkzArchive));
  if (!archive)
    return NULL;

  uint32_t block_count =
      (uint32_t)((original_size + block_size - 1) / block_size);
  if (block_count == 0)
    block_count = 1;

  archive->header.magic = VKZ_MAGIC;
  archive->header.version = VKZ_VERSION;
  archive->header.original_size = original_size;
  archive->header.block_size = block_size;
  archive->header.block_count = block_count;
  archive->header.flags = 0;
  archive->header.checksum = 0;

  archive->blocks = (VkzBlockEntry *)calloc(block_count, sizeof(VkzBlockEntry));
  if (!archive->blocks) {
    free(archive);
    return NULL;
  }

  // Initialize block sizes
  for (uint32_t i = 0; i < block_count; i++) {
    uint64_t remaining = original_size - (uint64_t)i * block_size;
    archive->blocks[i].original_size =
        (uint32_t)(remaining < block_size ? remaining : block_size);
  }

  if (original_filename) {
    strncpy(archive->original_filename, get_filename(original_filename),
            VKZ_MAX_FILENAME - 1);
  }

  return archive;
}

// ── Write archive to file ──────────────────────────────────────────
int vkz_write(const VkzArchive *archive, const uint8_t **compressed_blocks,
              const char *output_path) {
  FILE *f = fopen(output_path, "wb");
  if (!f) {
    LOG_ERR("Cannot create output file: %s", output_path);
    return -1;
  }

  // Calculate data offsets
  uint64_t header_size = sizeof(VkzHeader);
  uint64_t table_size =
      (uint64_t)archive->header.block_count * sizeof(VkzBlockEntry);
  uint64_t filename_size = strlen(archive->original_filename) + 1;
  uint64_t data_offset = header_size + table_size + filename_size;

  // Build block table with correct offsets
  VkzBlockEntry *entries = (VkzBlockEntry *)malloc(table_size);
  if (!entries) {
    fclose(f);
    return -1;
  }
  memcpy(entries, archive->blocks, table_size);

  uint64_t current_offset = data_offset;
  for (uint32_t i = 0; i < archive->header.block_count; i++) {
    entries[i].compressed_offset = current_offset;
    current_offset += entries[i].compressed_size;
  }

  // Write header
  fwrite(&archive->header, sizeof(VkzHeader), 1, f);

  // Write block table
  fwrite(entries, sizeof(VkzBlockEntry), archive->header.block_count, f);

  // Write original filename
  fwrite(archive->original_filename, 1, filename_size, f);

  // Write compressed blocks
  for (uint32_t i = 0; i < archive->header.block_count; i++) {
    if (compressed_blocks[i] && entries[i].compressed_size > 0) {
      fwrite(compressed_blocks[i], 1, entries[i].compressed_size, f);
    }
  }

  free(entries);
  fclose(f);

  return 0;
}

// ── Read archive from file ─────────────────────────────────────────
VkzArchive *vkz_read(const char *archive_path) {
  FILE *f = fopen(archive_path, "rb");
  if (!f) {
    LOG_ERR("Cannot open archive: %s", archive_path);
    return NULL;
  }

  VkzArchive *archive = (VkzArchive *)calloc(1, sizeof(VkzArchive));
  if (!archive) {
    fclose(f);
    return NULL;
  }

  // Read header
  if (fread(&archive->header, sizeof(VkzHeader), 1, f) != 1) {
    LOG_ERR("Failed to read archive header");
    free(archive);
    fclose(f);
    return NULL;
  }

  // Validate magic
  if (archive->header.magic != VKZ_MAGIC) {
    LOG_ERR(
        "Invalid archive: magic number mismatch (got 0x%08X, expected 0x%08X)",
        archive->header.magic, VKZ_MAGIC);
    free(archive);
    fclose(f);
    return NULL;
  }

  // Validate version
  if (archive->header.version != VKZ_VERSION) {
    LOG_ERR("Unsupported archive version: %u (supported: %u)",
            archive->header.version, VKZ_VERSION);
    free(archive);
    fclose(f);
    return NULL;
  }

  // Read block table
  archive->blocks = (VkzBlockEntry *)calloc(archive->header.block_count,
                                            sizeof(VkzBlockEntry));
  if (!archive->blocks) {
    free(archive);
    fclose(f);
    return NULL;
  }

  if (fread(archive->blocks, sizeof(VkzBlockEntry), archive->header.block_count,
            f) != archive->header.block_count) {
    LOG_ERR("Failed to read block table");
    vkz_free(archive);
    fclose(f);
    return NULL;
  }

  // Read filename
  int ch;
  size_t name_len = 0;
  while ((ch = fgetc(f)) != EOF && ch != '\0' &&
         name_len < VKZ_MAX_FILENAME - 1) {
    archive->original_filename[name_len++] = (char)ch;
  }
  archive->original_filename[name_len] = '\0';

  fclose(f);
  return archive;
}

// ── Read compressed block ──────────────────────────────────────────
uint8_t *vkz_read_block(const char *archive_path, const VkzArchive *archive,
                        uint32_t block_index, uint32_t *out_size) {
  if (block_index >= archive->header.block_count)
    return NULL;

  FILE *f = fopen(archive_path, "rb");
  if (!f)
    return NULL;

  const VkzBlockEntry *entry = &archive->blocks[block_index];
  uint8_t *data = (uint8_t *)malloc(entry->compressed_size);
  if (!data) {
    fclose(f);
    return NULL;
  }

  fseek(f, (long)entry->compressed_offset, SEEK_SET);
  if (fread(data, 1, entry->compressed_size, f) != entry->compressed_size) {
    free(data);
    fclose(f);
    return NULL;
  }

  *out_size = entry->compressed_size;
  fclose(f);
  return data;
}

// ── Free archive ───────────────────────────────────────────────────
void vkz_free(VkzArchive *archive) {
  if (!archive)
    return;
  if (archive->blocks)
    free(archive->blocks);
  free(archive);
}

// ── Print archive info ─────────────────────────────────────────────
void vkz_print_info(const VkzArchive *archive) {
  if (!archive)
    return;

  char orig_buf[32], comp_buf[32];
  format_size(archive->header.original_size, orig_buf, sizeof(orig_buf));

  uint64_t total_compressed = 0;
  for (uint32_t i = 0; i < archive->header.block_count; i++) {
    total_compressed += archive->blocks[i].compressed_size;
  }
  format_size(total_compressed, comp_buf, sizeof(comp_buf));

  double ratio = archive->header.original_size > 0
                     ? (double)total_compressed /
                           (double)archive->header.original_size * 100.0
                     : 0.0;

  printf("\n");
  printf(COL_BOLD COL_CYAN
         "╔══════════════════════════════════════════╗\n" COL_RESET);
  printf(COL_BOLD COL_CYAN
         "║          VKZ Archive Information         ║\n" COL_RESET);
  printf(COL_BOLD COL_CYAN
         "╠══════════════════════════════════════════╣\n" COL_RESET);
  printf(COL_CYAN "║" COL_RESET " File:       %-28s" COL_CYAN "║\n" COL_RESET,
         archive->original_filename);
  printf(COL_CYAN "║" COL_RESET " Original:   %-28s" COL_CYAN "║\n" COL_RESET,
         orig_buf);
  printf(COL_CYAN "║" COL_RESET " Compressed: %-28s" COL_CYAN "║\n" COL_RESET,
         comp_buf);
  printf(COL_CYAN "║" COL_RESET " Ratio:      %-27.1f%%" COL_CYAN
                  "║\n" COL_RESET,
         ratio);
  printf(COL_CYAN "║" COL_RESET " Blocks:     %-28u" COL_CYAN "║\n" COL_RESET,
         archive->header.block_count);
  printf(COL_CYAN "║" COL_RESET " Block Size: %-28u" COL_CYAN "║\n" COL_RESET,
         archive->header.block_size);
  printf(COL_CYAN "║" COL_RESET " Version:    %-28u" COL_CYAN "║\n" COL_RESET,
         archive->header.version);
  printf(COL_BOLD COL_CYAN
         "╚══════════════════════════════════════════╝\n" COL_RESET);
  printf("\n");
}
