/* crc32_files_and_dirs.c */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

#include "ytypes.h"
#include "crc32.h"

#define BUFFER_SIZE 4*4096
BYTE *BufferP;

ui32 *crc32P;

size_t bytesRead;

ui32 crc32_table[16][256];
#define POLY 0xEDB88320

void init_crc32_table(void) {
    for (int i = 0; i < 256; i++) {
        ui32 crc = i;
        for (int j = 0; j < 8; j++) {
            crc = (crc >> 1) ^ (POLY & -(crc & 1));
        }
        crc32_table[0][i] = crc;
    }

    for (int t = 1; t < 16; t++) {
        for (int i = 0; i < 256; i++) {
            crc32_table[t][i] = (crc32_table[t-1][i] >> 8) ^ crc32_table[0][crc32_table[t-1][i] & 0xFF];
        }
    }
}

ui32 crc32_slicing_by_16(ui32 crc, const BYTE *buf, size_t len) {
    crc = ~crc;
    while (len >= 16) {
        crc ^= *(ui32 *)buf;
        crc =
            crc32_table[15][(crc >> 0) & 0xFF] ^
            crc32_table[14][(crc >> 8) & 0xFF] ^
            crc32_table[13][(crc >> 16) & 0xFF] ^
            crc32_table[12][(crc >> 24) & 0xFF] ^
            crc32_table[11][buf[4]] ^
            crc32_table[10][buf[5]] ^
            crc32_table[9][buf[6]] ^
            crc32_table[8][buf[7]] ^
            crc32_table[7][buf[8]] ^
            crc32_table[6][buf[9]] ^
            crc32_table[5][buf[10]] ^
            crc32_table[4][buf[11]] ^
            crc32_table[3][buf[12]] ^
            crc32_table[2][buf[13]] ^
            crc32_table[1][buf[14]] ^
            crc32_table[0][buf[15]];
        buf += 16;
        len -= 16;
    }

    while (len--)
        crc = (crc >> 8) ^ crc32_table[0][(crc ^ *buf++) & 0xFF];

    return ~crc;
}

void init_crc32(ui32 *crc32Pointer) {
    crc32P = crc32Pointer;
    *crc32P = 0U;
}

void setbufferPointer(BYTE *Buffer) {
    BufferP = Buffer;
}

#ifndef _AS_STANDALONE_APP


void compute_crc32(void) {
        *crc32P = crc32_slicing_by_16(*crc32P, BufferP, bytesRead);
}

#else

BYTE buffer[BUFFER_SIZE];
ui32 crc32;

void compute_crc32(const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        perror(filename);
        return;
    }

    init_crc32(&crc32); /* initialisiert die crc32 Variable */

    while ((bytesRead = fread(BufferP, 1, BUFFER_SIZE, file)) > 0) {
        *crc32P = crc32_slicing_by_16(*crc32P, BufferP, bytesRead);
    }

    fclose(file);

    printf("%08X  %s\n", crc32, filename);
}

void traverse_directory(const char *path) {
    DIR *dir = opendir(path);
    if (!dir) {
        perror(path);
        return;
    }

    struct dirent *entry;
    char fullpath[4096];

    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            continue;

        snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);

        struct stat path_stat;
        if (stat(fullpath, &path_stat) != 0) {
            perror(fullpath);
            continue;
        }

        if (S_ISDIR(path_stat.st_mode)) {
            traverse_directory(fullpath);
        } else if (S_ISREG(path_stat.st_mode)) {
            compute_crc32(fullpath);
        }
    }

    closedir(dir);
}

void process_path(const char *path) {
    struct stat path_stat;
    if (stat(path, &path_stat) != 0) {
        perror(path);
        return;
    }

    if (S_ISDIR(path_stat.st_mode)) {
        traverse_directory(path);
    } else if (S_ISREG(path_stat.st_mode)) {
        compute_crc32(path);
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <file_or_directory> [more_files_or_dirs...]\n", argv[0]);
        return EXIT_FAILURE;
    }

    init_crc32_table();

    setbufferPointer(buffer);

    for (int i = 1; i < argc; i++) {
        process_path(argv[i]);
    }

    return EXIT_SUCCESS;
}

#endif /* AS_STANDALONE_APP */
