// 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 "dec.h"
#include "err.h"
#include "fp.h"
#include "frombe.h"
#include "utf8.h"

static bool
atomIsInt(struct KEKSAtom *atom)
{
    if ((atom->typ == KEKSItemPint) || (atom->typ == KEKSItemNint)) {
        return true;
    }
    if (atom->typ != KEKSItemRaw) {
        return false;
    }
    if (atom->v.str.len == 0) {
        return false;
    }
    return (atom->v.str.ptr[0] == KEKSAtomPint) || (atom->v.str.ptr[0] == KEKSAtomNint);
}

enum KEKSErr
KEKSAtomDecode( // NOLINT(misc-no-recursion)
    size_t *got,
    struct KEKSAtom *atom,
    const unsigned char *buf,
    const size_t len)
{
    assert(atom != NULL);
    assert(got != NULL);
    atom->v.str.ptr = NULL;
    atom->v.str.len = 0;
    (*got) = 1;
    if (len < 1) {
        return KEKSErrNotEnough;
    }
    assert(buf != NULL);
    unsigned char tag = buf[0];

    if ((tag & (uint8_t)KEKSAtomStrings) > 0) {
        atom->typ = ((tag & (uint8_t)KEKSAtomIsUTF8) == 0) ? KEKSItemBin : KEKSItemStr;
        uint64_t l = tag & (uint8_t)63;
        size_t ll = 0;
        switch (l) {
        case 61:
            ll = 1;
            break;
        case 62:
            ll = 2;
            l += 255;
            break;
        case 63:
            ll = 8;
            l += 255 + 65535;
            break;
        default:
            break;
        }
        if (ll != 0) {
            (*got) += ll;
            if (len < (*got)) {
                return KEKSErrNotEnough;
            }
            uint64_t ul = keksFromBE(buf + 1, ll);
            if (ul > (SIZE_MAX - (63 + 255 + 65535))) {
                return KEKSErrLenTooBig;
            }
            l += ul;
        }
        const size_t ls = (size_t)l;
        (*got) += ls;
        if ((*got) <= ls) {
            return KEKSErrLenTooBig;
        }
        if (len < (*got)) {
            return KEKSErrNotEnough;
        }
        atom->v.str.len = ls;
        atom->v.str.ptr = buf + 1 + ll;
        if (atom->typ == KEKSItemStr) {
            if (l > PTRDIFF_MAX) {
                return KEKSErrLenTooBig;
            }
            size_t cpl = 0;
            uint32_t cp = KEKSUTF8InvalidCp;
            for (size_t n = 0; n < l; n += cpl) {
                cpl = KEKSUTF8CpDecode(&cp, atom->v.str.ptr + (ptrdiff_t)n, l - n);
                if (cp == KEKSUTF8InvalidCp) {
                    return KEKSErrBadUTF8;
                }
            }
        }
        return KEKSErrNo;
    }

    switch (tag) {
    case KEKSAtomEOC:
        atom->typ = KEKSItemEOC;
        break;
    case KEKSAtomNIL:
        atom->typ = KEKSItemNIL;
        break;
    case KEKSAtomFalse:
        atom->typ = KEKSItemFalse;
        break;
    case KEKSAtomTrue:
        atom->typ = KEKSItemTrue;
        break;
    case KEKSAtomHexlet:
        atom->typ = KEKSItemHexlet;
        (*got) += 16;
        if (len < (*got)) {
            return KEKSErrNotEnough;
        }
        atom->v.str.ptr = buf + 1;
        atom->v.str.len = 16;
        break;
    case KEKSAtomMagic:
        atom->typ = KEKSItemMagic;
        (*got) += 15;
        if (len < (*got)) {
            return KEKSErrNotEnough;
        }
        if (memcmp(buf, (const unsigned char *)"KEKS", 4) != 0) {
            return KEKSErrBadMagic;
        }
        atom->v.str.ptr = buf + 4;
        {
            size_t l = 0;
            for (; l < 12; l++) {
                if (buf[4 + l] == 0) {
                    break;
                }
            }
            atom->v.str.len = l;
        }
        break;

    case KEKSAtomList:
        atom->typ = KEKSItemList;
        break;
    case KEKSAtomMap:
        atom->typ = KEKSItemMap;
        break;
    case KEKSAtomBlob: {
        atom->typ = KEKSItemBlob;
        (*got) += 8;
        if (len < (*got)) {
            return KEKSErrNotEnough;
        }
        const uint64_t chunkLen = keksFromBE(buf + 1, 8);
        if (chunkLen > (SIZE_MAX - 1)) {
            return KEKSErrLenTooBig;
        }
        atom->v.blob.chunkLen = (size_t)chunkLen + 1;
        break;
    }

    case KEKSAtomPint:
    case KEKSAtomNint: {
        atom->typ = (tag == KEKSAtomPint) ? KEKSItemPint : KEKSItemNint;
        size_t binGot = 0;
        struct KEKSAtom bin;
        memset(&bin, 0, sizeof(struct KEKSAtom));
        if ((buf[1] & (unsigned char)KEKSAtomStrings) == 0) {
            return KEKSErrIntNonBin;
        }
        enum KEKSErr err = KEKSAtomDecode(&binGot, &bin, buf + 1, len - 1);
        if (err != KEKSErrNo) {
            return err;
        }
        (*got) += binGot;
        if (bin.typ != KEKSItemBin) {
            return KEKSErrIntNonBin;
        }
        if (bin.v.str.len == 0) {
            if (atom->typ == KEKSItemPint) {
                atom->v.pint = 0;
            } else {
                atom->v.nint = -1;
            }
            return KEKSErrNo;
        }
        if (bin.v.str.ptr[0] == 0) {
            return KEKSErrIntNonMinimal;
        }
        if (bin.v.str.len > 8) {
            atom->typ = KEKSItemRaw;
            atom->v.str.len = binGot + 1;
            atom->v.str.ptr = buf;
            return KEKSErrNo;
        }
        atom->v.pint = keksFromBE(bin.v.str.ptr, bin.v.str.len);
        if (atom->typ == KEKSItemNint) {
            if (atom->v.pint >= ((uint64_t)1 << (uint8_t)63)) {
                atom->typ = KEKSItemRaw;
                atom->v.str.len = binGot + 1;
                atom->v.str.ptr = buf;
            } else {
                atom->v.nint = -1 - (int64_t)(atom->v.pint);
            }
        }
        return KEKSErrNo;
    }

    case KEKSAtomFloatNaN:
        atom->typ = KEKSItemFloat;
        atom->v.fp = (double)NAN;
        break;
    case KEKSAtomFloatPinf:
        atom->typ = KEKSItemFloat;
        atom->v.fp = (double)INFINITY;
        break;
    case KEKSAtomFloatNinf:
        atom->typ = KEKSItemFloat;
        atom->v.fp = -((double)INFINITY);
        break;
    case KEKSAtomFloat: {
        atom->typ = KEKSItemFloat;
        size_t mGot = 0;
        struct KEKSAtom m;
        memset(&m, 0, sizeof(struct KEKSAtom));
        enum KEKSErr err = KEKSAtomDecode(&mGot, &m, buf + 1, len - 1);
        if (err != KEKSErrNo) {
            return err;
        }
        (*got) += mGot;
        if (!atomIsInt(&m)) {
            return KEKSErrFloatNonInt;
        }

        size_t eGot = 0;
        struct KEKSAtom e;
        memset(&e, 0, sizeof(struct KEKSAtom));
        err = KEKSAtomDecode(&eGot, &e, buf + 1 + mGot, len - 1 - mGot);
        if (err != KEKSErrNo) {
            return err;
        }
        (*got) += eGot;
        if (!atomIsInt(&e)) {
            return KEKSErrFloatNonInt;
        }

        if (((m.typ == KEKSItemPint) && ((m.v.pint >> 63) > 0)) ||
            ((e.typ == KEKSItemPint) && ((e.v.pint >> 63) > 0))) {
            goto FloatRaw;
        }

        if (keksMEToDouble(
                &(atom->v.fp),
                (m.typ == KEKSItemPint) ? (int64_t)m.v.pint : m.v.nint,
                (e.typ == KEKSItemPint) ? (int64_t)e.v.pint : e.v.nint)) {
            break;
        }

    FloatRaw:
        atom->typ = KEKSItemRaw;
        atom->v.str.len = 1 + mGot + eGot;
        atom->v.str.ptr = buf;
        break;
    }

    case KEKSAtomTAI64:
    case KEKSAtomTAI64N:
    case KEKSAtomTAI64NA: {
        size_t l = 0;
        switch (tag) {
        case KEKSAtomTAI64:
            l = 8;
            break;
        case KEKSAtomTAI64N:
            l = 12;
            break;
        case KEKSAtomTAI64NA:
            l = 16;
            break;
        default:
            assert(false);
        }
        atom->typ = KEKSItemTAI64;
        (*got) += l;
        if (len < (*got)) {
            return KEKSErrNotEnough;
        }
        atom->v.str.len = l;
        atom->v.str.ptr = buf + 1;
        uint64_t v = keksFromBE(buf + 1, 8);
        if (v > ((uint64_t)1 << (uint8_t)63)) {
            return KEKSErrTAI64TooBig;
        }
        if (l > 8) {
            v = keksFromBE(buf + 1 + 8, 4);
            if ((l == 12) && (v == 0)) {
                return KEKSErrTAI64NonMinimal;
            }
            if (v > 999999999) {
                return KEKSErrTAI64BadNsec;
            }
        }
        if (l > 12) {
            v = keksFromBE(buf + 1 + 8 + 4, 4);
            if (v == 0) {
                return KEKSErrTAI64NonMinimal;
            }
            if (v > 999999999) {
                return KEKSErrTAI64BadAsec;
            }
        }
        break;
    }
    default:
        return KEKSErrUnknownType;
    }

    return KEKSErrNo;
}
