#include "caff.h"

void print_credits(CAFF_Credits *credits)
{
    printf("Creator:%s\n", credits->creator);
    printf("Date:%d.%d.%d %d:%d\n", credits->year, credits->month, credits->day, credits->hour, credits->minute);
}

void caff_init_animation_list(CAFF_Animation_List *a)
{
    a->array = NULL;
    a->length = 0;
    a->size = 0;
}

void caff_add_animation_list(CAFF_Animation_List *a, CAFF_Animation element)
{
    if (a->length == a->size)
    {
        a->size = (a->size == 0) ? 1 : a->size * 2;
        CAFF_Animation *new_array = realloc(a->array, a->size * sizeof(CAFF_Animation));
        if (!new_array)
        {
            printf("Failed to allocate memory!\n");
            exit(EXIT_FAILURE);
        }
        a->array = new_array;
    }
    a->array[a->length++] = element;
}

void caff_free_animtion_list(CAFF_Animation_List *a)
{
    free(a->array);
    a->array = NULL;
    a->length = a->size = 0;
}

CAFF *caff_parse_file(const char *filename)
{
    FILE *file = fopen(filename, "rb");
    if (!file)
    {
        printf("Failed to open CAFF file: %s\n", filename);
        return NULL;
    }

    CAFF *caff = malloc(sizeof(CAFF));
    if (!caff)
    {
        printf("Failed to allocate memory for CAFF!\n");
        return NULL;
    }
    caff->bytes_read = 0;
    caff->credits = NULL;
    caff->header = NULL;
    caff_init_animation_list(&caff->animations);

    size_t anim_count = 0;
    while (!feof(file))
    {
        uint8_t id;
        uint64_t length;
        if(fread(&id, sizeof(uint8_t), 1, file) != 1 ||
        fread(&length, sizeof(uint64_t), 1, file) != 1){
            printf("Failed to read block id/length!\n");
            fclose(file);
            if(anim_count == 0){
                free(caff);
                caff = NULL;
            }
            return caff;
        }

        switch (id)
        {
        case 0x1:
            // Do not replace the header after it is initialized
            if (!caff->header)
                caff->header = read_header(file);
            break;
        case 0x2:
            // Do not replace the credits after it is initialized
            if (!caff->credits)
                caff->credits = read_credits(file);
            print_credits(caff->credits);
            break;
        case 0x3:
            anim_count++;
            if (caff->header && caff->header->num_anim == anim_count)
            {
                fclose(file);
                return caff;
            }
            // You might want to append to a list of animations here.
            CAFF_Animation *anim = read_animation(file);
            if(anim){
                caff_add_animation_list(&caff->animations, *anim);
            }
            
            free(anim);
            // We only need the first image now...
            fclose(file);
            return caff;
        default:
            // Random bytes should not cause an error
            printf("Unknown block ID: %u\n", id);
            fclose(file);
            if(anim_count == 0){
                free(caff);
                caff = NULL;
            }
            return caff;
        }
    }

    fclose(file);
    printf("Parse END!\n");
    return caff;
}

CAFF_Header *read_header(FILE *file)
{
    CAFF_Header *header = malloc(sizeof(CAFF_Header));
    if (!header)
    {
        printf("Failed to allocate memory for CAFF header\n");
        return NULL;
    }

    // Read the magic string.
    if (fread(header->magic, sizeof(char), 4, file) != 4)
    {
        printf("Failed to read magic string from CAFF header\n");
        free(header);
        return NULL;
    }

    // Check the magic string.
    if (strncmp(header->magic, "CAFF", 4) != 0)
    {
        printf("Invalid magic string in CAFF header\n");
        free(header);
        return NULL;
    }

    // Read the header size.
    if (fread(&header->header_size, sizeof(uint64_t), 1, file) != 1)
    {
        printf("Failed to read header size from CAFF header\n");
        free(header);
        return NULL;
    }

    // Read the number of animations.
    if (fread(&header->num_anim, sizeof(uint64_t), 1, file) != 1)
    {
        printf("Failed to read number of animations from CAFF header\n");
        free(header);
        return NULL;
    }

    return header;
}

CAFF_Credits *read_credits(FILE *file)
{
    CAFF_Credits *credits = malloc(sizeof(CAFF_Credits));
    if (!credits)
    {
        printf("Failed to allocate memory for CAFF credits\n");
        return NULL;
    }

    // Read the creation date and time.
    if (fread(&credits->year, sizeof(uint16_t), 1, file) != 1 ||
        fread(&credits->month, sizeof(uint8_t), 1, file) != 1 ||
        fread(&credits->day, sizeof(uint8_t), 1, file) != 1 ||
        fread(&credits->hour, sizeof(uint8_t), 1, file) != 1 ||
        fread(&credits->minute, sizeof(uint8_t), 1, file) != 1)
    {
        printf("Failed to read creation date and time from CAFF credits\n");
        free(credits);
        return NULL;
    }

    // Read the length of the creator field.
    if (fread(&credits->creator_len, sizeof(uint64_t), 1, file) != 1)
    {
        printf("Failed to read length of creator field from CAFF credits\n");
        free(credits);
        return NULL;
    }

    // Allocate memory for the creator field.
    credits->creator = malloc(credits->creator_len + 1);
    if (!credits->creator)
    {
        printf("Failed to allocate memory for creator field in CAFF credits\n");
        free(credits);
        return NULL;
    }

    // Read the creator field.
    if (fread(credits->creator, 1, credits->creator_len, file) != credits->creator_len)
    {
        printf("Failed to read creator field from CAFF credits\n");
        free(credits->creator);
        free(credits);
        return NULL;
    }

    // Null-terminate the creator string.
    credits->creator[credits->creator_len] = '\0';

    return credits;
}

CAFF_Animation *read_animation(FILE *file)
{
    CAFF_Animation *animation = malloc(sizeof(CAFF_Animation));
    if (!animation)
    {
        printf("Failed to allocate memory for CAFF animation\n");
        return NULL;
    }

    // Read the duration.
    if (fread(&animation->duration, sizeof(uint64_t), 1, file) != 1)
    {
        printf("Failed to read duration from CAFF animation\n");
        free(animation);
        return NULL;
    }

    // Read the CIFF image.
    animation->ciff = read_ciff(file);
    if (!animation->ciff)
    {
        printf("Failed to read CIFF image from CAFF animation\n");
        free(animation);
        return NULL;
    }

    return animation;
}

void caff_free(CAFF *caff)
{
    // Free the header
    free(caff->header);
    caff->header = NULL;

    // Free the credits
    if (caff->credits->creator)
    {
        free(caff->credits->creator);
    }
    free(caff->credits);
    caff->credits = NULL;

    // Free the animations
    for (size_t i = 0; i < caff->animations.length; i++)
    {
        free_ciff(caff->animations.array[i].ciff);
    }
    caff_free_animtion_list(&caff->animations);

    // Finally, free the CAFF struct itself
    free(caff);
}