/*
 *  Copyright (c) 2023, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file implements the OpenThread platform abstraction for the alarm.
 *
 */

#include <assert.h>
#include <openthread-core-config.h>
#include <openthread-system.h>
#include <stdbool.h>
#include <stdint.h>
#include <openthread/platform/alarm-micro.h>
#include <openthread/platform/alarm-milli.h>
#include <openthread/platform/diag.h>
#include "common/debug.hpp"
#include "common/logging.hpp"

#include "alarm.h"
#include "platform-efr32.h"
#include "utils/code_utils.h"

#include "rail.h"
#include "sl_core.h"
#include "sl_multipan.h"
#include "sl_sleeptimer.h"

#ifndef TESTING
#define STATIC static
#else
#define STATIC
#endif

// timer data for handling wrapping
typedef struct wrap_timer_data wrap_timer_data_t;
struct wrap_timer_data
{
    uint16_t overflow_counter;
    uint16_t overflow_max;
};

// millisecond timer (sleeptimer)
static sl_sleeptimer_timer_handle_t sl_handle[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];

// microsecond timer (RAIL timer)
static RAIL_MultiTimer_t rail_timer[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];

// forward declare generic alarm handle
struct AlarmHandle;

// function pointers for timer operations
typedef void (*timerStartFunc)(struct AlarmHandle *, uint32_t);
typedef uint32_t (*timerMaxFunc)(void);
typedef uint32_t (*timerGetTimeFunc)(void);
typedef void (*timerStopFunc)(struct AlarmHandle *);

// alarm handle structure
typedef struct AlarmHandle AlarmHandle;
struct AlarmHandle
{
    otInstance       *mThreadInstance;
    void             *mTimerHandle;
    timerStartFunc    mTimerStart;
    timerMaxFunc      mTimerGetMax;
    timerGetTimeFunc  mTimerGetNow;
    timerStopFunc     mTimerStop;
    wrap_timer_data_t mWrapData;
    volatile bool     mIsRunning;
    volatile int      mFiredCount;
};

// callback function for the stack
typedef void (*StackAlarmCallback)(otInstance *);

// alarm handle instances
static AlarmHandle sMsAlarmHandles[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];
static AlarmHandle sUsAlarmHandles[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];

static uint64_t sPendingTimeMs[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM];

// millisecond-alarm callback
STATIC void msAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData);

// microsecond-alarm callback
STATIC void usAlarmCallback(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg);

// timer specific operations
static void     msTimerStart(AlarmHandle *aMsAlarm, uint32_t aAlarmDuration);
static uint32_t msTimerGetMaxTime(void);
static uint32_t msTimerGetNow(void);
static void     msTimerStop(AlarmHandle *aMsAlarm);

static void     usTimerStart(AlarmHandle *aUsAlarm, uint32_t aAlarmDuration);
static uint32_t usTimerGetMaxTime(void);
static uint32_t usTimerGetNow(void);
static void     usTimerStop(AlarmHandle *aUsAlarm);

// overflow utility functions
static inline bool isAlarmOverflowInProgress(AlarmHandle *aAlarm);

// common timer operations
static void FireAlarm(AlarmHandle *aAlarm);
static void StartAlarmAt(AlarmHandle *aAlarm, uint32_t aT0, uint32_t aDt);
static void StopActiveAlarm(AlarmHandle *aAlarm);
static void AlarmCallback(AlarmHandle *aAlarm);

// pending time utility functions
static inline uint64_t GetPendingTime(otInstance *aInstance);
static inline void     SetPendingTime(otInstance *aInstance, uint64_t aPendingTime);

// alarm handle utility functions
static inline AlarmHandle *GetAlarmHandle(AlarmHandle *aHandleList, otInstance *aInstance);
static AlarmHandle        *GetFirstFiredAlarm(AlarmHandle *aAlarm);
static AlarmHandle        *GetNextFiredAlarm(AlarmHandle *aAlarm, const AlarmHandle *aAlarmEnd);
static inline bool         HasAnyAlarmFired(void);
static inline uint32_t     SetAlarmWrappedDuration(AlarmHandle *aAlarm, uint64_t aRemainingTime);

static void msTimerStart(AlarmHandle *aMsAlarm, uint32_t aAlarmDuration)
{
    OT_ASSERT(aMsAlarm != NULL);
    OT_ASSERT(aMsAlarm->mIsRunning == false);
    sl_status_t status = sl_sleeptimer_start_timer_ms(aMsAlarm->mTimerHandle,
                                                      aAlarmDuration,
                                                      msAlarmCallback,
                                                      (void *)aMsAlarm,
                                                      0,
                                                      SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
#if OPENTHREAD_CONFIG_ASSERT_ENABLE
    OT_ASSERT(status == SL_STATUS_OK);
#else
    OT_UNUSED_VARIABLE(status);
#endif
}

static uint32_t msTimerGetMaxTime(void)
{
    return sl_sleeptimer_get_max_ms32_conversion();
}

static uint32_t msTimerGetNow(void)
{
    uint64_t    ticks;
    uint64_t    now;
    sl_status_t status;

    ticks  = sl_sleeptimer_get_tick_count64();
    status = sl_sleeptimer_tick64_to_ms(ticks, &now);

#if OPENTHREAD_CONFIG_ASSERT_ENABLE
    OT_ASSERT(status == SL_STATUS_OK);
#else
    OT_UNUSED_VARIABLE(status);
#endif

    return (uint32_t)now;
}

static void msTimerStop(AlarmHandle *aMsAlarm)
{
    OT_ASSERT(aMsAlarm != NULL);
    sl_sleeptimer_stop_timer((sl_sleeptimer_timer_handle_t *)aMsAlarm->mTimerHandle);
}

static void usTimerStart(AlarmHandle *aUsAlarm, uint32_t aAlarmDuration)
{
    OT_ASSERT(aUsAlarm != NULL);
    OT_ASSERT(aUsAlarm->mIsRunning == false);
    RAIL_Status_t status =
        RAIL_SetMultiTimer(aUsAlarm->mTimerHandle, aAlarmDuration, RAIL_TIME_DELAY, usAlarmCallback, (void *)aUsAlarm);
#if OPENTHREAD_CONFIG_ASSERT_ENABLE
    OT_ASSERT(status == RAIL_STATUS_NO_ERROR);
#else
    OT_UNUSED_VARIABLE(status);
#endif
}

static uint32_t usTimerGetMaxTime(void)
{
    return UINT32_MAX;
}

static uint32_t usTimerGetNow(void)
{
    return RAIL_GetTime();
}

static void usTimerStop(AlarmHandle *aUsAlarm)
{
    OT_ASSERT(aUsAlarm != NULL);
    RAIL_CancelMultiTimer((struct RAIL_MultiTimer *)aUsAlarm->mTimerHandle);
}

static inline bool isAlarmOverflowInProgress(AlarmHandle *aAlarm)
{
    OT_ASSERT(aAlarm != NULL);
    return aAlarm->mWrapData.overflow_counter < aAlarm->mWrapData.overflow_max;
}

static void FireAlarm(AlarmHandle *aAlarm)
{
    OT_ASSERT(aAlarm != NULL);
    aAlarm->mFiredCount++;
    StopActiveAlarm(aAlarm);

    otSysEventSignalPending();
}

static void ProcessAlarm(AlarmHandle *aAlarm, StackAlarmCallback aCallback)
{
    OT_ASSERT(aAlarm != NULL);
    otInstance *instance = aAlarm->mThreadInstance;

    CORE_DECLARE_IRQ_STATE;
    CORE_ENTER_ATOMIC();

    int numCallbacks    = aAlarm->mFiredCount;
    aAlarm->mFiredCount = 0;

    CORE_EXIT_ATOMIC();

    while (numCallbacks > 0)
    {
        numCallbacks--;
        aCallback(instance);
    }
}

static inline uint32_t SetAlarmWrappedDuration(AlarmHandle *aAlarm, uint64_t aRemainingTime)
{
    OT_ASSERT(aAlarm != NULL);
    uint64_t          initial_wrap_time = aRemainingTime;
    wrap_timer_data_t wrapData          = {0};

    if (initial_wrap_time > aAlarm->mTimerGetMax())
    {
        initial_wrap_time %= aAlarm->mTimerGetMax();
        wrapData.overflow_max     = (uint16_t)(aRemainingTime / aAlarm->mTimerGetMax());
        wrapData.overflow_counter = 0;
    }
    aAlarm->mWrapData = wrapData;
    return (uint32_t)initial_wrap_time;
}

static void StartAlarmAt(AlarmHandle *aAlarm, uint32_t aT0, uint32_t aDt)
{
    OT_ASSERT(aAlarm != NULL);
    otEXPECT(sl_ot_rtos_task_can_access_pal());
    StopActiveAlarm(aAlarm);

    uint64_t requested_time = (uint64_t)aT0 + (uint64_t)aDt;
    int64_t  remaining      = (int64_t)requested_time - (int64_t)aAlarm->mTimerGetNow();

    if (remaining <= 0)
    {
        FireAlarm(aAlarm);
    }
    else
    {
        aAlarm->mTimerStart(aAlarm, SetAlarmWrappedDuration(aAlarm, (uint64_t)remaining));
        aAlarm->mIsRunning = true;
    }

exit:
    return;
}

static void StopActiveAlarm(AlarmHandle *aAlarm)
{
    OT_ASSERT(aAlarm != NULL);
    otEXPECT(aAlarm->mIsRunning);
    otEXPECT(sl_ot_rtos_task_can_access_pal());
    aAlarm->mTimerStop(aAlarm);
    aAlarm->mIsRunning = false;
exit:
    return;
}

static void AlarmCallback(AlarmHandle *aAlarm)
{
    OT_ASSERT(aAlarm != NULL);
    if (isAlarmOverflowInProgress(aAlarm))
    {
        aAlarm->mIsRunning = false;
        aAlarm->mWrapData.overflow_counter++;
        aAlarm->mTimerStart(aAlarm, aAlarm->mTimerGetMax());
    }
    else
    {
        FireAlarm(aAlarm);
    }
}

static inline uint64_t GetPendingTime(otInstance *aInstance)
{
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
    efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance);
    OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    return sPendingTimeMs[currentIid - 1];
#else
    OT_UNUSED_VARIABLE(aInstance);
    return sPendingTimeMs[0];
#endif
}

static inline void SetPendingTime(otInstance *aInstance, uint64_t aPendingTime)
{
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
    efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance);
    OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    sPendingTimeMs[currentIid - 1] = aPendingTime;
#else
    OT_UNUSED_VARIABLE(aInstance);
    sPendingTimeMs[0] = aPendingTime;
#endif
}

static inline AlarmHandle *GetAlarmHandle(AlarmHandle *aHandleList, otInstance *aInstance)
{
    AlarmHandle *alarmHandle = aHandleList;
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
    efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance);
    OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
    alarmHandle = aHandleList + ((uint8_t)(currentIid - 1));
#else
    OT_UNUSED_VARIABLE(aInstance);
#endif
    return alarmHandle;
}

static AlarmHandle *GetFirstFiredAlarm(AlarmHandle *aHandleList)
{
    return GetNextFiredAlarm(aHandleList, aHandleList + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM);
}

static AlarmHandle *GetNextFiredAlarm(AlarmHandle *aAlarm, const AlarmHandle *aAlarmEnd)
{
    AlarmHandle *nextAlarm = aAlarm;

    while (nextAlarm && nextAlarm->mFiredCount == 0 && nextAlarm < aAlarmEnd)
    {
        nextAlarm++;
    }

    if (nextAlarm >= aAlarmEnd)
    {
        nextAlarm = NULL;
    }

    return nextAlarm;
}

static inline bool HasAnyAlarmFired(void)
{
    return (GetFirstFiredAlarm(sMsAlarmHandles) != NULL) || (GetFirstFiredAlarm(sUsAlarmHandles) != NULL);
}

// millisecond-alarm callback
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL)
STATIC void msAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData)
{
    OT_UNUSED_VARIABLE(aHandle);

    AlarmCallback((AlarmHandle *)aData);
}

// microsecond-alarm callback
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL)
STATIC void usAlarmCallback(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg)
{
    OT_UNUSED_VARIABLE(tmr);
    OT_UNUSED_VARIABLE(expectedTimeOfEvent);

    AlarmCallback((AlarmHandle *)cbArg);
}

void efr32AlarmInit(void)
{
    memset(&sl_handle, 0, sizeof sl_handle);
    memset(&rail_timer, 0, sizeof rail_timer);

    for (uint8_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++)
    {
        sPendingTimeMs[i] = 0;

        sMsAlarmHandles[i].mThreadInstance = NULL;
        sMsAlarmHandles[i].mTimerHandle    = &sl_handle[i];
        sMsAlarmHandles[i].mTimerStart     = msTimerStart;
        sMsAlarmHandles[i].mTimerGetMax    = msTimerGetMaxTime;
        sMsAlarmHandles[i].mTimerGetNow    = msTimerGetNow;
        sMsAlarmHandles[i].mTimerStop      = msTimerStop;
        sMsAlarmHandles[i].mIsRunning      = false;
        sMsAlarmHandles[i].mFiredCount     = 0;
        memset(&sMsAlarmHandles[i].mWrapData, 0, sizeof(wrap_timer_data_t));

        sUsAlarmHandles[i].mThreadInstance = NULL;
        sUsAlarmHandles[i].mTimerHandle    = &rail_timer[i];
        sUsAlarmHandles[i].mTimerStart     = usTimerStart;
        sUsAlarmHandles[i].mTimerGetMax    = usTimerGetMaxTime;
        sUsAlarmHandles[i].mTimerGetNow    = usTimerGetNow;
        sUsAlarmHandles[i].mTimerStop      = usTimerStop;
        sUsAlarmHandles[i].mIsRunning      = false;
        sUsAlarmHandles[i].mFiredCount     = 0;
        memset(&sUsAlarmHandles[i].mWrapData, 0, sizeof(wrap_timer_data_t));
    }
}

void efr32AlarmProcess(otInstance *aInstance)
{
    OT_UNUSED_VARIABLE(aInstance);

    otEXPECT(HasAnyAlarmFired());

    AlarmHandle       *msAlarm    = GetFirstFiredAlarm(sMsAlarmHandles);
    const AlarmHandle *msAlarmEnd = sMsAlarmHandles + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM;

    StackAlarmCallback alarmCb;

    while (msAlarm != NULL)
    {
#if OPENTHREAD_CONFIG_DIAG_ENABLE
        if (otPlatDiagModeGet())
        {
            alarmCb = otPlatDiagAlarmFired;
        }
        else
#endif
        {
            alarmCb = otPlatAlarmMilliFired;
        }
        ProcessAlarm(msAlarm, alarmCb);
        msAlarm = GetNextFiredAlarm(msAlarm, msAlarmEnd);
    }
#if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
    AlarmHandle       *usAlarm    = GetFirstFiredAlarm(sUsAlarmHandles);
    const AlarmHandle *usAlarmEnd = sUsAlarmHandles + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM;

    while (usAlarm != NULL)
    {
        alarmCb = otPlatAlarmMicroFired;
        ProcessAlarm(usAlarm, alarmCb);
        usAlarm = GetNextFiredAlarm(usAlarm, usAlarmEnd);
    }
#endif
exit:
    return;
}

uint64_t efr32AlarmPendingTime(otInstance *aInstance)
{
    uint64_t remaining = GetPendingTime(aInstance);
    uint32_t now       = otPlatAlarmMilliGetNow();

    otEXPECT_ACTION(GetAlarmHandle(sMsAlarmHandles, aInstance)->mIsRunning, remaining = 0);

    if (remaining > now)
    {
        remaining -= (uint64_t)now;
    }

exit:
    return remaining;
}

bool efr32AlarmIsRunning(otInstance *aInstance)
{
    return (otInstanceIsInitialized(aInstance) ? GetAlarmHandle(sMsAlarmHandles, aInstance)->mIsRunning : false);
}

#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
// Callback to determine if the system can sleep after an interrupt has fired
bool efr32AlarmIsReady(void)
{
    return HasAnyAlarmFired();
}
#endif // SL_CATALOG_POWER_MANAGER_PRESENT

uint32_t otPlatAlarmMilliGetNow(void)
{
    return sMsAlarmHandles[0].mTimerGetNow();
}

uint16_t otPlatTimeGetXtalAccuracy(void)
{
#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
    // For sleepies, we need to account for the low-frequency crystal
    // accuracy when they go to sleep.  Accounting for that as well,
    // for the worst case.
    if (efr32AllowSleepCallback())
    {
        return SL_OPENTHREAD_HFXO_ACCURACY + SL_OPENTHREAD_LFXO_ACCURACY;
    }
#endif
    return SL_OPENTHREAD_HFXO_ACCURACY;
}

void otPlatAlarmMilliStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt)
{
    AlarmHandle *alarm = GetAlarmHandle(sMsAlarmHandles, aInstance);
    if (alarm->mThreadInstance == NULL)
    {
        alarm->mThreadInstance = aInstance;
    }

    SetPendingTime(aInstance, (uint64_t)aT0 + (uint64_t)aDt);
    CORE_ATOMIC_SECTION(StartAlarmAt(alarm, aT0, aDt);)
}

void otPlatAlarmMilliStop(otInstance *aInstance)
{
    CORE_ATOMIC_SECTION(StopActiveAlarm(GetAlarmHandle(sMsAlarmHandles, aInstance));)
}

uint32_t otPlatAlarmMicroGetNow(void)
{
    return sUsAlarmHandles[0].mTimerGetNow();
}

// Note: This function should be called at least once per wrap
// period for the wrap-around logic to work below
uint64_t otPlatTimeGet(void)
{
    static uint32_t timerWraps   = 0U;
    static uint32_t prev32TimeUs = 0U;
    uint32_t        now32TimeUs;
    uint64_t        now64TimeUs;
    CORE_DECLARE_IRQ_STATE;
    CORE_ENTER_CRITICAL();
    now32TimeUs = RAIL_GetTime();
    if (now32TimeUs < prev32TimeUs)
    {
        timerWraps += 1U;
    }
    prev32TimeUs = now32TimeUs;
    now64TimeUs  = ((uint64_t)timerWraps << 32) + now32TimeUs;
    CORE_EXIT_CRITICAL();
    return now64TimeUs;
}

void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt)
{
    AlarmHandle *alarm = GetAlarmHandle(sUsAlarmHandles, aInstance);
    if (alarm->mThreadInstance == NULL)
    {
        alarm->mThreadInstance = aInstance;
    }

    CORE_ATOMIC_SECTION(StartAlarmAt(alarm, aT0, aDt);)
}

void otPlatAlarmMicroStop(otInstance *aInstance)
{
    CORE_ATOMIC_SECTION(StopActiveAlarm(GetAlarmHandle(sUsAlarmHandles, aInstance));)
}
