// 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 <stddef.h>
#include <stdint.h>
#include <time.h>

#include "dectai.h"
#include "err.h"
#include "frombe.h"
#include "leapsecs.h"

enum KEKSErr
KEKSTAI64ToTimespec(struct timespec *ts, const unsigned char *buf, const size_t len)
{
    switch (len) {
    case 8:
    case 12:
        break;
    case 16:
    default:
        return KEKSErrTAI64BadAsec;
    }
    assert(buf != NULL);
    int64_t v = 0;
    {
        uint64_t uv = keksFromBE(buf, 8);
        if (((uint64_t)1 << ((sizeof(time_t) * 8) - 1)) < uv) {
            return KEKSErrTAI64TooBig;
        }
        v = (int64_t)uv;
    }
    v -= 0x4000000000000000;
    if (v <= 0) {
        return KEKSErrTAI64InPast;
    }
    assert(ts != NULL);
    ts->tv_sec = (time_t)v;
    if (len > 8) {
        uint32_t n = (uint32_t)keksFromBE(buf + 8, 4);
        if (n >= 1000000000) {
            return KEKSErrTAI64BadNsec;
        }
        ts->tv_nsec = n;
    }
    return KEKSErrNo;
}

enum KEKSErr
KEKSTimespecToUTC(struct timespec *ts)
{
    assert(ts != NULL);
    if (ts->tv_sec < 0) {
        return KEKSErrTAI64InPast;
    }
    uint64_t v = (uint64_t)(ts->tv_sec);
    enum KEKSErr err = KEKSErrNo;
    {
        uint64_t diff = 10;
        for (size_t i = 0; i < KEKSLeapsecsN; i++) {
            if (v < KEKSLeapsecs[i]) {
                break;
            }
            diff++;
            if (v == KEKSLeapsecs[i]) {
                err = KEKSErrTAI64IsLeap;
                break;
            }
        }
        if (diff > v) {
            return KEKSErrTAI64InPast;
        }
        v -= diff;
    }
    ts->tv_sec = (time_t)v;
    return err;
}
