#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "imageInput.h"
#define FILE_HEADER_STRING "__info2_image_file_format__"

/*
 * Hilfsfunktion, um testweise eine valide .info2-Datei zu erzeugen.
 * Diese wird von mehreren Tests genutzt.
 */
static void prepareImageFile(const char *path, unsigned short int width, unsigned short int height, unsigned short numberOfImages, unsigned char label)
{
    FILE *file = fopen(path, "wb");

    if(file != NULL)
    {
        const char *fileTag = "__info2_image_file_format__";
        GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));

        if(zeroBuffer != NULL)
        {
            fwrite(fileTag, 1, strlen(fileTag), file);
            fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
            fwrite(&width, sizeof(width), 1, file);
            fwrite(&height, sizeof(height), 1, file);

            for(int i = 0; i < numberOfImages; i++)
            {
                fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
                fwrite(&label, sizeof(unsigned char), 1, file);
            }

            free(zeroBuffer);
        }

        fclose(file);
    }
}


/****************************************************
 *                 Bestehende Tests                 *
 ****************************************************/

void test_readImagesReturnsCorrectNumberOfImages(void)
{
    GrayScaleImageSeries *series = NULL;
    const unsigned short expectedNumberOfImages = 2;
    const char *path = "testFile.info2";

    prepareImageFile(path, 8, 8, expectedNumberOfImages, 1);
    series = readImages(path);

    TEST_ASSERT_NOT_NULL(series);
    TEST_ASSERT_EQUAL_UINT16(expectedNumberOfImages, series->count);

    clearSeries(series);
    remove(path);
}

void test_readImagesReturnsCorrectImageWidth(void)
{
    GrayScaleImageSeries *series = NULL;
    const unsigned short expectedWidth = 10;
    const char *path = "testFile.info2";

    prepareImageFile(path, expectedWidth, 8, 2, 1);
    series = readImages(path);

    TEST_ASSERT_NOT_NULL(series);
    TEST_ASSERT_NOT_NULL(series->images);
    TEST_ASSERT_EQUAL_UINT16(2, series->count);
    TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[0].width);
    TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[1].width);

    clearSeries(series);
    remove(path);
}

void test_readImagesReturnsCorrectImageHeight(void)
{
    GrayScaleImageSeries *series = NULL;
    const unsigned short expectedHeight = 10;
    const char *path = "testFile.info2";

    prepareImageFile(path, 8, expectedHeight, 2, 1);
    series = readImages(path);

    TEST_ASSERT_NOT_NULL(series);
    TEST_ASSERT_NOT_NULL(series->images);
    TEST_ASSERT_EQUAL_UINT16(2, series->count);
    TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[0].height);
    TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[1].height);

    clearSeries(series);
    remove(path);
}

void test_readImagesReturnsCorrectLabels(void)
{
    const unsigned char expectedLabel = 15;
    GrayScaleImageSeries *series = NULL;
    const char *path = "testFile.info2";

    prepareImageFile(path, 8, 8, 2, expectedLabel);
    series = readImages(path);

    TEST_ASSERT_NOT_NULL(series);
    TEST_ASSERT_NOT_NULL(series->labels);
    TEST_ASSERT_EQUAL_UINT16(2, series->count);

    for (int i = 0; i < 2; i++)
    {
        TEST_ASSERT_EQUAL_UINT8(expectedLabel, series->labels[i]);
    }

    clearSeries(series);
    remove(path);
}

void test_readImagesReturnsNullOnNotExistingPath(void)
{
    const char *path = "testFile.txt";
    remove(path);
    TEST_ASSERT_NULL(readImages(path));
}

void test_readImagesFailsOnWrongFileTag(void)
{
    const char *path = "testFile.info2";
    FILE *file = fopen(path, "w");

    if(file != NULL)
    {
        fprintf(file, "some_tag                  ");
        fclose(file);
        TEST_ASSERT_NULL(readImages(path));
    }

    remove(path);
}



/****************************************************
 *     Neue Tests     *
 ****************************************************/

/*
 * Dieser Test prüft, ob readImages() korrekt fehlschlägt,
 * wenn die Datei zwar den Header-String enthält, aber
 * NICHT die erforderlichen folgenden Meta-Informationen:
 *
 *  - numberOfImages
 *  - width
 *  - height
 *
 * Der Header allein reicht NICHT aus – daher MUSS readImages()
 * die Datei als ungültig erkennen und NULL zurückgeben.
 */
void test_readImagesFailsOnIncompleteHeader(void)
{
    const char *path = "test_incomplete_header.info2";

    FILE *file = fopen(path, "wb");

    /*
     * Wir schreiben absichtlich NUR den Datei-Header FILE_HEADER_STRING.
     * Danach müssten aber 3 Werte folgen:
     *
     *   unsigned short count
     *   unsigned short width
     *   unsigned short height
     *
     * Da diese fehlen, wird readImages() beim Lesen feststellen,
     * dass fread() nicht genug Daten bekommt → Fehler → NULL.
     */
    fwrite(FILE_HEADER_STRING, 1, strlen(FILE_HEADER_STRING), file);

    fclose(file);

    TEST_ASSERT_NULL(readImages(path));

    remove(path);
}



/*
 * Dieser Test erzeugt eine Datei mit
 *    count = 1
 *    width = 4
 *    height = 4
 *
 * Daraus folgt:
 *    1 Bild → 16 Pixelbytes + 1 Labelbyte müssen folgen.
 *
 * WIR SCHREIBEN ABER NUR 5 Pixelbytes!
 *
 * Das bedeutet:
 *  - loadImageData() erhält weniger Daten als erwartet
 *  - fread() liefert eine zu kleine Anzahl gelesener Bytes
 *  - loadImageData() gibt 0 zurück
 *  - readImages() erkennt den Fehler und gibt NULL zurück
 */
void test_readImagesFailsOnTooFewPixels(void)
{
    const char *path = "test_incomplete_pixels.info2";
    FILE *file = fopen(path, "wb");

    unsigned short count = 1, width = 4, height = 4;

    /* Header + reguläre Metadaten */
    fwrite(FILE_HEADER_STRING, 1, strlen(FILE_HEADER_STRING), file);
    fwrite(&count, sizeof(count), 1, file);
    fwrite(&width, sizeof(width), 1, file);
    fwrite(&height, sizeof(height), 1, file);

    /*
     * Erwartet wären 4 × 4 = 16 Pixelbytes.
     * Wir schreiben ABSICHTLICH NUR 5.
     */
    unsigned char pixels[5] = {0};
    fwrite(pixels, 1, 5, file);

    fclose(file);

    /* Erwartung: Fehler erkannt → NULL */
    TEST_ASSERT_NULL(readImages(path));

    remove(path);
}



/*
 * Test prüft ungültige Meta-Daten:
 *
 * width = 0  → Ein Bild mit Breite 0 existiert nicht.
 *
 * Auch height = 0 wäre ungültig.
 *
 * readImages() muss solche Fälle erkennen, bevor Speicher
 * allokiert wird. Sobald width oder height 0 ist, muss
 * readImages() abbrechen → NULL zurückgeben.
 */
void test_readImagesFailsOnZeroWidthOrHeight(void)
{
    const char *path = "test_zero_dims.info2";
    FILE *file = fopen(path, "wb");

    unsigned short count = 1, width = 0, height = 5;

    /* Header + fehlerhafte Metadaten */
    fwrite(FILE_HEADER_STRING, 1, strlen(FILE_HEADER_STRING), file);
    fwrite(&count, sizeof(count), 1, file);
    fwrite(&width, sizeof(width), 1, file);
    fwrite(&height, sizeof(height), 1, file);

    fclose(file);

    TEST_ASSERT_NULL(readImages(path));

    remove(path);
}



/****************************************************
 *                 Unity Setup/Teardown             *
 ****************************************************/

void setUp(void)  { /* nicht benötigt */ }
void tearDown(void) { /* nicht benötigt */ }



/****************************************************
 *                      MAIN                        *
 ****************************************************/

int main()
{
    UNITY_BEGIN();

    printf("\n============================\nImage input tests\n============================\n");

    RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
    RUN_TEST(test_readImagesReturnsCorrectImageWidth);
    RUN_TEST(test_readImagesReturnsCorrectImageHeight);
    RUN_TEST(test_readImagesReturnsCorrectLabels);
    RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
    RUN_TEST(test_readImagesFailsOnWrongFileTag);

    /* Neue Tests */
    RUN_TEST(test_readImagesFailsOnIncompleteHeader);
    RUN_TEST(test_readImagesFailsOnTooFewPixels);
    RUN_TEST(test_readImagesFailsOnZeroWidthOrHeight);

    return UNITY_END();
}
