#include <container.h>
#include <gfx.h>
#include <sfx.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int readstr(char *const str, const size_t n, FILE *const f)
{
    int ret = -1;
    size_t i = 0;

    do
    {
        if (i >= n)
        {
            fprintf(stderr, "%s: string too long (> %zu bytes): %*.s\n",
                __func__, n, (int)n, str);
            goto end;
        }
        else if (!fread(&str[i], sizeof *str, 1, f))
        {
            fprintf(stderr, "%s: expected null-terminated string\n", __func__);
            goto end;
        }
    } while (str[i++]);

    ret = 0;

end:
    return ret;
}

static int get_file_size(size_t *const sz, FILE *const f)
{
    char szstr[sizeof "2147483647"];

    if (readstr(szstr, sizeof szstr, f))
        return -1;

    errno = 0;
    char *end;
    const unsigned long val = strtoul(szstr, &end, 10);

    if (errno)
    {
        fprintf(stderr, "%s: strtoul(3): %s\n", __func__, strerror(errno));
        return -1;
    }
    else if (*end)
    {
        fprintf(stderr, "%s: invalid number %s\n", __func__, szstr);
        return -1;
    }

    *sz = val;
    return 0;
}

static int read_file_contents(const struct container *const el, FILE *const f,
    const long init_off, const size_t sz)
{
    int ret = -1;

    switch (el->type)
    {
        case CONTAINER_TYPE_SPRITE:
            if (sprite_from_fp(el->data.sprite, f))
                goto end;

            break;

        case CONTAINER_TYPE_SOUND:
            if (sfx_sound_from_fp(el->data.sound, f, sz))
                goto end;

            break;
    }

    const long off = ftell(f);

    if (off < 0)
    {
        fprintf(stderr, "%s:%d: fseek failed: %s\n",
            __func__, __LINE__, strerror(errno));
        goto end;
    }
    else if (off > init_off + sz)
    {
        fprintf(stderr, "%s: %s: detected read past file contents\n",
            __func__, el->path);
        goto end;
    }
    else if (off != init_off + sz)
    {
        fprintf(stderr, "only %ld bytes read, %ld expected\n",
            off, init_off + sz);
        goto end;
    }

    ret = 0;

end:
    return ret;
}

static const struct container *find_element(const struct container *const list,
    const size_t n, FILE *const f)
{
    char name[128];

    if (readstr(name, sizeof name, f))
        return NULL;

    for (size_t i = 0; i < n; i++)
    {
        const struct container *const el = &list[i];

        if (!strcmp(el->path, name))
            return el;
    }

    fprintf(stderr, "file %s not found on the list\n", name);
    return NULL;
}

static int read_element(const struct container *const list, const size_t n,
    FILE *const f, bool *const done)
{
    long init_off;
    const struct container *el = NULL;
    size_t sz;

    if (!(el = find_element(list, n, f))
        || get_file_size(&sz, f))
        return -1;
    else if ((init_off = ftell(f)) < 0)
    {
        fprintf(stderr, "%s:%d: fseek failed: %s\n",
            __func__, __LINE__, strerror(errno));
        return -1;
    }
    else if (read_file_contents(el, f, init_off, sz))
        return -1;

    done[el - list] = true;
    return 0;
}

static int read_all_elements(const struct container *const list, const size_t n,
    FILE *const f)
{
    int ret = -1;
    /* VLAs are generally frowned upon, but we can safely assume
     * 'n' is reasonably low. */
    bool done[n];

    memset(&done, 0, sizeof done);

    for (size_t i = 0; i < sizeof done / sizeof *done; i++)
    {
        if (feof(f) || ferror(f))
        {
            fprintf(stderr, "feof or ferror found\n");
            goto end;
        }
        else if (read_element(list, n, f, done))
            goto end;
    }

    for (size_t i = 0; i < sizeof done / sizeof *done; i++)
    {
        if (!done[i])
        {
            fprintf(stderr, "%s: %s not found inside container file\n",
                __func__, list[i].path);
            goto end;
        }
    }

    ret = 0;

end:
    return ret;
}

void container_free(const struct container *const list, const size_t n)
{
    if (!list)
        return;

    for (size_t i = 0; i < n; i++)
    {
        const struct container *const el = &list[i];

        switch (el->type)
        {
            case CONTAINER_TYPE_SPRITE:
                sprite_free(el->data.sprite);
                break;

            case CONTAINER_TYPE_SOUND:
                sfx_free(el->data.sound);
                break;
        }
    }
}

int container_load_ex(const char *const path, const struct container *const list,
    const size_t n)
{
    int ret = -1;
    FILE *f = NULL;

    if (!(f = fopen(path, "rb")))
    {
        fprintf(stderr, "could not open %s: %s\n", path, strerror(errno));
        goto end;
    }
    else if (read_all_elements(list, n, f))
        goto end;

    ret = 0;

end:
    if (f)
        fclose(f);

    return ret;
}
