#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/random.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
#include <tap.h>
#pragma clang diagnostic pop

#include <keks/atom.h>
#include <keks/enc.h>
#include <keks/err.h>
#include <keks/items.h>

int
main(void)
{
    plan(NO_PLAN);

    unsigned char magic[12] = {0};
    getrandom(magic, 12, 0);
    magic[11] = 123;

    size_t len = 0;
    unsigned char buf[16] = {0};
    size_t cap = 16 - 1;
    ok(!KEKSAtomMagicEncode(&len, buf, cap, magic, 13), "atom too long");
    ok(len == 0, "atom too long len");
    ok(!KEKSAtomMagicEncode(&len, buf, cap, magic, 12), "atom small buf");
    ok(len == 16, "atom small buf len");

    cap = 16;
    len = 0;
    ok(KEKSAtomMagicEncode(&len, buf, cap, magic, 12), "atom encode");
    ok(len == 16, "atom len");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "atom tag");
    ok(memcmp(buf + 4, magic, 12) == 0, "atom val");

    struct KEKSItems items;
    KEKSItemsInit(&items, 1 + 1);
    size_t off = 0;
    enum KEKSErr err = KEKSItemsParse(&items, &off, buf, cap - 1);
    ok(err == KEKSErrNotEnough, "parse not enough");
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrNo, "parse");
    ok(off == 16, "parse off");
    ok(items.reallocs == 0, "parse no reallocs");
    ok(items.len == 1, "parse len");
    ok(items.offsets[0] == 0, "parse offset");
    struct KEKSItem item = items.list[0];
    ok(item.next == 0, "atom next");
    ok(item.atom.typ == KEKSItemMagic, "atom typ");
    ok(item.atom.v.str.len == 12, "atom typ");
    ok(memcmp(item.atom.v.str.ptr, magic, 12) == 0, "atom val");

    cap = 0;
    off = 0;
    ok(!KEKSItemsEncode(&items, 0, &off, buf, cap), "items encode small buf");
    ok(off == 0, "items encode off");
    cap = sizeof buf;
    ok(KEKSItemsEncode(&items, 0, &off, buf, cap), "items encode");
    ok(off == 16, "items encode off");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "items encode tag");
    ok(memcmp(buf + 4, magic, 12) == 0, "items encode val");

    cap = sizeof buf;
    item.atom.v.str.len = 12 + 1;
    ok(!KEKSItemsEncode(&items, 0, &off, buf, cap), "items encode too long");
    ok(off == 16, "items encode too long off");

    // ------------------------ >8 ------------------------

    memset(magic + 7, 0, 5);

    cap = 16;
    len = 0;
    ok(KEKSAtomMagicEncode(&len, buf, cap, magic, 7), "shorter atom encode");
    ok(len == 16, "shorter atom len");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "shorter atom tag");
    ok(memcmp(buf + 4, magic, 7) == 0, "shorter atom val");
    ok(memcmp(buf + 4 + 7, (const unsigned char *)"\x00\x00\x00\x00\x00", 5) == 0,
       "shorter atom suffix");

    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrNo, "shorter parse");
    ok(off == 16, "parse off");
    ok(items.reallocs == 0, "shorter parse no reallocs");
    ok(items.len == 1, "shorter parse len");
    ok(items.offsets[0] == 0, "shorter parse offset");
    item = items.list[0];
    ok(item.next == 0, "shorter atom next");
    ok(item.atom.typ == KEKSItemMagic, "shorter atom typ");
    ok(item.atom.v.str.len == 7, "shorter atom typ");
    ok(memcmp(item.atom.v.str.ptr, magic, 7) == 0, "shorter atom val");

    cap = 0;
    off = 0;
    ok(!KEKSItemsEncode(&items, 0, &off, buf, cap), "shorter items encode small buf");
    ok(off == 0, "shorter items encode off");
    cap = sizeof buf;
    ok(KEKSItemsEncode(&items, 0, &off, buf, cap), "shorter items encode");
    ok(off == 16, "shorter items encode off");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "shorter items encode tag");
    ok(memcmp(buf + 4, magic, 7) == 0, "shorter items encode val");
    ok(memcmp(buf + 4 + 7, (const unsigned char *)"\x00\x00\x00\x00\x00", 5) == 0,
       "shorter atom suffix");

    // ------------------------ >8 ------------------------

    cap = 16;
    len = 0;
    ok(KEKSAtomMagicEncode(&len, buf, cap, NULL, 0), "atom encode empty");
    ok(len == 16, "atom empty len");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "atom empty tag");
    {
        bool allZeros = true;
        for (size_t i = 0; i < 12; i++) {
            if (buf[4 + i] != 0) {
                allZeros = false;
            }
        }
        ok(allZeros, "atom empty val");
    }

    // ------------------------ >8 ------------------------

    cap = 16;
    len = 0;
    ok(KEKSAtomMagicEncode(&len, buf, cap, NULL, 0), "atom encode empty");
    ok(len == 16, "atom empty len");
    ok(memcmp(buf, (const unsigned char *)"KEKS", 4) == 0, "atom empty tag");
    {
        bool allZeros = true;
        for (size_t i = 0; i < 12; i++) {
            if (buf[4 + i] != 0) {
                allZeros = false;
            }
        }
        ok(allZeros, "atom empty val");
    }

    // ------------------------ >8 ------------------------

    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    buf[0] = 'K';
    buf[1] = 'E';
    buf[2] = 'K';
    buf[3] = 's';
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrBadMagic, "bad magic KEKs");
    buf[0] = 'K';
    buf[1] = 'e';
    buf[2] = 'K';
    buf[3] = 'S';
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrBadMagic, "bad magic KeKS");

    return exit_status();
}
