#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "imageInput.h"

#define FILE_HEADER_STRING "__info2_image_file_format__"

/* -------------------------------------------------------------------------
 * Hilfsfunktion: liest ein unsigned short (16-bit) aus Datei
 * Rückgabe: 1 bei Erfolg, 0 bei Fehler
 * ------------------------------------------------------------------------- */
static int readUInt16(FILE *f, unsigned short *out)
{
    return fread(out, sizeof(unsigned short), 1, f) == 1;
}

/* -------------------------------------------------------------------------
 * Header prüfen:
 * - Tests schreiben den Header exakt als strlen(FILE_HEADER_STRING) Bytes
 *   (OHNE Nullterminator). Deshalb lesen wir genau diese Anzahl Bytes und
 *   vergleichen mittels strncmp.
 * ------------------------------------------------------------------------- */
static int readHeader(FILE *f)
{
    size_t len = strlen(FILE_HEADER_STRING);
    char buf[64]; /* genügend groß für unseren Header */

    if (len >= sizeof(buf))
        return 0;

    if (fread(buf, 1, len, f) != len)
        return 0;

    /* selbst terminieren, damit strcmp / strncmp sauber arbeiten können */
    buf[len] = '\0';

    return strcmp(buf, FILE_HEADER_STRING) == 0;
}

/* -------------------------------------------------------------------------
 * readImages:
 * Liest das Format wie in den Tests erzeugt:
 *
 * [header bytes (strlen(FILE_HEADER_STRING))]
 * uint16_t numberOfImages
 * uint16_t width
 * uint16_t height
 *
 * für jedes Bild:
 *   width*height bytes pixel
 *   uint8_t label
 * ------------------------------------------------------------------------- */
GrayScaleImageSeries *readImages(const char *path)
{
    if (!path)
        return NULL;

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

    /* Header prüfen */
    if (!readHeader(f))
    {
        fclose(f);
        return NULL;
    }

    /* Anzahl der Bilder (unsigned short in den Tests) */
    unsigned short count16;
    if (!readUInt16(f, &count16))
    {
        fclose(f);
        return NULL;
    }

    /* Breite und Höhe (ebenfalls unsigned short in den Tests) */
    unsigned short width16, height16;
    if (!readUInt16(f, &width16) || !readUInt16(f, &height16))
    {
        fclose(f);
        return NULL;
    }

    /* Sicherheitscheck: keine extremen Werte */
    if (count16 == 0 || width16 == 0 || height16 == 0)
    {
        /* ungültige/metadaten -> als Fehler behandeln */
        fclose(f);
        return NULL;
    }

    /* Serie allokieren */
    GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
    if (!series)
    {
        fclose(f);
        return NULL;
    }

    series->count = (unsigned int)count16;
    series->images = calloc(series->count, sizeof(GrayScaleImage));
    series->labels = calloc(series->count, sizeof(unsigned char));
    if (!series->images || !series->labels)
    {
        clearSeries(series);
        fclose(f);
        return NULL;
    }

    /* Für alle Bilder: Breite / Höhe sind gleich (wie im Test geschrieben) */
    for (unsigned int i = 0; i < series->count; ++i)
    {
        series->images[i].width  = (unsigned int)width16;
        series->images[i].height = (unsigned int)height16;

        /* Größe berechnen und Puffer allokieren */
        unsigned int size = series->images[i].width * series->images[i].height;

        series->images[i].buffer = malloc(size * sizeof(GrayScalePixelType));
        if (!series->images[i].buffer)
        {
            clearSeries(series);
            fclose(f);
            return NULL;
        }

        /* Pixeldaten einlesen */
        size_t readPixels = fread(series->images[i].buffer, sizeof(GrayScalePixelType), size, f);
        if (readPixels != size)
        {
            clearSeries(series);
            fclose(f);
            return NULL;
        }

        /* Label einlesen (1 Byte) */
        if (fread(&series->labels[i], sizeof(unsigned char), 1, f) != 1)
        {
            clearSeries(series);
            fclose(f);
            return NULL;
        }
    }

    fclose(f);
    return series;
}

/* -------------------------------------------------------------------------
 * clearSeries: gibt alle Ressourcen einer Serie frei
 * - ist NULL-sicher
 * ------------------------------------------------------------------------- */
void clearSeries(GrayScaleImageSeries *series)
{
    if (!series)
        return;

    if (series->images)
    {
        for (unsigned int i = 0; i < series->count; ++i)
        {
            free(series->images[i].buffer);
            series->images[i].buffer = NULL;
        }
        free(series->images);
        series->images = NULL;
    }

    free(series->labels);
    series->labels = NULL;

    free(series);
}
