#include <terrain.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int read_token(FILE *const f, char *const s, const size_t n)
{
    uint8_t c;
    size_t i = 0;

    while (fread(&c, sizeof c, 1, f))
    {
        if (isspace(c))
        {
            if (i)
                break;
            else
                continue;
        }
        else if (!isalnum(c))
        {
            fprintf(stderr, "%s: invalid character %" PRIx8 " at offset %ld\n",
                __func__, c, ftell(f));
            return -1;
        }
        else if (i + 1 >= n)
        {
            fprintf(stderr, "%s: exceeded maximum token size %zu\n",
                __func__, n);
            return -1;
        }

        s[i++] = c;
    }

    if (!i)
    {
        fprintf(stderr, "%s: feof=%d, ferror=%d\n", __func__, feof(f),
            ferror(f));
        return -1;
    }

    s[i] = '\0';
    return 0;
}

static int read_num(FILE *const f, char *s, const size_t n,
    unsigned *const out, const int base, const int limit)
{
    if (read_token(f, s, n))
        return -1;

    errno = 0;

    char *end;
    const unsigned long value = strtoul(s, &end, base);

    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__, s);
        return -1;
    }
    else if (value > limit)
    {
        fprintf(stderr, "%s: %lu exceeds maximum range %d\n", __func__, value,
            limit);
        return -1;
    }

    *out = value;
    return 0;
}

static int read_dec(FILE *const f, unsigned *const out)
{
    char s[sizeof "65535"];

    return read_num(f, s, sizeof s, out, 10, UINT16_MAX);
}

static int read_hex(FILE *const f, unsigned *const out)
{
    char s[sizeof "0000"];

    return read_num(f, s, sizeof s, out, 16, UINT16_MAX);
}

static int read_dimensions(FILE *const f, unsigned *const w, unsigned *const h)
{
    if (read_dec(f, w) || read_dec(f, h))
        return -1;
    else if (*w > MAP_TILES || *h > MAP_TILES)
    {
        fprintf(stderr, "%s: map size (%ux%u) exceeded, max %dx%d\n",
            __func__, *w, *h, MAP_TILES, MAP_TILES);
        return -1;
    }

    return 0;
}

static int read_map(FILE *const f, const unsigned w, const unsigned h,
    struct terrain_map *const map)
{
    for (unsigned j = 0; j < h; j++)
        for (unsigned i = 0; i < w; i++)
        {
            unsigned tile;

            if (read_hex(f, &tile))
                return -1;

            map->m[j][i].t = tile;
            map->m[j][i].obj = tile >> 8;
        }

    return 0;
}

int terrain_init(const char *const path, struct terrain_map *const map)
{
    int ret = -1;
    FILE *const f = fopen(path, "rb");
    unsigned w, h;

    *map = (const struct terrain_map){0};

    if (!f)
    {
        fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
        goto end;
    }
    else if (read_dimensions(f, &w, &h) || read_map(f, w, h, map))
        goto end;

    ret = 0;

end:
    if (f && fclose(f))
    {
        fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
        ret = -1;
    }

    return ret;
}
