// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file time_source.cpp
 * @brief Multi-source time fallback matrix - implementation.
 *
 * A fixed BSS table of sources queried in ascending priority. See time_source.h.
 */

#include "time_source.h"
#include "shared_primitives/http_date.h" // detws_http_date() - the shared IMF-fixdate formatter

#if DETWS_ENABLE_TIME_SOURCE

namespace
{
struct Src
{
    const char *name;
    TimeSourceFn fn;
    uint8_t priority;
    bool used;
};

// All time-source state, owned by one instance (internal linkage): the priority-ordered
// source table and the last-selected source name, grouped so it is one named owner.
struct TimeSourceCtx
{
    Src sources[DETWS_TIME_SOURCE_MAX];
    const char *active = nullptr;
};
TimeSourceCtx s_ts;
} // namespace

bool detws_time_source_add(const char *name, uint8_t priority, TimeSourceFn fn)
{
    if (!fn)
        return false;
    for (int i = 0; i < DETWS_TIME_SOURCE_MAX; i++)
    {
        if (!s_ts.sources[i].used)
        {
            s_ts.sources[i].name = name;
            s_ts.sources[i].fn = fn;
            s_ts.sources[i].priority = priority;
            s_ts.sources[i].used = true;
            return true;
        }
    }
    return false; // table full
}

uint32_t detws_time_now(void)
{
    s_ts.active = nullptr;

    // Query sources in ascending priority (lowest value first); stop at the first
    // that returns a nonzero epoch. A selection scan avoids sorting and, more
    // importantly, does not invoke lower-priority callbacks once a higher-priority
    // source has answered (reading an RTC / GPS can be costly).
    uint32_t queried = 0; // bitmask of sources already tried
    for (int pass = 0; pass < DETWS_TIME_SOURCE_MAX; pass++)
    {
        int sel = -1;
        for (int i = 0; i < DETWS_TIME_SOURCE_MAX; i++)
        {
            if (!s_ts.sources[i].used || (queried & (1u << i)))
                continue;
            if (sel < 0 || s_ts.sources[i].priority < s_ts.sources[sel].priority)
                sel = i;
        }
        if (sel < 0)
            break; // no more sources

        queried |= (1u << sel);
        uint32_t epoch = s_ts.sources[sel].fn();
        if (epoch != 0)
        {
            s_ts.active = s_ts.sources[sel].name;
            return epoch;
        }
    }
    return 0;
}

const char *detws_time_source_active(void)
{
    return s_ts.active;
}

void detws_time_source_reset(void)
{
    for (int i = 0; i < DETWS_TIME_SOURCE_MAX; i++)
        s_ts.sources[i] = Src{};
    s_ts.active = nullptr;
}

#else // DETWS_ENABLE_TIME_SOURCE == 0 -> no-op stubs

bool detws_time_source_add(const char *, uint8_t, TimeSourceFn)
{
    return false;
}
uint32_t detws_time_now(void)
{
    return 0;
}
const char *detws_time_source_active(void)
{
    return nullptr;
}
void detws_time_source_reset(void)
{
}

#endif // DETWS_ENABLE_TIME_SOURCE

// The current best time (detws_time_now, any registered NTP / GPS / RTC / ... source) as an RFC 7231
// IMF-fixdate. Defined unconditionally: with the registry disabled detws_time_now() is 0, so this
// returns 0 (no Date). Lets the HTTP Date header draw from whatever time source is enabled.
size_t detws_time_http_date(char *out, size_t out_cap)
{
    return detws_http_date((time_t)detws_time_now(), out, out_cap);
}
