/**
 * @file	   hal_timer.c
 *
 * @copyright  Nehemis SARL reserves all rights even in the event of industrial
 *             property rights. We reserve all rights of disposal such as
 *             copying and passing on to third parties.
 *
 * @brief	   HAL layer for the TIMER module.
 *
 */

/******************************************************************************
 * Include Header Files
 ******************************************************************************/
#include "map_hal.h"
#include "hal_timer.h"

/* CubeMX */
#include "tim.h"

/******************************************************************************
 * Macro constant declarations
 ******************************************************************************/
#define HAL_TIMER_SEC_TO_MSEC        1000000uLL      /**< Number of microseconds within a second */

/******************************************************************************
 * Type declarations
 ******************************************************************************/
typedef struct
{
    uint64 timerPeriod_u64;
    uint64 downCounter_u64;
} HalTimerInstance_tst;

/******************************************************************************
 * Module Global Variable Declarations
 ******************************************************************************/
static HalTimerInstance_tst instance_gast[MAP_HAL_TIMER_NUMBER] = {0};
static void (*halTimerCallbacks[MAP_HAL_TIMER_NUMBER])(void);


/******************************************************************************
 * Static Function Declarations
 ******************************************************************************/
static uint32 HalTimerComputeTimerPeriod(MapHalTimerModule_en timerModule_en, uint64 * timerPeriod_u64);

/******************************************************************************
 * Extern Function Definition
 ******************************************************************************/
uint32 HalTimerInit(void)
{
    uint32 error_u32 = NMS_ERR_NONE;

    for(uint8 i_u8 = 0u; i_u8 < MAP_HAL_TIMER_NUMBER; i_u8++)
    {
        uint64 timerPeriod_u64 = 0uLL;
        error_u32 = HalTimerComputeTimerPeriod((MapHalTimerModule_en)(i_u8), &timerPeriod_u64);

        if (error_u32 == NMS_ERR_NONE)
        {
            instance_gast[i_u8].timerPeriod_u64 = timerPeriod_u64;
            instance_gast[i_u8].downCounter_u64 = 0uLL;
        }
    }

    return error_u32;
}


uint32_t HalTimerGetCounter(MapHalTimerModule_en timerModule_en)
{
    TIM_HandleTypeDef *targetModule_pst = NULL;

    /* Get the timer handle using MapHalTimerModule */
    if (MapHalTimerModule(timerModule_en, &targetModule_pst) != NMS_ERR_NONE || targetModule_pst == NULL)
    {
        return 0;
    }

    return (uint32)(targetModule_pst->Instance->CNT);
}


void HalTimerSetCounter(MapHalTimerModule_en timerModule_en, uint32 counter_u32)
{
    TIM_HandleTypeDef *targetModule_pst = NULL;

    if (MapHalTimerModule(timerModule_en, &targetModule_pst) != NMS_ERR_NONE || targetModule_pst == NULL)
    {
        return;
    }

    targetModule_pst->Instance->CNT = counter_u32;
}


void HalTimerSetCompare(MapHalTimerModule_en timerModule_en, uint32 channel_u32, uint32 compareValue_32)
{
    TIM_HandleTypeDef *targetModule_pst = NULL;

    if (MapHalTimerModule(timerModule_en, &targetModule_pst) != NMS_ERR_NONE || targetModule_pst == NULL)
    {
        /* Handle the error (e.g., log it or return early) */
        return;
    }
    switch (channel_u32)
    {
        case TIM_CHANNEL_1:
            targetModule_pst->Instance->CCR1 = compareValue_32;  break;

        case TIM_CHANNEL_2:
            targetModule_pst->Instance->CCR2 = compareValue_32;  break;

        case TIM_CHANNEL_3:
            targetModule_pst->Instance->CCR3 = compareValue_32;  break;

        case TIM_CHANNEL_4:
            targetModule_pst->Instance->CCR4 = compareValue_32;  break;

        #ifdef TIM_CHANNEL_5
        case TIM_CHANNEL_5:
            targetModule_pst->Instance->CCR5 = compareValue_32;  break;
        #endif

        #ifdef TIM_CHANNEL_6
        case TIM_CHANNEL_6:
            targetModule_pst->Instance->CCR6 = compareValue_32;  break;
        #endif

        default:
            /* Invalid channel case (log or handle the error) */
            break;
    }
}


uint32 HalTimerGetAutoReload(MapHalTimerModule_en timerModule_en)
{
    TIM_HandleTypeDef *targetModule_pst = NULL;

    if (MapHalTimerModule(timerModule_en, &targetModule_pst) != NMS_ERR_NONE || targetModule_pst == NULL)
    {
        /* Error */
    }

    return (uint32)(targetModule_pst->Instance->ARR);
}



uint32 HalTimerEncoderStart(MapHalTimerModule_en timerModule_en)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Start the requested timer */
        halTimerStatus_en = HAL_TIM_Encoder_Start(targetModule_pst, TIM_CHANNEL_ALL);

        switch(halTimerStatus_en)
        {
            case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
            case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
            case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
            case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
            default:            error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerEncoderStop(MapHalTimerModule_en timerModule_en)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Start the requested timer */
        halTimerStatus_en = HAL_TIM_Encoder_Stop(targetModule_pst, TIM_CHANNEL_ALL);

        switch(halTimerStatus_en)
        {
            case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
            case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
            case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
            case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
            default:            error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerPwmStart(MapHalTimerModule_en timerModule_en, uint32 Channel_u32)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Start the requested timer */
        halTimerStatus_en = HAL_TIM_PWM_Start(targetModule_pst, Channel_u32);

        switch(halTimerStatus_en)
        {
            case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
            case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
            case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
            case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
            default:            error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerPwmStop(MapHalTimerModule_en timerModule_en, uint32 Channel_u32)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Stop the requested timer */
        halTimerStatus_en = HAL_TIM_PWM_Stop(targetModule_pst, Channel_u32);

        switch(halTimerStatus_en)
        {
            case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
            case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
            case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
            case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
            default:            error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerStart(MapHalTimerModule_en timerModule_en)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Start the requested timer */
        halTimerStatus_en = HAL_TIM_Base_Start_IT(targetModule_pst);

        switch(halTimerStatus_en)
        {
            case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
            case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
            case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
            case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
            default:           	error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerStop(MapHalTimerModule_en timerModule_en)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    HAL_StatusTypeDef halTimerStatus_en = NMS_ERR_NONE;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Stop the requested timer */
        halTimerStatus_en = HAL_TIM_Base_Stop_IT(targetModule_pst);

        switch(halTimerStatus_en)
        {
			case HAL_BUSY:      error_u32 = NMS_ERR_BUSY;		break;
			case HAL_TIMEOUT:   error_u32 = NMS_ERR_TIMEOUT;    break;
			case HAL_ERROR:   	error_u32 = NMS_ERR_UNKNOWN;    break;
			case HAL_OK:      	error_u32 = NMS_ERR_NONE;       break;
			default:           	error_u32 = NMS_ERR_UNKNOWN;	break;
        }
    }

    return error_u32;
}


uint32 HalTimerReloadUs(MapHalTimerModule_en timerModule_en, uint64 period_u64)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        if(period_u64 == 0uLL)
        {
            error_u32 = NMS_ERR_UNKNOWN;
        }
        else
        {
            /* Compute the number of step that we need to count */
            instance_gast[timerModule_en].downCounter_u64 = period_u64 / instance_gast[timerModule_en].timerPeriod_u64;
            if((instance_gast[timerModule_en].downCounter_u64 * instance_gast[timerModule_en].timerPeriod_u64) < period_u64)
            {
                instance_gast[timerModule_en].downCounter_u64++;
            }
            else
            {
                error_u32 = NMS_ERR_UNKNOWN;
            }
        }
    }

    return error_u32;
}


uint32 HalTimerGetRemainingTimeUs(MapHalTimerModule_en timerModule_en, uint64 * remainingTime_pu64)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        /* Add the time corresponding to the number of interruption left for this timer */
        *remainingTime_pu64 = instance_gast[timerModule_en].downCounter_u64;
        *remainingTime_pu64 *= instance_gast[timerModule_en].timerPeriod_u64;
    }
    else
    {
        error_u32 = NMS_ERR_UNKNOWN;
    }

    return error_u32;
}


uint32 HalTimerConfigureCallback(MapHalTimerModule_en timerModule_en, void (*callback_pfn)(void))
{
    uint32 error_u32 = NMS_ERR_DEFAULT;

    if (timerModule_en < MAP_HAL_TIMER_NUMBER )
    {
        halTimerCallbacks[timerModule_en] = callback_pfn;
        error_u32 = NMS_ERR_NONE;
    }
    else
    {
        error_u32 = NMS_ERR_UNKNOWN;
    }

    return error_u32;
}


void HalTimerDeinit(MapHalTimerModule_en timerModule_en)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    TIM_HandleTypeDef * targetModule_pst;
    uint8 index_u8;
    uint8 startIndex_u8;
    uint8 iteration_u8 = 1u;		 /* Default to 1 loop iteration (deinit one module) */

    if(timerModule_en == MAP_HAL_TIMER_NUMBER)
    {
        iteration_u8  = timerModule_en;
        startIndex_u8 = 0u;
    }
    else
    {
        iteration_u8 += timerModule_en;
        startIndex_u8 = timerModule_en;
    }

    for(index_u8 = startIndex_u8; index_u8 < iteration_u8; index_u8++)
    {
        error_u32 = MapHalTimerModule((MapHalTimerModule_en)index_u8, &targetModule_pst);
        if(error_u32 == NMS_ERR_NONE)
        {
            HAL_TIM_Base_DeInit(targetModule_pst);
        }
    }
}

/******************************************************************************
 * Static Function Definitions
 ******************************************************************************/

/**
* @brief    Compute the timerPeriod of the requested timer
*
* @param    timerModule_en      The timer module to use
*
* @return   The value of the timer period. If the timer module is invalid, the
*           value returned will be 0.
*
* @note     CAUTION : The part to compute the actual time frequency is target
*                     dependant, do not forget to update it to fit your needs.
*                     This affects the calculation of the timerPeriod and so
*                     can change the resolution that has been computed by the
*                     user in cubeMX
*/
static uint32 HalTimerComputeTimerPeriod(MapHalTimerModule_en timerModule_en, uint64 * timerPeriod_u64)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    uint32 timerFrequency_u32 = 0uL;
    TIM_HandleTypeDef * targetModule_pst;

    error_u32 = MapHalTimerModule(timerModule_en, &targetModule_pst);

    if(error_u32 == NMS_ERR_NONE)
    {
        *timerPeriod_u64 = HAL_TIMER_SEC_TO_MSEC;
        *timerPeriod_u64 *= (targetModule_pst->Instance->PSC + 1UL);
        *timerPeriod_u64 *= (targetModule_pst->Instance->ARR + 1UL);
        *timerPeriod_u64 /= timerFrequency_u32;
    }
    else
    {
        timerPeriod_u64 = 0uLL;
        error_u32 = NMS_ERR_UNKNOWN;
    }

    return error_u32;
}

/******************************************************************************
 * Callback Function Definitions
 ******************************************************************************/

/**
 * @brief      CubeMX timer period elapsed callback
 *
 * @param[in]  htim    pointer to a TIM_HandleTypeDef structure that contains
 *                     the configuration information for the specified TIMER.
 * @return             Error code in case there is one during the process.
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    MapHalTimerModule_en timerModule_en;
    uint32 error_u32 = NMS_ERR_DEFAULT;

    error_u32 = MapHalTimerCallback(&htim, &timerModule_en);

    if (error_u32 == NMS_ERR_NONE)
    {
        /* Check if we reach the interrupt enough to reach the goal counter */
        if(instance_gast[timerModule_en].downCounter_u64 == 0uLL)
        {
            if(halTimerCallbacks[timerModule_en] != NULL)
            {
                /* Call the user callback */
                halTimerCallbacks[timerModule_en]();
            }
            else
            {
               error_u32 = NMS_ERR_UNKNOWN;
            }
        }
        else
        {
            instance_gast[timerModule_en].downCounter_u64--;
        }
    }
    else
    {
       error_u32 = NMS_ERR_UNKNOWN;
    }
}


/**
 * @brief      CubeMX timer error callback
 *
 * @param[in]  htim         pointer to a TIM_HandleTypeDef structure that contains
 *                          the configuration information for the specified TIMER.
 * @return                  Error code in case there is one during the process.
 */
void HAL_TIM_ErrorCallback(TIM_HandleTypeDef * htim)
{
    uint32 error_u32 = NMS_ERR_DEFAULT;
    MapHalTimerModule_en timerModule_en;

    error_u32 = MapHalTimerCallback(&htim, &timerModule_en);

    if(error_u32 == NMS_ERR_NONE)
    {
        switch(htim->State)
        {
        case HAL_TIM_STATE_RESET:
        	error_u32 = NMS_ERR_NOT_INITIALIZED;	break;

        case HAL_TIM_STATE_BUSY:
        	error_u32 = NMS_ERR_BUSY;
            break;

        case HAL_TIM_STATE_TIMEOUT:
        	error_u32 = NMS_ERR_TIMEOUT;			break;

        case HAL_TIM_STATE_ERROR:
        	error_u32 = NMS_ERR_UNEXPECTED;			break;

        case HAL_TIM_STATE_READY:
        	error_u32 = NMS_ERR_NOT_RUNNING;		break;

        default:
        	error_u32 = NMS_ERR_UNKNOWN;			break;
        }
    }
    else
    {
    	error_u32 = NMS_ERR_INVALID_ARGUMENT;
    }
}
