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

// Größe eines temporären Puffers
#define BUFFER_SIZE 100

// Fester Header-String, der am Anfang jeder gültigen Bilddatei stehen muss
#define FILE_HEADER_STRING "__info2_image_file_format__"

/*
 * Prüft den Dateiheader und liest Bildanzahl, Breite und Höhe aus.
 * Gibt 1 (true) zurück, wenn alles gültig ist, sonst 0 (false).
 */
static int checkheader(FILE *path, unsigned short *count, unsigned short *width, unsigned short *height)
{
    unsigned int headerlen = strlen(FILE_HEADER_STRING);
    unsigned char header[64];

    // Prüfen, ob der Header-String in den Puffer passt
    if (headerlen >= sizeof(header))
    {
        return 0;
    }

    // Header aus Datei lesen – muss exakt gleich lang sein
    if (fread(header, 1, headerlen, path) != headerlen)
    {
        return 0;
    }

    // Header vergleichen – Datei muss mit FILE_HEADER_STRING beginnen
    if (strncmp((char *)header, FILE_HEADER_STRING, headerlen) != 0)
    {
        return 0;
    }

    // Danach folgen in der Datei: count, width, height (je 2 Bytes)
    if (fread(count, sizeof(unsigned short), 1, path) != 1)
        return 0;

    if (fread(width, sizeof(unsigned short), 1, path) != 1)
        return 0;

    if (fread(height, sizeof(unsigned short), 1, path) != 1)
        return 0;

    return 1;
}

/*
 * Reserviert Speicher für eine Serie von Graustufenbildern.
 * Legt:
 *   - Hauptstruktur
 *   - Array von Bildern
 *   - Array von Labels
 *   - Für jedes Bild einen Pixelpuffer
 * an.
 */
static GrayScaleImageSeries *allocateSeries(unsigned short count, unsigned short width, unsigned short height)
{
    // Platz für die Gesamtstruktur anfordern
    GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
    if (series == NULL)
    {
        return NULL;
    }

    series->count = count;

    // Arrays für Bilder und Labels anlegen
    series->images = calloc(count, sizeof(GrayScaleImage));
    series->labels = calloc(count, sizeof(unsigned char));

    if (series->images == NULL || series->labels == NULL)
    {
        return NULL;
    }

    // Jedem Bild Höhe, Breite und einen eigenen Pixelpuffer zuweisen
    for (unsigned int i = 0; i < count; i++)
    {
        series->images[i].height = height;
        series->images[i].width = width;

        // Für jedes Bild einen Puffer anlegen: width * height Pixel
        series->images[i].buffer = calloc(height * width, sizeof(GrayScalePixelType));

        if (series->images[i].buffer == NULL)
        {
            // Falls ein Puffer fehlschlägt → alles bisherige wieder freigeben
            clearSeries(series);
            return NULL;
        }
    }

    return series;
}

/*
 * Liest die Pixeldaten und Labels aller Bilder aus der Datei.
 * Reihenfolge in der Datei je Bild:
 *      1. Pixelwerte (width*height Stück)
 *      2. 1 Byte Label
 */
static int loadImageData(FILE *file, GrayScaleImageSeries *series, unsigned short count, unsigned short width, unsigned short height)
{
    for (unsigned int i = 0; i < count; i++)
    {
        size_t pixelCount = width * height;

        // Pixeldaten in den Bildpuffer schreiben
        if (fread(series->images[i].buffer, sizeof(GrayScalePixelType), pixelCount, file) != pixelCount)
        {
            return 0;
        }

        // Label lesen (1 Byte)
        if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1)
        {
            return 0;
        }
    }

    return 1;
}

// Schliesst eine Datei, sofern sie geöffnet ist
static void closefile(FILE *file)
{
    if (file != NULL)
    {
        fclose(file);
    }
}

/*
 * Hauptfunktion zum Einlesen der Bilddaten:
 *  1. Datei öffnen
 *  2. Header + Metadaten lesen
 *  3. Speicher anlegen
 *  4. Pixeldaten + Labels laden
 *  5. Datei schließen
 *  6. Zeiger auf fertige Daten zurückgeben
 */
GrayScaleImageSeries *readImages(const char *path)
{
    unsigned short count, width, height;
    GrayScaleImageSeries *series = NULL;

    // Datei im binären Modus öffnen
    FILE *file = fopen(path, "rb");
    if (file == NULL)
    {
        return NULL;
    }

    // Header überprüfen und Metadaten lesen
    if (!checkheader(file, &count, &width, &height))
    {
        closefile(file);
        return NULL;
    }

    // Speicher für Serie anlegen
    series = allocateSeries(count, width, height);
    if (series == NULL)
    {
        closefile(file);
        return NULL;
    }

    // Bilddaten laden
    if (!loadImageData(file, series, count, width, height))
    {
        clearSeries(series);
        closefile(file);
        return NULL;
    }

    // Datei schließen und fertige Struktur zurückgeben
    closefile(file);

    return series;
}

/*
 * Gibt den gesamten reservierten Speicher der Bildserie wieder frei.
 * Dazu:
 *   - alle Pixelpuffer
 *   - Bilderarray
 *   - Labelsarray
 *   - Hauptstruktur
 */
void clearSeries(GrayScaleImageSeries *series)
{
    if (series != NULL)
    {
        // Erst alle Bildpuffer freigeben
        for (unsigned int i = 0; i < series->count; i++)
        {
            free(series->images[i].buffer);
        }

        // Danach Arrays freigeben
        free(series->images);
        free(series->labels);

        // Am Ende die Struktur selbst
        free(series);
    }

    // count, width, height müssen nicht auf 0 gesetzt werden,
    // da sie bei einer Neu-Allokation sowieso überschrieben werden.
}
