#include <stdlib.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/frombe.h>
#include <keks/items.h>
#include <keks/tobe.h>

int
main(void)
{
    plan(NO_PLAN);

    unsigned char src[16] = {0};
    getrandom(src, 16, 0);
    size_t len = 0;
    unsigned char buf[16 + 1] = {0};

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

    size_t cap = 16;
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 8 - 1), "64: bad src len");
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 8 + 1), "64: bad src len");
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 12 - 1), "64N: bad src len");
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 12 + 1), "64N: bad src len");
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 16 - 1), "64NA: bad src len");
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 16 + 1), "64NA: bad src len");

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

    cap = 8;
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 8), "64: encode small buf");
    ok(len == 8 + 1, "64: encode small buf len");
    cap = 12;
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 12), "64N: encode small buf");
    ok(len == 12 + 1, "64: encode small buf len");
    cap = 16;
    ok(!KEKSAtomTAI64Encode(&len, buf, cap, src, 16), "64NA: encode small buf");
    ok(len == 16 + 1, "64: encode small buf len");

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

    cap = 8 + 1;
    ok(KEKSAtomTAI64Encode(&len, buf, cap, src, 8), "64: encode");
    ok(len == 8 + 1, "64: encode len");
    ok(buf[0] == 0x18, "64: tag");
    ok(memcmp(buf + 1, src, 8) == 0, "64: val");

    cap = 12 + 1;
    ok(KEKSAtomTAI64Encode(&len, buf, cap, src, 12), "64N: encode");
    ok(len == 12 + 1, "64N: encode len");
    ok(buf[0] == 0x19, "64N: tag");
    ok(memcmp(buf + 1, src, 12) == 0, "64N: val");

    cap = 16 + 1;
    ok(KEKSAtomTAI64Encode(&len, buf, cap, src, 16), "64NA: encode");
    ok(len == 16 + 1, "64NA: encode len");
    ok(buf[0] == 0x1A, "64NA: tag");
    ok(memcmp(buf + 1, src, 16) == 0, "64NA: val");

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

    cap = 8 + 1;
    src[0] &= (unsigned char)0x7F;
    KEKSAtomTAI64Encode(&len, buf, cap, src, 8);
    struct KEKSItems items;
    KEKSItemsInit(&items, 1 + 1);
    size_t off = 0;
    enum KEKSErr err = KEKSItemsParse(&items, &off, buf, cap - 1);
    ok(err == KEKSErrNotEnough, "64: parse not enough");
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrNo, "64: parse");
    ok(off == 8 + 1, "64: parse off");
    ok(items.reallocs == 0, "64: parse no reallocs");
    ok(items.len == 1, "64: parse len");
    ok(items.offsets[0] == 0, "64: parse offset");
    struct KEKSItem item = items.list[0];
    ok(item.next == 0, "64: atom next");
    ok(item.atom.typ == KEKSItemTAI64, "64: atom typ");
    ok(item.atom.v.str.len == 8, "64: atom len");
    ok(memcmp(item.atom.v.str.ptr, src, 8) == 0, "64: atom val");
    src[0] |= (unsigned char)0x80;
    KEKSAtomTAI64Encode(&len, buf, cap, src, 8);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64TooBig, "64: highest bit set");

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

    cap = 12 + 1;
    src[0] &= (unsigned char)0x7F;
    memset(src + 8, 0, 4);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 12);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64NonMinimal, "64N: parse non-minimal");

    getrandom(src + 8, 4, 0);
    keksToBE(src + 8, 4, keksFromBE(src + 8, 4) % 1000000000);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 12);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap - 1);
    ok(err == KEKSErrNotEnough, "64N: parse not enough");
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrNo, "64N: parse");
    ok(off == 12 + 1, "64N: parse off");
    ok(items.reallocs == 0, "64N: parse no reallocs");
    ok(items.len == 1, "64N: parse len");
    ok(items.offsets[0] == 0, "64N: parse offset");
    item = items.list[0];
    ok(item.next == 0, "64N: atom next");
    ok(item.atom.typ == KEKSItemTAI64, "64N: atom typ");
    ok(item.atom.v.str.len == 12, "64N: atom len");
    ok(memcmp(item.atom.v.str.ptr, src, 12) == 0, "64N: atom val");

    keksToBE(src + 8, 4, keksFromBE(src + 8, 4) + 1000000000);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 12);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64BadNsec, "64N: bad N");

    src[0] |= (unsigned char)0x80;
    KEKSAtomTAI64Encode(&len, buf, cap, src, 12);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64TooBig, "64N: highest bit set");

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

    cap = 16 + 1;
    src[0] &= (unsigned char)0x7F;
    keksToBE(src + 8, 4, keksFromBE(src + 8, 4) % 1000000000);
    memset(src + 12, 0, 4);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 16);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64NonMinimal, "64NA: parse non-minimal");

    memset(src + 8, 0, 4);
    getrandom(src + 12, 4, 0);
    keksToBE(src + 12, 4, keksFromBE(src + 12, 4) % 1000000000);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 16);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap - 1);
    ok(err == KEKSErrNotEnough, "64NA: parse not enough");
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrNo, "64NA: parse");
    ok(off == 16 + 1, "64NA: parse off");
    ok(items.reallocs == 0, "64NA: parse no reallocs");
    ok(items.len == 1, "64NA: parse len");
    ok(items.offsets[0] == 0, "64NA: parse offset");
    item = items.list[0];
    ok(item.next == 0, "64NA: atom next");
    ok(item.atom.typ == KEKSItemTAI64, "64NA: atom typ");
    ok(item.atom.v.str.len == 16, "64NA: atom len");
    ok(memcmp(item.atom.v.str.ptr, src, 16) == 0, "64NA: atom val");

    keksToBE(src + 12, 4, keksFromBE(src + 12, 4) + 1000000000);
    KEKSAtomTAI64Encode(&len, buf, cap, src, 16);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64BadAsec, "64NA: bad A");

    src[0] |= (unsigned char)0x80;
    KEKSAtomTAI64Encode(&len, buf, cap, src, 16);
    KEKSItemsFree(&items);
    KEKSItemsInit(&items, 1 + 1);
    off = 0;
    err = KEKSItemsParse(&items, &off, buf, cap);
    ok(err == KEKSErrTAI64TooBig, "64NA: highest bit set");

    return exit_status();
}
