// KEKS -- C99 KEKS encoder implementation
// 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 <stddef.h>
#include <stdint.h>
#include <string.h>

#include "atom.h"
#include "enc.h"
#include "fp.h"
#include "tobe.h"

bool
KEKSAtomEOCEncode(size_t *len, unsigned char *buf, const size_t cap)
{
    assert(len != NULL);
    (*len) = 1;
    if (cap < 1) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomEOC;
    return true;
}

bool
KEKSAtomNILEncode(size_t *len, unsigned char *buf, const size_t cap)
{
    assert(len != NULL);
    (*len) = 1;
    if (cap < 1) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomNIL;
    return true;
}

bool
KEKSAtomBoolEncode(size_t *len, unsigned char *buf, const size_t cap, const bool v)
{
    assert(len != NULL);
    (*len) = 1;
    if (cap < 1) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = v ? KEKSAtomTrue : KEKSAtomFalse;
    return true;
}

bool
KEKSAtomHexletEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char v[16])
{
    assert(len != NULL);
    (*len) = 1 + 16;
    if (cap < (1 + 16)) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomHexlet;
    memcpy(buf + 1, v, 16);
    return true;
}

bool
KEKSAtomMagicEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char *v,
    const size_t vlen)
{
    if (vlen > 12) {
        return false;
    }
    assert(len != NULL);
    (*len) = 1 + 15;
    if (cap < (1 + 15)) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = 'K';
    buf[1] = 'E';
    buf[2] = 'K';
    buf[3] = 'S';
    memcpy(buf + 4, v, vlen);
    memset(buf + 4 + vlen, 0, 12 - vlen);
    return true;
}

static bool
keksAtomIntEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64_t v)
{
    if (cap < 1) {
        return false;
    }
    assert(len != NULL);
    assert(buf != NULL);
    if (v == 0) {
        const bool ok =
            KEKSAtomBinEncode(len, buf + 1, cap - 1, (const unsigned char *)"", 0);
        (*len)++;
        return ok;
    }
    size_t l = 0;
    for (; l < 7; l++) {
        if (v < ((uint64_t)1 << ((l + 1) * 8))) {
            break;
        }
    }
    l++;
    unsigned char be[8] = {0};
    keksToBE(be, l, v);
    const bool ok = KEKSAtomBinEncode(len, buf + 1, cap - 1, be, l);
    (*len)++;
    return ok;
}

bool
KEKSAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64_t v)
{
    bool ok = keksAtomIntEncode(len, buf, cap, v);
    if (ok) {
        buf[0] = KEKSAtomPint;
    }
    return ok;
}

bool
KEKSAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, const int64_t v)
{
    if (v >= 0) {
        return KEKSAtomUintEncode(len, buf, cap, (uint64_t)v);
    }
    const uint64_t vp = (uint64_t)(-(v + 1));
    bool ok = keksAtomIntEncode(len, buf, cap, vp);
    if (ok) {
        buf[0] = KEKSAtomNint;
    }
    return ok;
}

bool
KEKSAtomFloatMEEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const int64_t m,
    const int e)
{
    if (cap < 1) {
        return false;
    }
    assert(len != NULL);
    assert(buf != NULL);
    (*len) = 1;
    buf[0] = KEKSAtomFloat;
    size_t mLen = 0;
    if (!KEKSAtomSintEncode(&mLen, buf + 1, cap - 1, m)) {
        return false;
    }
    (*len) += mLen;
    size_t eLen = 0;
    if (!KEKSAtomSintEncode(&eLen, buf + 1 + mLen, cap - 1 - mLen, (int64_t)e)) {
        return false;
    }
    (*len) += eLen;
    return true;
}

bool
KEKSAtomFloatEncode(size_t *len, unsigned char *buf, const size_t cap, const double v)
{
    if (cap < 1) {
        return false;
    }
    assert(len != NULL);
    assert(buf != NULL);
    int64_t m = 0;
    int e = 0;
    switch (fpclassify(v)) {
    case FP_INFINITE:
        (*len) = 1;
        buf[0] = (v > 0) ? KEKSAtomFloatPinf : KEKSAtomFloatNinf;
        return true;
    case FP_NAN:
        (*len) = 1;
        buf[0] = KEKSAtomFloatNaN;
        return true;
    case FP_NORMAL:
        if (!keksDoubleToME(&m, &e, v)) {
            return false;
        }
        break;
    case FP_SUBNORMAL:
        return false;
    case FP_ZERO:
        break;
    default:
        assert(false);
    }
    return KEKSAtomFloatMEEncode(len, buf, cap, m, e);
}

bool
KEKSAtomListEncode(size_t *len, unsigned char *buf, const size_t cap)
{
    assert(len != NULL);
    (*len) = 1;
    if (cap < 1) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomList;
    return true;
}

bool
KEKSAtomMapEncode(size_t *len, unsigned char *buf, const size_t cap)
{
    assert(len != NULL);
    (*len) = 1;
    if (cap < 1) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomMap;
    return true;
}

bool
KEKSAtomBlobEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const size_t chunkLen)
{
    assert(len != NULL);
    (*len) = 1 + 8;
    if (cap < 1 + 8) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = KEKSAtomBlob;
    keksToBE(buf + 1, 8, (uint64_t)chunkLen - 1);
    return true;
}

static bool
keksAtomStrEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char *src,
    const size_t srcLen,
    const bool utf8)
{
    unsigned char lVal = 0;
    size_t lLen = 0;
    unsigned char lBuf[8] = {0};
    if (srcLen >= (63 + 255 + 65535)) {
        lVal = 63;
        lLen = 8;
        keksToBE(lBuf, 8, (uint64_t)srcLen - (63 + 255 + 65535));
    } else if (srcLen >= (62 + 255)) {
        lVal = 62;
        lLen = 2;
        keksToBE(lBuf, 2, (uint64_t)srcLen - (62 + 255));
    } else if (srcLen >= 61) {
        lVal = 61;
        lLen = 1;
        lBuf[0] = (unsigned char)((srcLen - 61) & (uint8_t)0xFF);
    } else {
        lVal = (unsigned char)srcLen;
    }
    assert(len != NULL);
    (*len) = 1 + lLen + srcLen;
    if ((*len) <= srcLen) {
        (*len) = 0;
        return false;
    }
    if (cap < (*len)) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = (uint8_t)KEKSAtomStrings | lVal;
    if (utf8) {
        buf[0] |= (unsigned char)KEKSAtomIsUTF8;
    }
    memcpy(buf + 1, lBuf, lLen);
    memcpy(buf + 1 + lLen, src, srcLen);
    return true;
}

bool
KEKSAtomStrEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char *src,
    const size_t srcLen)
{
    return keksAtomStrEncode(len, buf, cap, src, srcLen, true);
}

bool
KEKSAtomBinEncode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char *src,
    const size_t srcLen)
{
    return keksAtomStrEncode(len, buf, cap, src, srcLen, false);
}

bool
KEKSAtomTAI64Encode(
    size_t *len,
    unsigned char *buf,
    const size_t cap,
    const unsigned char *src,
    const size_t srcLen)
{
    unsigned char tag = 0;
    switch (srcLen) {
    case 8:
        tag = KEKSAtomTAI64;
        break;
    case 12:
        tag = KEKSAtomTAI64N;
        break;
    case 16:
        tag = KEKSAtomTAI64NA;
        break;
    default:
        return false;
    }
    assert(len != NULL);
    (*len) = 1 + srcLen;
    if (cap < (*len)) {
        return false;
    }
    assert(buf != NULL);
    buf[0] = tag;
    memcpy(buf + 1, src, srcLen);
    return true;
}
