/*
 * Deferrer Flag Library
 * An asynchronous execution helper that decouples execution contexts in nRF5 SDK event-driven systems.
 * It leverages app_timer to prevent reentrancy and state corruption by deferring callbacks to a
 * low-priority RTC interrupt context.
 */

#include "deferrer_flag.h"
#include "nrf_mesh_assert.h"
#include "utils.h"
#include "bitfield.h"
#include "app_timer.h"

#include "log.h"



/*** Static variables */

APP_TIMER_DEF(m_deferrer_timer_id);
static uint32_t m_flag_count;
static volatile uint32_t m_flags[BITFIELD_BLOCK_COUNT(DEFERRER_FLAG_COUNT)] = {0};
static void *m_flag_data[DEFERRER_FLAG_COUNT];
static deferrer_flag_cb_t m_flag_callbacks[DEFERRER_FLAG_COUNT];


/*** Static functions */

static void deferrer_timer_handler(void * p_context)
{
    for (uint32_t i = 0; i < m_flag_count; i++) {
        if (bitfield_get((uint32_t *)m_flags, i)) {
            bitfield_clear((uint32_t *)m_flags, i);
            m_flag_callbacks[i](m_flag_data[i]);
        }
    }
}



/*** Interface functions */

/* initializes the module's internal structures and allocates the underlying app_timer */
void deferrer_flag_init()
{
    m_flag_count = 0;

    APP_ERROR_CHECK(app_timer_create(&m_deferrer_timer_id,
                                     APP_TIMER_MODE_SINGLE_SHOT,
                                     deferrer_timer_handler));
}


/* registers a callback and context data, returning a unique flag handle */
deferrer_flag_t deferrer_flag_add(deferrer_flag_cb_t callback, void *p_data)
{
    NRF_MESH_ASSERT(callback != NULL);

    /* Check if we can still fit flags in the pool. */
    NRF_MESH_ASSERT(m_flag_count < DEFERRER_FLAG_COUNT);

    uint32_t flag = m_flag_count++;
    m_flag_callbacks[flag] = callback;
    m_flag_data[flag] = p_data;

    return flag;
}


/* schedules the registered callback for asynchronous execution on the next timer tick */
void deferrer_flag_set(deferrer_flag_t flag)
{
    NRF_MESH_ASSERT(flag < m_flag_count);
    bitfield_set((uint32_t *) m_flags, flag);
    APP_ERROR_CHECK(app_timer_start(m_deferrer_timer_id,
                                    APP_TIMER_MIN_TIMEOUT_TICKS,
                                    NULL));
}
