/*
 * Copyright 2022-2023 NXP
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <math.h>
#include "fsl_netc_timer.h"

#define NETC_TIMER_GLOBAL_BASE_OFFSET          (0x10000U) /*!< The Timer global register base address offset. */
#define NETC_TIMER_EXT_TRIG_FIFO_THRESHOLD_MAX (15U)

/*!
 * @brief Set the MSIX entry table
 *
 * This function is to set entry table in specified MSIX Table memory.
 *
 * @param handle  The Timer handle.
 * @param config  The Timer configuration.
 * @return status_t
 */
static status_t NETC_TimerMSIXSetEntryTable(netc_timer_handle_t *handle, const netc_timer_config_t *config)
{
    uint32_t *msixTable  = (uint32_t *)(uintptr_t)handle->hw.msixTable;
    uint32_t *entryTable = (uint32_t *)(uintptr_t)config->msixEntry;
    uint8_t msixNum;

    msixNum = (uint8_t)((handle->hw.base->TMR_CAPR & ENETC_PF_TMR_TMR_CAPR_NUM_MSIX_MASK) >>
                        ENETC_PF_TMR_TMR_CAPR_NUM_MSIX_SHIFT) +
              1U;

    /* Entry > 0, enable MSIX. */
    if (config->entryNum != 0U)
    {
        if (config->entryNum > msixNum)
        {
            return kStatus_InvalidArgument;
        }

        /* Use 32-bit access to set MSIX table. */
        msixTable[0] = entryTable[0];
        msixTable[1] = entryTable[1];
        msixTable[2] = entryTable[2];
        msixTable[3] = entryTable[3];

        /* Enable MSIX. */
        handle->hw.func->PCI_CFC_MSIX_MSG_CTL = ENETC_PCI_TYPE0_PCI_CFC_MSIX_MSG_CTL_MSIX_EN_MASK;
    }
    else
    {
        /* Disable MSIX. */
        handle->hw.func->PCI_CFC_MSIX_MSG_CTL = ENETC_PCI_TYPE0_PCI_CFC_MSIX_MSG_CTL_FUNC_MASK_MASK;
    }

    return kStatus_Success;
}

void NETC_TimerInitHandle(netc_timer_handle_t *handle)
{
    /* Initialize the handle. */
    handle->hw.func      = TMR_PCI_HDR_TYPE0;
    handle->hw.base      = TMR0_BASE;
    handle->hw.global    = (ENETC_GLOBAL_Type *)(TMR0_BASE_BASE + NETC_TIMER_GLOBAL_BASE_OFFSET);
    handle->hw.msixTable = (netc_msix_entry_t *)(FSL_FEATURE_NETC_MSIX_TABLE_BASE);
}

status_t NETC_TimerInit(netc_timer_handle_t *handle, const netc_timer_config_t *config)
{
    status_t result = kStatus_Success;
    uint32_t period = NETC_NANOSECOND_ONE_SECOND / config->refClkHz;

    /* Initialize the handle. */
    NETC_TimerInitHandle(handle);
    handle->entryNum     = config->entryNum;
    handle->timerFreq    = config->refClkHz;

    /* Reset this function */
    handle->hw.func->PCI_CFC_PCIE_DEV_CTL |= ENETC_PCI_TYPE0_PCI_CFC_PCIE_DEV_CTL_INIT_FLR_MASK;
    while ((handle->hw.func->PCI_CFC_PCIE_DEV_CTL & ENETC_PCI_TYPE0_PCI_CFC_PCIE_DEV_CTL_INIT_FLR_MASK) != 0U)
    {
    }

    /* Enable master bus and memory access for PCIe and MSI-X. */
    handle->hw.func->PCI_CFH_CMD |=
        ENETC_PCI_TYPE0_PCI_CFH_CMD_MEM_ACCESS_MASK | ENETC_PCI_TYPE0_PCI_CFH_CMD_BUS_MASTER_EN_MASK;

    result = NETC_TimerMSIXSetEntryTable(handle, config);
    if (result != kStatus_Success)
    {
        return result;
    }

    handle->hw.base->TMR_CTRL =
        ENETC_PF_TMR_TMR_CTRL_TCLK_PERIOD(period) | ENETC_PF_TMR_TMR_CTRL_COPH(config->clkOutputPhase) |
        ENETC_PF_TMR_TMR_CTRL_CIPH(config->clkInputPhase) | ENETC_PF_TMR_TMR_CTRL_TE(config->enableTimer) |
        ENETC_PF_TMR_TMR_CTRL_COMP_MODE(config->atomicMode) | ENETC_PF_TMR_TMR_CTRL_CK_SEL(config->clockSelect);

    NETC_TimerAdjustFreq(handle, config->defaultPpb);

    handle->hw.base->TMR_CNT_L = 0;
    handle->hw.base->TMR_CNT_H = 0;

    return kStatus_Success;
}

void NETC_TimerDeinit(netc_timer_handle_t *handle)
{
    /* Disable the 1588 timer. */
    NETC_TimerEnable(handle, false);

    /* Disable master bus and memory access for PCIe and MSI-X. */
    TMR_PCI_HDR_TYPE0->PCI_CFH_CMD &=
        (uint16_t)(~(ENETC_PCI_TYPE0_PCI_CFH_CMD_MEM_ACCESS_MASK | ENETC_PCI_TYPE0_PCI_CFH_CMD_BUS_MASTER_EN_MASK));

    (void)memset(handle, 0, sizeof(netc_timer_handle_t));
}

void NETC_TimerEnable(netc_timer_handle_t *handle, bool enable)
{
    if (enable)
    {
        handle->hw.base->TMR_CTRL |= ENETC_PF_TMR_TMR_CTRL_TE_MASK;
    }
    else
    {
        handle->hw.base->TMR_CTRL &= ~ENETC_PF_TMR_TMR_CTRL_TE_MASK;
    }
}

void NETC_TimerConfigureAlarm(netc_timer_handle_t *handle,
                              netc_timer_alarm_index_t alarmId,
                              const netc_timer_alarm_t *alarm)
{
    uint32_t control;

    control = handle->hw.base->TMR_CTRL;
    control &=
        (alarmId == kNETC_TimerAlarm1) ? (~ENETC_PF_TMR_TMR_CTRL_ALM1P_MASK) : (~ENETC_PF_TMR_TMR_CTRL_ALM2P_MASK);
    control |= (alarmId == kNETC_TimerAlarm1) ? (ENETC_PF_TMR_TMR_CTRL_ALM1P(alarm->polarity)) :
                                                ENETC_PF_TMR_TMR_CTRL_ALM2P(alarm->polarity);
    handle->hw.base->TMR_CTRL = control;

    control = handle->hw.base->TMR_ALARM_CTRL;
    control &= (alarmId == kNETC_TimerAlarm1) ?
                   (~(ENETC_PF_TMR_TMR_ALARM_CTRL_PG1_MASK | ENETC_PF_TMR_TMR_ALARM_CTRL_ALARM1_PW_MASK)) :
                   (~(ENETC_PF_TMR_TMR_ALARM_CTRL_PG2_MASK | ENETC_PF_TMR_TMR_ALARM_CTRL_ALARM2_PW_MASK));
    control |= (alarmId == kNETC_TimerAlarm1) ? (ENETC_PF_TMR_TMR_ALARM_CTRL_PG1(alarm->pulseGenSync) |
                                                 ENETC_PF_TMR_TMR_ALARM_CTRL_ALARM1_PW(alarm->pulseWidth)) :
                                                (ENETC_PF_TMR_TMR_ALARM_CTRL_PG2(alarm->pulseGenSync) |
                                                 ENETC_PF_TMR_TMR_ALARM_CTRL_ALARM2_PW(alarm->pulseWidth));
    handle->hw.base->TMR_ALARM_CTRL = control;

    /* Alarm interrupt mask configuration. */
    control = handle->hw.base->TMR_TEMASK;
    control &= (alarmId == kNETC_TimerAlarm1) ? (~ENETC_PF_TMR_TMR_TEMASK_ALM1EN_MASK) :
                                                (~ENETC_PF_TMR_TMR_TEMASK_ALM2EN_MASK);
    control |= (alarmId == kNETC_TimerAlarm1) ? ENETC_PF_TMR_TMR_TEMASK_ALM1EN(alarm->enableInterrupt) :
                                                ENETC_PF_TMR_TMR_TEMASK_ALM2EN(alarm->enableInterrupt);
    handle->hw.base->TMR_TEMASK = control;
}

void NETC_TimerStartAlarm(netc_timer_handle_t *handle, netc_timer_alarm_index_t alarmId, uint64_t nanosecond)
{
    if (alarmId == kNETC_TimerAlarm1)
    {
        handle->hw.base->TMR_ALARMM[0].TMR_ALARM_L = (uint32_t)nanosecond;
        handle->hw.base->TMR_ALARMM[0].TMR_ALARM_H = (uint32_t)(nanosecond >> 32U);
    }
    else
    {
        handle->hw.base->TMR_ALARMM[1].TMR_ALARM_L = (uint32_t)nanosecond;
        handle->hw.base->TMR_ALARMM[1].TMR_ALARM_H = (uint32_t)(nanosecond >> 32U);
    }
}

void NETC_TimerStopAlarm(netc_timer_handle_t *handle, netc_timer_alarm_index_t alarmId)
{
    if (alarmId == kNETC_TimerAlarm1)
    {
        handle->hw.base->TMR_ALARMM[0].TMR_ALARM_L = 0;
    }
    else
    {
        handle->hw.base->TMR_ALARMM[1].TMR_ALARM_L = 0;
    }
}

void NETC_TimerConfigureFIPER(netc_timer_handle_t *handle, const netc_timer_fiper_config_t *config)
{
    assert((config->prescale % 2U) == 0U);

    handle->hw.base->TMR_CTRL &=
        ~(ENETC_PF_TMR_TMR_CTRL_FS_MASK | ENETC_PF_TMR_TMR_CTRL_PP1L_MASK | ENETC_PF_TMR_TMR_CTRL_PP2L_MASK);
    handle->hw.base->TMR_CTRL |= ENETC_PF_TMR_TMR_CTRL_FS(config->startCondition) |
                                 ENETC_PF_TMR_TMR_CTRL_PP1L(config->fiper1Loopback) |
                                 ENETC_PF_TMR_TMR_CTRL_PP2L(config->fiper1Loopback);
    handle->hw.base->TMR_PRSC = config->prescale;
}

void NETC_TimerStartFIPER(netc_timer_handle_t *handle,
                          netc_timer_fiper_index_t fiperId,
                          const netc_timer_fiper_t *fiper)
{
    uint32_t control;
    uint32_t clear;

    if (fiperId == kNETC_TimerFiper1)
    {
        /* Disbale the FIPER first */
        handle->hw.base->TMR_FIPER_CTRL |= ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER1_DIS_MASK;

        handle->hw.base->TMR_TEMASK &= ~ENETC_PF_TMR_TMR_TEMASK_PP1EN_MASK;
        handle->hw.base->TMR_TEMASK |= ENETC_PF_TMR_TMR_TEMASK_PP1EN(fiper->enableInterrupt);
        clear   = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER1_PW_MASK | ENETC_PF_TMR_TMR_FIPER_CTRL_PG1_MASK;
        control = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER1_PW(fiper->pulseWidth) |
                  ENETC_PF_TMR_TMR_FIPER_CTRL_PG1(fiper->pulseGenSync);
    }
    else if (fiperId == kNETC_TimerFiper2)
    {
        /* Disbale the FIPER first */
        handle->hw.base->TMR_FIPER_CTRL |= ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER2_DIS_MASK;

        handle->hw.base->TMR_TEMASK &= ~ENETC_PF_TMR_TMR_TEMASK_PP2EN_MASK;
        handle->hw.base->TMR_TEMASK |= ENETC_PF_TMR_TMR_TEMASK_PP2EN(fiper->enableInterrupt);
        clear   = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER2_PW_MASK | ENETC_PF_TMR_TMR_FIPER_CTRL_PG2_MASK;
        control = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER2_PW(fiper->pulseWidth) |
                  ENETC_PF_TMR_TMR_FIPER_CTRL_PG2(fiper->pulseGenSync);
    }
    else
    {
        /* Disbale the FIPER first */
        handle->hw.base->TMR_FIPER_CTRL |= ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER3_DIS_MASK;

        handle->hw.base->TMR_TEMASK &= ~ENETC_PF_TMR_TMR_TEMASK_PP3EN_MASK;
        handle->hw.base->TMR_TEMASK |= ENETC_PF_TMR_TMR_TEMASK_PP3EN(fiper->enableInterrupt);
        clear   = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER3_PW_MASK | ENETC_PF_TMR_TMR_FIPER_CTRL_PG3_MASK;
        control = ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER3_PW(fiper->pulseWidth) |
                  ENETC_PF_TMR_TMR_FIPER_CTRL_PG3(fiper->pulseGenSync);
    }

    handle->hw.base->TMR_FIPER[fiperId] = fiper->pulsePeriod;
    handle->hw.base->TMR_FIPER_CTRL &= ~clear;
    handle->hw.base->TMR_FIPER_CTRL |= control;

    handle->hw.base->TMR_FIPER_CTRL &=
        ~((uint32_t)ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER1_DIS_MASK << ((uint32_t)fiperId * 8U));
}

void NETC_TimerStopFIPER(netc_timer_handle_t *handle, netc_timer_fiper_index_t fiperId)
{
    handle->hw.base->TMR_FIPER_CTRL |=
        ((uint32_t)ENETC_PF_TMR_TMR_FIPER_CTRL_FIPER1_DIS_MASK << ((uint32_t)fiperId * 8U));
}

status_t NETC_TimerSetTsFifoThreshold(netc_timer_handle_t *handle, uint8_t threshold)
{
    status_t result = kStatus_Fail;

    if (threshold <= NETC_TIMER_EXT_TRIG_FIFO_THRESHOLD_MAX)
    {
        handle->hw.base->TMR_ECTRL = threshold;
        result                     = kStatus_Success;
    }

    return result;
}

void NETC_TimerConfigureExtPulseTrig(netc_timer_handle_t *handle,
                                     netc_timer_exttrig_index_t extTrigId,
                                     const netc_timer_ext_trig_t *extTrig)
{
    uint32_t control;
    uint32_t clear;

    clear   = (extTrigId == kNETC_TimerExtTrig1) ? ENETC_PF_TMR_TMR_CTRL_ETEP1_MASK : ENETC_PF_TMR_TMR_CTRL_ETEP2_MASK;
    control = (extTrigId == kNETC_TimerExtTrig1) ? ENETC_PF_TMR_TMR_CTRL_ETEP1(extTrig->polarity) :
                                                   ENETC_PF_TMR_TMR_CTRL_ETEP2(extTrig->polarity);
    handle->hw.base->TMR_CTRL &= ~clear;
    handle->hw.base->TMR_CTRL |= control;

    clear = (extTrigId == kNETC_TimerExtTrig1) ?
                (ENETC_PF_TMR_TMR_TEMASK_ETS1EN_MASK | ENETC_PF_TMR_TMR_TEMASK_ETS1_THREN_MASK |
                 ENETC_PF_TMR_TMR_TEMASK_ETS1_OVEN_MASK) :
                (ENETC_PF_TMR_TMR_TEMASK_ETS2EN_MASK | ENETC_PF_TMR_TMR_TEMASK_ETS2_THREN_MASK |
                 ENETC_PF_TMR_TMR_TEMASK_ETS2_OVEN_MASK);
    control = (extTrigId == kNETC_TimerExtTrig1) ?
                  (ENETC_PF_TMR_TMR_TEMASK_ETS1EN(extTrig->enableTsAvailInterrupt) |
                   ENETC_PF_TMR_TMR_TEMASK_ETS1_THREN(extTrig->enableFifoThresholdHitInterrupt) |
                   ENETC_PF_TMR_TMR_TEMASK_ETS1_OVEN(extTrig->enableFifoOverflowInterrupt)) :
                  (ENETC_PF_TMR_TMR_TEMASK_ETS2EN(extTrig->enableTsAvailInterrupt) |
                   ENETC_PF_TMR_TMR_TEMASK_ETS2_THREN(extTrig->enableFifoThresholdHitInterrupt) |
                   ENETC_PF_TMR_TMR_TEMASK_ETS2_OVEN(extTrig->enableFifoOverflowInterrupt));
    handle->hw.base->TMR_TEMASK &= ~clear;
    handle->hw.base->TMR_TEMASK |= control;
}

/*!
 * @brief Check the timestamp available status in FIFO
 *
 * @param base NETC timer peripheral base address.
 * @param tsFifo The FIFO number to read timestamp out.
 * @return True: FIFO is empty, False: FIFO is not empty
 */
static bool NETC_TimerCheckTsFifoEmpty(netc_timer_handle_t *handle, netc_timer_exttrig_index_t extTrigId)
{
    return ((handle->hw.base->TMR_STAT & (ENETC_PF_TMR_TMR_STAT_ETS1_VLD_MASK << (uint32_t)extTrigId)) == 0U);
}

status_t NETC_TimerReadExtPulseCaptureTime(netc_timer_handle_t *handle,
                                           netc_timer_exttrig_index_t extTrigId,
                                           uint64_t *nanosecond)
{
    status_t result = kStatus_Fail;
    uint32_t timeLow, timeHigh;

    if (!NETC_TimerCheckTsFifoEmpty(handle, extTrigId))
    {
        if (extTrigId == kNETC_TimerExtTrig1)
        {
            timeLow  = handle->hw.base->TMR_ETTSN[0].TMR_ETTS_L;
            timeHigh = handle->hw.base->TMR_ETTSN[0].TMR_ETTS_H;
        }
        else
        {
            timeLow  = handle->hw.base->TMR_ETTSN[1].TMR_ETTS_L;
            timeHigh  = handle->hw.base->TMR_ETTSN[1].TMR_ETTS_H;
        }
        *nanosecond = ((uint64_t)timeHigh << 32U) + timeLow;
        result      = kStatus_Success;
    }
    return result;
}

static void __NETC_TimerGetCurrentTime(ENETC_PF_TMR_Type *base, uint64_t *nanosecond)
{
    uint32_t timeLow, timeHigh[2];

    timeHigh[0] = base->TMR_CUR_TIME_H;
    do {
        timeHigh[1] = timeHigh[0];
        timeLow     = base->TMR_CUR_TIME_L;
        timeHigh[0] = base->TMR_CUR_TIME_H;
    } while (timeHigh[0] != timeHigh[1]);

    *nanosecond = ((uint64_t)timeHigh[0] << 32U) + timeLow;
}

void NETC_TimerGetTime(ENETC_PF_TMR_Type *base, uint64_t *nanosecond)
{
    if ((base->TMR_CTRL & ENETC_PF_TMR_TMR_CTRL_TE_MASK) != 0U) {
        __NETC_TimerGetCurrentTime(base, nanosecond);
    } else {
        uint32_t timeLow, timeHigh[2];

        timeHigh[0] = base->TMR_DEF_CNT_H;
        do {
            timeHigh[1] = timeHigh[0];
            timeLow     = base->TMR_DEF_CNT_L;
            timeHigh[0] = base->TMR_DEF_CNT_H;
        } while (timeHigh[0] != timeHigh[1]);

        *nanosecond = ((uint64_t)timeHigh[0] << 32U) + timeLow;
    }
}

void NETC_TimerGetCurrentTime(netc_timer_handle_t *handle, uint64_t *nanosecond)
{
    __NETC_TimerGetCurrentTime(handle->hw.base, nanosecond);
}

void NETC_TimerGetFreeRunningTime(netc_timer_handle_t *handle, uint64_t *nanosecond)
{
    uint32_t timeLow, timeHigh[2];

    timeHigh[0] = handle->hw.base->TMR_FRT_H;
    do {
        timeHigh[1] = timeHigh[0];
        timeLow     = handle->hw.base->TMR_FRT_L;
        timeHigh[0] = handle->hw.base->TMR_FRT_H;
    } while (timeHigh[0] != timeHigh[1]);

    *nanosecond = ((uint64_t)timeHigh[0] << 32U) + timeLow;
}

void NETC_TimerAddOffset(netc_timer_handle_t *handle, int64_t nanosecond)
{
    uint64_t offset;

    if (nanosecond >= 0)
    {
        offset = (uint64_t)nanosecond;
    }
    else
    {
        offset = UINT64_MAX - (uint64_t)(-nanosecond);
    }

    /* Calculate a new offset value based on the current one. */
    offset += ((uint64_t)handle->hw.base->TMROFF_H << 32U) + handle->hw.base->TMROFF_L;
    /* Update the latest offset. */
    handle->hw.base->TMROFF_L = (uint32_t)offset;
    handle->hw.base->TMROFF_H = (uint32_t)(offset >> 32U);
}

void NETC_TimerAdjustFreq(netc_timer_handle_t *handle, int32_t ppb)
{
    int64_t offset = 1000000000LL + ppb;
    uint64_t addend;

    /* period (in ns) is given by: 10^9 / freq */
    /* ppb is applied to period: period' = period * (1 + ppb / 10^9) */
    /* addend is the fractional part of the period (in ns) scaled by 2^32, */
    /* which is equivalent to scaling period by 2^32, and then taking the lower 32bits */
    /* addend' = 10^9 / freq * (1 + ppp / 10^9) * 2^32 = (2^32 * (10^9 + ppb)) / freq */
    addend = (((uint64_t)1ULL << 32) * (uint64_t)offset) / handle->timerFreq;

    handle->hw.base->TMR_ADD = (uint32_t)addend;
}

void NETC_TimerGetFrtSrtTime(netc_timer_handle_t *handle, uint64_t *frt, uint64_t *srt)
{
    uint32_t tsLow, tsHigh;

    /* Read TMR_FRT_L captures all 64b of FRT_H/L and 64b of SRT_H/L for an atomic read of all 4 registers. */
    tsLow  = handle->hw.base->TMR_FRT_L;
    tsHigh = handle->hw.base->TMR_FRT_H;
    *frt   = ((uint64_t)tsHigh << 32U) + tsLow;

    tsLow  = handle->hw.base->TMR_SRT_L;
    tsHigh = handle->hw.base->TMR_SRT_H;
    *srt   = ((uint64_t)tsHigh << 32U) + tsLow;
}

status_t NETC_TimerMsixSetGlobalMask(netc_timer_handle_t *handle, bool mask)
{
    if (mask)
    {
        handle->hw.func->PCI_CFC_MSIX_MSG_CTL |= ENETC_PCI_TYPE0_PCI_CFC_MSIX_MSG_CTL_FUNC_MASK_MASK;
    }
    else
    {
        handle->hw.func->PCI_CFC_MSIX_MSG_CTL &= ~(uint16_t)ENETC_VF_PCI_TYPE0_PCI_CFC_MSIX_MSG_CTL_FUNC_MASK_MASK;
    }

    return kStatus_Success;
}

status_t NETC_TimerMsixSetEntryMask(netc_timer_handle_t *handle, uint8_t entryIdx, bool mask)
{
    status_t result;

    if (entryIdx < handle->entryNum)
    {
        handle->hw.msixTable[entryIdx].control = (uint32_t)mask;
        result                                 = kStatus_Success;
    }
    else
    {
        result = kStatus_Fail;
    }

    return result;
}

status_t NETC_TimerMsixGetPendingStatus(netc_timer_handle_t *handle, uint8_t pbaIdx, uint64_t *status)
{
    status_t result = kStatus_Success;
    bool funcEnable;

    if (handle->entryNum == 0U)
    {
        return kStatus_Fail;
    }

    /* Check MSIX enable status. */
    funcEnable = ((handle->hw.func->PCI_CFC_MSIX_MSG_CTL & ENETC_PCI_TYPE0_PCI_CFC_MSIX_MSG_CTL_MSIX_EN_MASK) != 0U);

    if (funcEnable)
    {
        if (pbaIdx <= ((handle->entryNum - 1U) / 64U))
        {
            *status = *(uint64_t *)((uintptr_t)handle->hw.msixTable + NETC_MSIX_TABLE_PBA_OFFSET + 8U * pbaIdx);
            result  = kStatus_Success;
        }
        else
        {
            result = kStatus_InvalidArgument;
        }
    }
    else
    {
        *status = 0;
        result  = kStatus_Fail;
    }

    return result;
}
