#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <time.h>

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

#include <keks/dectai.h>
#include <keks/enctai.h>
#include <keks/err.h>
#include <keks/frombe.h>
#include <keks/leapsecs.h>
#include <keks/tobe.h>

int
main(void)
{
    plan(NO_PLAN);

    unsigned char src[8] = {0};
    getrandom(src, 4, 0);
    struct timespec ts;
    ts.tv_sec = (time_t)(0xFFFFFFFFFFFFFFFF - keksFromBE(src + 0, 4) % 1000000);
    ts.tv_nsec = 0;
    unsigned char buf[16] = {0};
    ok(!KEKSTimespecToTAI64(buf, &ts), "too big secs");

    getrandom(src, 8, 0);
    ts.tv_sec = (time_t)(keksFromBE(src + 0, 8) % 0x4000000000000000);
    ok(KEKSTimespecToTAI64(buf, &ts), "secs ok");
    ok(memcmp(buf + 1 + 8, (const unsigned char *)"\x00\x00\x00\x00", 4) == 0,
       "ns empty");

    ts.tv_nsec = (long)(1000000000 + 1);
    ok(!KEKSTimespecToTAI64(buf, &ts), "too big ns");

    getrandom(src, 4, 0);
    keksToBE(src, 4, keksFromBE(src, 4) % 1000000000);
    ts.tv_nsec = (long)(keksFromBE(src, 4));
    ok(KEKSTimespecToTAI64(buf, &ts), "ns ok");
    ok(keksFromBE(buf + 8, 4) == keksFromBE(src, 4), "ns eq");

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

    ts.tv_sec = (time_t)(0xFFFFFFFFFFFFFFFF);
    ok(!KEKSTimespecToTAI(&ts), "to tai overflow, negative");

    ts.tv_sec = (time_t)(0x8000000000000000 - 1);
    ok(!KEKSTimespecToTAI(&ts), "to tai overflow, too big positive");

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

    ts.tv_nsec = 0;
    enum KEKSErr err = KEKSErrNo;
    bool failed = false;
    for (size_t i = 0; i < 1000; i++) {
        getrandom(src, 4, 0);
        ts.tv_sec = (time_t)(keksFromBE(src, 4));
        if (!KEKSTimespecToTAI(&ts)) {
            failed = false;
            break;
        }
        err = KEKSTimespecToUTC(&ts);
        if (err != KEKSErrNo) {
            failed = false;
            break;
        }
    }
    ok(!failed, "symmetric ok");

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

    ts.tv_sec = -1;
    ok(KEKSTimespecToUTC(&ts) == KEKSErrTAI64InPast, "to utc, negative");

    ts.tv_sec = 9;
    ok(KEKSTimespecToUTC(&ts) == KEKSErrTAI64InPast, "to utc, in past");

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

    for (size_t i = 0; i < KEKSLeapsecsN; i++) {
        ts.tv_sec = (time_t)(KEKSLeapsecs[i]);
        ok(KEKSTimespecToUTC(&ts) == KEKSErrTAI64IsLeap, "to utc, leap");
        ts.tv_sec++;
        ok(KEKSTimespecToUTC(&ts) == KEKSErrNo, "to utc, non-leap");
    }

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

    ok(KEKSTAI64ToTimespec(&ts, NULL, 13) == KEKSErrTAI64BadAsec, "too long");
    ok(KEKSTAI64ToTimespec(&ts, NULL, 11) == KEKSErrTAI64BadAsec, "bad len");
    ok(KEKSTAI64ToTimespec(&ts, NULL, 7) == KEKSErrTAI64BadAsec, "too short");

    ok(KEKSTAI64ToTimespec(
           &ts, (const unsigned char *)"\x3F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8) ==
           KEKSErrTAI64InPast,
       "in past");
    ok(KEKSTAI64ToTimespec(
           &ts, (const unsigned char *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8) ==
           KEKSErrTAI64TooBig,
       "too big");

    ok(KEKSTAI64ToTimespec(
           &ts,
           (const unsigned char *)"\x40\xFF\x00\x00\x00\x00\x00\x10\x3B\xA0\x00\x00",
           12) == KEKSErrTAI64BadNsec,
       "too big ns");

    return exit_status();
}
