// test-vector -- KEKS test structure
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <keks/atom.h>
#include <keks/enc.h>
#include <keks/enctai.h>

static size_t Off = 0;
static size_t Got = 0;

static void
adder(const bool ok)
{
    assert(ok);
    Off += Got;
}

int
main(void)
{
    const size_t len = (size_t)68 * (uint16_t)1024;
    unsigned char *buf = malloc(len);
    assert(buf != NULL);
    unsigned char *bin = malloc((uint32_t)1 << (uint8_t)17);
    assert(bin != NULL);

    {
        unsigned char *magic = (unsigned char *)"test-vector";
        adder(KEKSAtomMagicEncode(
            &Got, buf + Off, len - Off, magic, strlen((const char *)magic)));
    }

    adder(KEKSAtomMapEncode(&Got, buf + Off, len - Off)); // .

    {
        adder(KEKSAtomStrEncode(
            &Got, buf + Off, len - Off, (const unsigned char *)"ip", 2));
        memcpy(
            bin,
            (const unsigned char
                 *)"\x20\x01\x0d\xb8\x85\xa3\x08\xd3\x13\x19\x8a\x2e\x03\x70\x73\x48",
            16);
        adder(KEKSAtomHexletEncode(&Got, buf + Off, len - Off, bin));
    }
    adder(
        KEKSAtomStrEncode(&Got, buf + Off, len - Off, (const unsigned char *)"nil", 3));
    adder(KEKSAtomNILEncode(&Got, buf + Off, len - Off));

    adder(
        KEKSAtomStrEncode(&Got, buf + Off, len - Off, (const unsigned char *)"str", 3));
    adder(KEKSAtomMapEncode(&Got, buf + Off, len - Off)); // .str

    adder(
        KEKSAtomStrEncode(&Got, buf + Off, len - Off, (const unsigned char *)"bin", 3));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .str.bin
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, (const unsigned char *)"", 0));
    memset(bin, '0', 60);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 60));
    memset(bin, '1', 61);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 61));
    memset(bin, '2', 255);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 255));
    memset(bin, 'A', 61 + 255);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 61 + 255));
    memset(bin, 'B', 62 + 255);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 62 + 255));
    memset(bin, '3', 1024);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 1024));
    memset(bin, '4', 63 + 255 + 65535 + 1);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 63 + 255 + 65535 + 1));
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .str.bin

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"utf8", 4));
    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"привет мир", 19));
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .str

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"blob", 4));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .blob

    adder(KEKSAtomBlobEncode(&Got, buf + Off, len - Off, 12)); // .blob.0
    memset(bin, '5', 1);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 1));

    adder(KEKSAtomBlobEncode(&Got, buf + Off, len - Off, 12)); // .blob.1
    memset(bin, '6', 12);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 12));
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, NULL, 0));

    adder(KEKSAtomBlobEncode(&Got, buf + Off, len - Off, 12)); // .blob.2
    memset(bin, '7', 12);
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 12));
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, bin, 1));

    adder(KEKSAtomBlobEncode(&Got, buf + Off, len - Off, 5)); // .blob.3
    adder(KEKSAtomBinEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"12345", 5));
    adder(KEKSAtomBinEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"67890", 5));
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, (const unsigned char *)"-", 1));

    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .blob

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"bool", 4));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .bool
    adder(KEKSAtomBoolEncode(&Got, buf + Off, len - Off, true));
    adder(KEKSAtomBoolEncode(&Got, buf + Off, len - Off, false));
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .bool

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"ints", 4));
    adder(KEKSAtomMapEncode(&Got, buf + Off, len - Off)); // .ints

    adder(
        KEKSAtomStrEncode(&Got, buf + Off, len - Off, (const unsigned char *)"neg", 3));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .ints.neg
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -1));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -2));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -32));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -33));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -123));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -1234));
    adder(KEKSAtomSintEncode(&Got, buf + Off, len - Off, -12345678));
    {
        buf[Off] = KEKSAtomNint;
        Off++;
        size_t l = 11;
        memcpy(
            buf + Off,
            (const unsigned char *)"\x8A\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
            l);
        Off += l;
    }
    {
        buf[Off] = KEKSAtomNint;
        Off++;
        size_t l = 18;
        memcpy(
            buf + Off,
            (const unsigned char
                 *)"\x91\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
            l);
        Off += l;
    }
    {
        buf[Off] = KEKSAtomNint;
        Off++;
        size_t l = 481;
        memcpy(
            buf + Off,
            (const unsigned char
                 *)"\xBE\x00\xA1\xE5\xA4\x61\x28\x03\x41\x85\x6D\x4A\xD9\x08\xA6\x9E\xA5\xF3\xCC\xC1\x0C\x78\x82\x14\x2B\xB7\xD8\x01\xCC\x38\x0F\x26\xB6\xB4\xD6\x96\x32\x02\x4E\xE5\x21\xF8\xCF\xAF\xB4\x43\xD4\x9A\x2A\x3D\x0C\xC7\x3B\xB4\x75\x7E\x88\x2F\x53\x96\xED\x30\x2B\x41\x82\x10\xD0\xD4\x9D\x71\xBE\x86\xCA\x69\x9C\xF5\xEE\x3B\xD6\xD5\x7E\xD6\x58\xE6\x93\x16\x22\x96\x44\xBA\x65\x0C\x92\xD7\xF0\xD4\xDB\x29\xC3\xAD\x1D\xFA\x99\x79\x16\x6F\x4C\x6E\x79\x56\x1A\x58\xF8\xE2\xC6\x3D\x08\xDF\x4E\x22\x46\xED\x1F\x64\xD2\xD6\x13\xA1\x9D\x8C\x9A\x68\x70\xE6\x18\x8E\x2F\x3A\xD4\x0C\x03\x8F\xDA\x30\x45\x2F\x8D\xDF\xCD\x21\x2A\x6A\x97\x4B\xC2\x5E\xC6\xA0\x56\x4C\x66\xA7\xD2\x87\x50\xFF\x9D\xB4\x58\xB7\x44\x41\xE4\x9E\xE5\xE8\x2D\xBF\x49\x74\xD6\x45\x67\x8E\x0A\xD0\x31\xF9\x7A\xAB\xA8\x55\x45\x1E\xEF\x17\xA8\x9B\x42\x82\x1E\x53\x08\x16\xDD\x57\x93\xA8\x3B\x7A\x82\xE8\xED\xE8\x1E\x7F\x33\x95\x69\x1F\x76\x17\x84\xF8\xBC\x62\x79\x61\xCD\x40\x84\x5E\xE9\x08\xA4\x0B\x9D\x1F\x01\x92\x7B\x38\xEB\x1A\x7D\x4E\xFD\x60\xDB\x09\x44\xF7\xEC\x1B\x83\x2B\x7E\x6E\xB1\x83\x3F\x9A\x35\x15\x76\xAD\x5D\xE5\x71\xFA\xE8\x86\x5D\xA7\x51\x4F\x06\xB0\xFB\xF3\x8C\x1F\x2A\x85\x38\xF5\xD3\x8B\x4E\x18\x00\x1C\xCB\xB9\xDD\xCB\x48\x85\x30\xF6\x08\x6D\x14\x74\x4D\x8B\x56\x72\x16\x6E\x48\xE9\xEF\x93\x77\x25\x75\xDB\x66\xB6\xF2\x57\xC6\xFF\xAD\x6E\x2C\x29\x15\x10\xC5\xED\x02\xE1\xA8\xB2\x4B\x44\xEC\x1E\x2A\x91\x68\x62\x38\xE8\xDE\xFD\x18\xC0\x19\x98\x63\x4A\x50\x76\xA6\xB7\xF8\x5F\xC8\x1A\x1D\x61\xA1\x5B\x2C\x52\x8D\xFA\x08\x2C\xE3\xE3\xE2\xCA\x64\x9A\xC0\x48\x17\xEC\x5C\x12\x3E\x0B\x76\x1A\xB1\x03\xF7\x80\xC0\x14\xF0\x21\xBB\xEB\x7E\xA3\xB8\x6E\x0C\xA1\xC8\x33\xE3\x8E\xF5\xC8\x97\xA6\xD7\xE1\xF4\xA2\x39\x8C\x49\x0B\x3D\x65\xE2\xF4\x5C\x7F\xAE\x40\x2D\x1D\xF1\x69\x8B\x6F\xDD\xB1\x85\x48\x16\x64\x87\x1C\x26\x64\xBF\xD1\x68\x6B\x2B\x33\x72\x78\x3F\x18\x56\xF6\x24\x7A\x3F\x84\x37\xA2\x81\x8F\x68\xB7\xC4\xEA\x13\xA5\xF5\x7B\x73\xC7\x28\x70\xB6\x84\x04\x5F\x14",
            l);
        Off += l;
    }
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .ints.neg

    adder(
        KEKSAtomStrEncode(&Got, buf + Off, len - Off, (const unsigned char *)"pos", 3));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .ints.pos
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 0));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 1));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 31));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 32));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 123));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 1234));
    adder(KEKSAtomUintEncode(&Got, buf + Off, len - Off, 12345678));
    {
        buf[Off] = KEKSAtomPint;
        Off++;
        size_t l = 12;
        memcpy(
            buf + Off,
            (const unsigned char *)"\x8B\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
            l);
        Off += l;
    }
    {
        buf[Off] = KEKSAtomPint;
        Off++;
        size_t l = 18;
        memcpy(
            buf + Off,
            (const unsigned char
                 *)"\x91\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
            l);
        Off += l;
    }
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .ints.pos

    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .ints

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"uuid", 4));
    adder(KEKSAtomHexletEncode(
        &Got,
        buf + Off,
        len - Off,
        (const unsigned char
             *)"\x0e\x87\x5e\x3f\xd3\x85\x49\xeb\x87\xb4\xbe\x42\xd6\x41\xc3\x67"));

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"dates", 5));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .dates
    {
        struct timespec ts;
        ts.tv_sec = 1234567890;
        ts.tv_nsec = 0;
        assert(KEKSTimespecToTAI(&ts));
        unsigned char tai[12] = {0};
        assert(KEKSTimespecToTAI64(tai, &ts));
        adder(KEKSAtomTAI64Encode(&Got, buf + Off, len - Off, tai, 8));

        ts.tv_nsec = 456000;
        assert(KEKSTimespecToTAI64(tai, &ts));
        adder(KEKSAtomTAI64Encode(&Got, buf + Off, len - Off, tai, 12));

        {
            buf[Off] = KEKSAtomTAI64N;
            Off++;
            size_t l = 12;
            memcpy(
                buf + Off,
                (const unsigned char
                     *)"\x40\x00\x00\x00\x49\x96\x02\xF4\x00\x06\xF8\x55",
                l);
            Off += l;
        }
        {
            buf[Off] = KEKSAtomTAI64NA;
            Off++;
            size_t l = 16;
            memcpy(
                buf + Off,
                (const unsigned char
                     *)"\x40\x00\x00\x00\x49\x96\x02\xF4\x00\x06\xF8\x55\x07\x5B\xCD\x15",
                l);
            Off += l;
        }
    }
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .dates

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"floats", 6));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .floats
    {
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, (double)NAN));
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, (double)INFINITY));
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, -(double)INFINITY));
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, 0.0));
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, -45.25));
        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, 0.15625));
        adder(KEKSAtomFloatMEEncode(
            &Got, buf + Off, len - Off, -8687443681197687, -46)); // -123.456
        {
            buf[Off] = KEKSAtomFloat;
            Off++;
            size_t l = 18;
            memcpy(
                buf + Off,
                (const unsigned char
                     *)"\x0C\x8C\x27\xE4\x1B\x32\x46\xBE\xC9\xB1\x6E\x39\x81\x15\x0D\x82\x30\x38",
                l);
            Off += l;
        }
    }
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .floats

    adder(KEKSAtomStrEncode(
        &Got, buf + Off, len - Off, (const unsigned char *)"empties", 7));
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .empties
    adder(KEKSAtomListEncode(&Got, buf + Off, len - Off));
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off));
    adder(KEKSAtomMapEncode(&Got, buf + Off, len - Off));
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off));
    adder(KEKSAtomBlobEncode(&Got, buf + Off, len - Off, 123));
    adder(KEKSAtomBinEncode(&Got, buf + Off, len - Off, NULL, 0));
    memset(bin, '\x00', 16);
    adder(KEKSAtomHexletEncode(&Got, buf + Off, len - Off, bin));
    {
        buf[Off] = KEKSAtomTAI64;
        Off++;
        size_t l = 8;
        memcpy(buf + Off, (const unsigned char *)"\x00\x00\x00\x00\x00\x00\x00\x00", l);
        Off += l;
    }
    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .empties

    adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .

    free(bin);
    assert(write(STDOUT_FILENO, buf, Off) == (ssize_t)Off);
    free(buf);

    return EXIT_SUCCESS;
}
