/*!
 * \file      timer.c
 *
 * \brief     Timer objects and scheduling management implementation
 *
 * \copyright Revised BSD License, see section \ref LICENSE.
 *
 * \code
 *                ______                              _
 *               / _____)             _              | |
 *              ( (____  _____ ____ _| |_ _____  ____| |__
 *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 *               _____) ) ____| | | || |_| ____( (___| | | |
 *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
 *              (C)2013-2017 Semtech
 *
 * \endcode
 *
 * \author    Miguel Luis ( Semtech )
 *
 * \author    Gregory Cristian ( Semtech )
 */

/******************************************************************************
 * @file    stm32_timer.c
 * @author  MCD Application Team
 * @brief   Time server utility
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
 * All rights reserved.</center></h2>
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/
#include "stm32_timer.h"

/** @addtogroup TIMER_SERVER
  * @{
  */

/* Private macro -----------------------------------------------------------*/
/**
 * @defgroup TIMER_SERVER_private_macro TIMER_SERVER private macros
 *  @{
 */
/**
  * @brief macro definition to initialize a critical section.
  *
  */
#ifndef UTIL_TIMER_INIT_CRITICAL_SECTION
  #define UTIL_TIMER_INIT_CRITICAL_SECTION( )
#endif

/**
  * @brief macro definition to enter a critical section.
  *
  */
#ifndef UTIL_TIMER_ENTER_CRITICAL_SECTION
  #define UTIL_TIMER_ENTER_CRITICAL_SECTION( )   UTILS_ENTER_CRITICAL_SECTION( )
#endif

/**
  * @brief macro definition to exit a critical section.
  *
  */
#ifndef UTIL_TIMER_EXIT_CRITICAL_SECTION
  #define UTIL_TIMER_EXIT_CRITICAL_SECTION( )    UTILS_EXIT_CRITICAL_SECTION( )
#endif
/**
  *  @}
  */
 
/* Private variables -----------------------------------------------------------*/
/**
 * @defgroup TIMER_SERVER_private_varaible TIMER_SERVER private variable
 *  @{
 */

/**
  * @brief Timers list head pointer
  *
  */
static UTIL_TIMER_Object_t *TimerListHead = NULL;

/**
  *  @}
  */

/**
 * @defgroup TIMER_SERVER_private_function TIMER_SERVER private function
 *  @{
 */

void TimerInsertNewHeadTimer( UTIL_TIMER_Object_t *TimerObject );
void TimerInsertTimer( UTIL_TIMER_Object_t *TimerObject );
void TimerSetTimeout( UTIL_TIMER_Object_t *TimerObject );
bool TimerExists( UTIL_TIMER_Object_t *TimerObject );

/**
  *  @}
  */

/* Functions Definition ------------------------------------------------------*/
/**
  * @addtogroup TIMER_SERVER_exported_function
  *  @{
  */

UTIL_TIMER_Status_t UTIL_TIMER_Init(void)
{
  UTIL_TIMER_INIT_CRITICAL_SECTION();
  TimerListHead = NULL;
  return UTIL_TimerDriver.InitTimer();
}

UTIL_TIMER_Status_t UTIL_TIMER_DeInit(void)
{
  return UTIL_TimerDriver.DeInitTimer();
}

UTIL_TIMER_Status_t UTIL_TIMER_Create( UTIL_TIMER_Object_t *TimerObject, uint32_t PeriodValue, UTIL_TIMER_Mode_t Mode, void ( *Callback )( void *), void *Argument)
{
  if((TimerObject != NULL) && (Callback != NULL))
  {
    TimerObject->Timestamp = 0U;
    TimerObject->ReloadValue = UTIL_TimerDriver.ms2Tick(PeriodValue);
    TimerObject->IsPending = 0U;
    TimerObject->IsRunning = 0U;
    TimerObject->IsReloadStopped = 0U;
    TimerObject->Callback = Callback;
    TimerObject->argument = Argument;
    TimerObject->Mode = Mode;
    TimerObject->Next = NULL;
    return UTIL_TIMER_OK;
  }
  else
  {
    return UTIL_TIMER_INVALID_PARAM;
  }
}

UTIL_TIMER_Status_t UTIL_TIMER_Start( UTIL_TIMER_Object_t *TimerObject)
{
  UTIL_TIMER_Status_t  ret = UTIL_TIMER_OK;
  uint32_t elapsedTime;
  uint32_t minValue;
  uint32_t ticks;
    
  if(( TimerObject != NULL ) && ( TimerExists( TimerObject ) == false ) && (TimerObject->IsRunning == 0U))
  {
    UTIL_TIMER_ENTER_CRITICAL_SECTION();
    ticks = TimerObject->ReloadValue;
    minValue = UTIL_TimerDriver.GetMinimumTimeout( );
    
    if( ticks < minValue )
    {
      ticks = minValue;
    }
    
    TimerObject->Timestamp = ticks;
    TimerObject->IsPending = 0U;
    TimerObject->IsRunning = 1U;
    TimerObject->IsReloadStopped = 0U;
    if( TimerListHead == NULL )
    {
      UTIL_TimerDriver.SetTimerContext();
      TimerInsertNewHeadTimer( TimerObject ); /* insert a timeout at now+obj->Timestamp */
    }
    else 
    {
      elapsedTime = UTIL_TimerDriver.GetTimerElapsedTime( );
      TimerObject->Timestamp += elapsedTime;
      
      if( TimerObject->Timestamp < TimerListHead->Timestamp )
      {
        TimerInsertNewHeadTimer( TimerObject);
      }
      else
      {
        TimerInsertTimer( TimerObject);
      }
    }
    UTIL_TIMER_EXIT_CRITICAL_SECTION();
  }
  else
  {
    ret =  UTIL_TIMER_INVALID_PARAM;
  }
  return ret;
}

UTIL_TIMER_Status_t UTIL_TIMER_StartWithPeriod( UTIL_TIMER_Object_t *TimerObject, uint32_t PeriodValue)
{
  UTIL_TIMER_Status_t  ret = UTIL_TIMER_OK;

  if(NULL == TimerObject)
  {
    ret = UTIL_TIMER_INVALID_PARAM;
  }
  else
  {
    TimerObject->ReloadValue = UTIL_TimerDriver.ms2Tick(PeriodValue);
    if(TimerExists(TimerObject))
    {
      (void)UTIL_TIMER_Stop(TimerObject);
    }
    ret = UTIL_TIMER_Start(TimerObject);
  }
  return ret;
}

UTIL_TIMER_Status_t UTIL_TIMER_Stop( UTIL_TIMER_Object_t *TimerObject )
{
  UTIL_TIMER_Status_t  ret = UTIL_TIMER_OK;

  if (NULL != TimerObject)
  {
    UTIL_TIMER_ENTER_CRITICAL_SECTION();
    UTIL_TIMER_Object_t* prev = TimerListHead;
    UTIL_TIMER_Object_t* cur = TimerListHead;
    TimerObject->IsReloadStopped = 1U;
    
    /* List is empty or the Obj to stop does not exist  */
    if(NULL != TimerListHead)
    {
      TimerObject->IsRunning = 0U;
      
      if( TimerListHead == TimerObject ) /* Stop the Head */
      {
          TimerListHead->IsPending = 0;
          if( TimerListHead->Next != NULL )
          {
            TimerListHead = TimerListHead->Next;
            TimerSetTimeout( TimerListHead );
          }
          else
          {
            UTIL_TimerDriver.StopTimerEvt( );
            TimerListHead = NULL;
          }
      }
      else /* Stop an object within the list */
      {      
        while( cur != NULL )
        {
          if( cur == TimerObject )
          {
            if( cur->Next != NULL )
            {
              cur = cur->Next;
              prev->Next = cur;
            }
            else
            {
              cur = NULL;
              prev->Next = cur;
            }
            break;
          }
          else
          {
            prev = cur;
            cur = cur->Next;
          }
        }   
      }
      ret = UTIL_TIMER_OK;
    }
    UTIL_TIMER_EXIT_CRITICAL_SECTION();
  }
  else
  {
    ret = UTIL_TIMER_INVALID_PARAM;
  }
  return ret;
}

UTIL_TIMER_Status_t UTIL_TIMER_SetPeriod(UTIL_TIMER_Object_t *TimerObject, uint32_t NewPeriodValue)
{
  UTIL_TIMER_Status_t  ret = UTIL_TIMER_OK;
  
  if(NULL == TimerObject)
  {
	  ret = UTIL_TIMER_INVALID_PARAM;
  }
  else
  {
    TimerObject->ReloadValue = UTIL_TimerDriver.ms2Tick(NewPeriodValue);
    if(TimerExists(TimerObject))
    {
      (void)UTIL_TIMER_Stop(TimerObject);
      ret = UTIL_TIMER_Start(TimerObject);
    }
  }
  return ret;
}

UTIL_TIMER_Status_t UTIL_TIMER_SetReloadMode(UTIL_TIMER_Object_t *TimerObject, UTIL_TIMER_Mode_t ReloadMode)
{
  UTIL_TIMER_Status_t  ret = UTIL_TIMER_OK;

  if(NULL == TimerObject)
  {
	ret = UTIL_TIMER_INVALID_PARAM;
  }
  else
  {
	TimerObject->Mode = ReloadMode;
  }
  return ret;
}

UTIL_TIMER_Status_t UTIL_TIMER_GetRemainingTime(UTIL_TIMER_Object_t *TimerObject, uint32_t *ElapsedTime)
{
  UTIL_TIMER_Status_t ret = UTIL_TIMER_OK;
  if(TimerExists(TimerObject))
  {
    uint32_t time = UTIL_TimerDriver.GetTimerElapsedTime();
    if (TimerObject->Timestamp < time )
    {
      *ElapsedTime = 0;
    }
    else
    {
      *ElapsedTime = TimerObject->Timestamp - time;
    }
  }
  else
  {
    ret = UTIL_TIMER_INVALID_PARAM;
  }
  return ret;
}

uint32_t UTIL_TIMER_IsRunning( UTIL_TIMER_Object_t *TimerObject )
{
  if( TimerObject != NULL )
  {
    return TimerObject->IsRunning;
  }
  else
  {
    return 0;
  }
}

uint32_t UTIL_TIMER_GetFirstRemainingTime(void)
{
	uint32_t NextTimer = 0xFFFFFFFFU;

	if(TimerListHead != NULL)
	{
		(void)UTIL_TIMER_GetRemainingTime(TimerListHead, &NextTimer);
	}
	return NextTimer;
}

void UTIL_TIMER_IRQ_Handler( void )
{
  UTIL_TIMER_Object_t* cur;
  uint32_t old, now, DeltaContext;

  UTIL_TIMER_ENTER_CRITICAL_SECTION();

  old  =  UTIL_TimerDriver.GetTimerContext( );
  now  =  UTIL_TimerDriver.SetTimerContext( );

  DeltaContext = now  - old; /*intentional wrap around */
  
  /* update timeStamp based upon new Time Reference*/
  /* because delta context should never exceed 2^32*/
  if ( TimerListHead != NULL )
  {
    cur = TimerListHead;
	do {
      if (cur->Timestamp > DeltaContext)
      {
        cur->Timestamp -= DeltaContext;
      }
      else
      {
        cur->Timestamp = 0;
      }
      cur = cur->Next;
    } while(cur != NULL);
  }

  /* Execute expired timer and update the list */
  while ((TimerListHead != NULL) && ((TimerListHead->Timestamp == 0U) || (TimerListHead->Timestamp < UTIL_TimerDriver.GetTimerElapsedTime(  ))))
  {
      cur = TimerListHead;
      TimerListHead = TimerListHead->Next;
      cur->IsPending = 0;
      cur->IsRunning = 0;
      cur->Callback(cur->argument);
      if(( cur->Mode == UTIL_TIMER_PERIODIC) && (cur->IsReloadStopped == 0U))
      {
        (void)UTIL_TIMER_Start(cur);
      }
  }

  /* start the next TimerListHead if it exists and it is not pending*/
  if(( TimerListHead != NULL ) && (TimerListHead->IsPending == 0U))
  {
    TimerSetTimeout( TimerListHead );
  }
  UTIL_TIMER_EXIT_CRITICAL_SECTION();
}

UTIL_TIMER_Time_t UTIL_TIMER_GetCurrentTime(void)
{
  uint32_t now = UTIL_TimerDriver.GetTimerValue( );
  return  UTIL_TimerDriver.Tick2ms(now);
}

UTIL_TIMER_Time_t UTIL_TIMER_GetElapsedTime(UTIL_TIMER_Time_t past )
{
  uint32_t nowInTicks = UTIL_TimerDriver.GetTimerValue( );
  uint32_t pastInTicks = UTIL_TimerDriver.ms2Tick( past );
  /* intentional wrap around. Works Ok if tick duation below 1ms */
  return UTIL_TimerDriver.Tick2ms( nowInTicks- pastInTicks );
}

/**
  *  @}
  */

/**************************** Private functions *******************************/

/**
  *  @addtogroup TIMER_SERVER_private_function
  *
  *  @{
  */
/**
 * @brief Check if the Object to be added is not already in the list
 *
 * @param TimerObject Structure containing the timer object parameters
 * @retval 1 (the object is already in the list) or 0
 */
bool TimerExists( UTIL_TIMER_Object_t *TimerObject )
{
  UTIL_TIMER_Object_t* cur = TimerListHead;

  while( cur != NULL )
  {
    if( cur == TimerObject )
    {
      return true;
    }
    cur = cur->Next;
  }
  return false;
}

/**
 * @brief Sets a timeout with the duration "timestamp"
 *
 * @param TimerObject Structure containing the timer object parameters
 */
void TimerSetTimeout( UTIL_TIMER_Object_t *TimerObject )
{
  uint32_t minTicks= UTIL_TimerDriver.GetMinimumTimeout( );
  TimerObject->IsPending = 1;

  /* In case deadline too soon */
  if(TimerObject->Timestamp  < (UTIL_TimerDriver.GetTimerElapsedTime(  ) + minTicks) )
  {
	  TimerObject->Timestamp = UTIL_TimerDriver.GetTimerElapsedTime(  ) + minTicks;
  }
  UTIL_TimerDriver.StartTimerEvt( TimerObject->Timestamp );
}

/**
 * @brief Adds a timer to the list.
 *
 * @remark The list is automatically sorted. The list head always contains the
 *     next timer to expire.
 *
 * @param TimerObject Structure containing the timer object parameters
 */
void TimerInsertTimer( UTIL_TIMER_Object_t *TimerObject)
{
  UTIL_TIMER_Object_t* cur = TimerListHead;
  UTIL_TIMER_Object_t* next = TimerListHead->Next;

  while (cur->Next != NULL )
  {  
    if( TimerObject->Timestamp  > next->Timestamp )
    {
        cur = next;
        next = next->Next;
    }
    else
    {
        cur->Next = TimerObject;
        TimerObject->Next = next;
        return;

    }
  }
  cur->Next = TimerObject;
  TimerObject->Next = NULL;
}

/**
 * @brief Adds or replace the head timer of the list.
 *
 * @param TimerObject Structure containing the timer object parameters
 *
 * @remark The list is automatically sorted. The list head always contains the
 *         next timer to expire.
 */
void TimerInsertNewHeadTimer( UTIL_TIMER_Object_t *TimerObject )
{
  UTIL_TIMER_Object_t* cur = TimerListHead;

  if( cur != NULL )
  {
    cur->IsPending = 0;
  }

  TimerObject->Next = cur;
  TimerListHead = TimerObject;
  TimerSetTimeout( TimerListHead );
}

/**
  *  @}
  */

/**
  *  @}
  */

