 /*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (C)2013 Semtech

Description: Generic lora driver implementation

License: Revised BSD License, see LICENSE.TXT file include in the project

Maintainer: Miguel Luis, Gregory Cristian and Wael Guibene
*/
/******************************************************************************
  * @file    timeserver.c
  * @author  MCD Application Team
  * @brief   Time server infrastructure
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2018 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include <time.h>
#include "rtc.h"
#include "timeServer.h"
//#include "low_power.h"


/*!
 * safely execute call back
 */
#define exec_cb( _callback_, context )     \
  do {                          \
      if( _callback_ == NULL )    \
      {                           \
        while(1);                 \
      }                           \
      else                        \
      {                           \
        _callback_( context );               \
      }                           \
  } while(0);



/*!
 * Timers list head pointer
 */
static TimerEvent_t *TimerListHead = NULL;

/*!
 * \brief Adds or replace the head timer of the list.
 *
 * \remark The list is automatically sorted. The list head always contains the
 *         next timer to expire.
 *
 * \param [IN]  obj Timer object to be become the new head
 * \param [IN]  remainingTime Remaining time of the previous head to be replaced
 */
static void TimerInsertNewHeadTimer( TimerEvent_t *obj );

/*!
 * \brief Adds a timer to the list.
 *
 * \remark The list is automatically sorted. The list head always contains the
 *         next timer to expire.
 *
 * \param [IN]  obj Timer object to be added to the list
 * \param [IN]  remainingTime Remaining time of the running head after which the object may be added
 */
static void TimerInsertTimer( TimerEvent_t *obj );

/*!
 * \brief Sets a timeout with the duration "timestamp"
 *
 * \param [IN] timestamp Delay duration
 */
static void TimerSetTimeout( TimerEvent_t *obj );

/*!
 * \brief Check if the Object to be added is not already in the list
 *
 * \param [IN] timestamp Delay duration
 * \retval true (the object is already in the list) or false
 */
static bool TimerExists( TimerEvent_t *obj );

void TimerInit( TimerEvent_t *obj, void ( *callback )( void *context ) )
{
  obj->Timestamp = 0;
  obj->ReloadValue = 0;
  obj->IsStarted = false;
  obj->IsNext2Expire = false;
  obj->Callback = callback;
  obj->Context = NULL;
  obj->Next = NULL;
}

void TimerSetContext( TimerEvent_t *obj, void* context )
{
  obj->Context = context;
}

void TimerStart( TimerEvent_t *obj )
{
  uint32_t elapsedTime = 0;

  BACKUP_PRIMASK();

  DISABLE_IRQ( );


  if( ( obj == NULL ) || ( TimerExists( obj ) == true ) )
  {
    RESTORE_PRIMASK( );
    return;
  }
  obj->Timestamp = obj->ReloadValue;
  obj->IsStarted = true;
  obj->IsNext2Expire = false;

  if( TimerListHead == NULL )
  {
    rtc_set_timer_context( );
    TimerInsertNewHeadTimer( obj ); // insert a timeout at now+obj->Timestamp
  }
  else
  {
    elapsedTime = rtc_get_timer_elapsed_time( );
    obj->Timestamp += elapsedTime;

    if( obj->Timestamp < TimerListHead->Timestamp )
    {
      TimerInsertNewHeadTimer( obj);
    }
    else
    {
      TimerInsertTimer( obj);
    }
  }
  RESTORE_PRIMASK( );
}



bool TimerIsStarted( TimerEvent_t *obj )
{
  return obj->IsStarted;
}

void TimerIrqHandler( void )
{
  TimerEvent_t* cur;
  TimerEvent_t* next;



  uint32_t old =  rtc_get_timer_context( );
  uint32_t now =  rtc_set_timer_context( );
  uint32_t DeltaContext = now - old; //intentionnal wrap around

  /* Update timeStamp based upon new Time Reference*/
  /* because delta context should never exceed 2^32*/
  if ( TimerListHead != NULL )
  {
    for (cur=TimerListHead; cur->Next != NULL; cur= cur->Next)
    {
      next =cur->Next;
      if (next->Timestamp > DeltaContext)
      {
        next->Timestamp -= DeltaContext;
      }
      else
      {
        next->Timestamp = 0 ;
      }
    }
  }

  /* execute imediately the alarm callback */
  if ( TimerListHead != NULL )
  {
    cur = TimerListHead;
    TimerListHead = TimerListHead->Next;
    cur->IsStarted = false;
    exec_cb( cur->Callback, cur->Context );
  }


  // remove all the expired object from the list
  while( ( TimerListHead != NULL ) && ( TimerListHead->Timestamp < rtc_get_timer_elapsed_time(  )  ))
  {
   cur = TimerListHead;
   TimerListHead = TimerListHead->Next;
   cur->IsStarted = false;
   exec_cb( cur->Callback, cur->Context );
  }

  /* start the next TimerListHead if it exists AND NOT running */
  if( ( TimerListHead != NULL ) && ( TimerListHead->IsNext2Expire == false ) )
  {
    TimerSetTimeout( TimerListHead );
  }
}

void TimerStop( TimerEvent_t *obj )
{
  BACKUP_PRIMASK();

  DISABLE_IRQ( );

  TimerEvent_t* prev = TimerListHead;
  TimerEvent_t* cur = TimerListHead;

  // List is empty or the Obj to stop does not exist
  if( ( TimerListHead == NULL ) || ( obj == NULL ) )
  {
    RESTORE_PRIMASK( );
    return;
  }

  obj->IsStarted = false;

  if( TimerListHead == obj ) // Stop the Head
  {
    if( TimerListHead->IsNext2Expire == true ) // The head is already running
    {

      TimerListHead->IsNext2Expire = false;
      if( TimerListHead->Next != NULL )
      {
        TimerListHead = TimerListHead->Next;
        TimerSetTimeout( TimerListHead );
      }
      else
      {
        rtc_stop_alarm( );
        TimerListHead = NULL;
      }
    }
    else // Stop the head before it is started
    {
      if( TimerListHead->Next != NULL )
      {
        TimerListHead = TimerListHead->Next;
      }
      else
      {
        TimerListHead = NULL;
      }
    }
  }
  else // Stop an object within the list
  {
    while( cur != NULL )
    {
      if( cur == obj )
      {
        if( cur->Next != NULL )
        {
          cur = cur->Next;
          prev->Next = cur;
        }
        else
        {
          cur = NULL;
          prev->Next = cur;
        }
        break;
      }
      else
      {
        prev = cur;
        cur = cur->Next;
      }
    }
  }

  RESTORE_PRIMASK( );
}



void TimerReset( TimerEvent_t *obj )
{
  TimerStop( obj );
  TimerStart( obj );
}

void TimerSetValue( TimerEvent_t *obj, uint32_t value )
{
  uint32_t minValue = 0;
  uint32_t ticks = rtc_ms2tick( value );

  TimerStop( obj );

  minValue = rtc_get_min_timeout( );

  if( ticks < minValue )
  {
    ticks = minValue;
  }

  obj->Timestamp = ticks;
  obj->ReloadValue = ticks;
}

TimerTime_t TimerGetCurrentTime( void )
{
  uint32_t now = rtc_get_timer_value( );
  return  rtc_tick2ms(now);
}

TimerTime_t TimerGetElapsedTime( TimerTime_t past )
{
  if ( past == 0 )
  {
    return 0;
  }
  uint32_t nowInTicks = rtc_get_timer_value( );
  uint32_t pastInTicks = rtc_ms2tick( past );
  /* intentional wrap around. Works Ok if tick duation below 1ms */
  return rtc_tick2ms( nowInTicks- pastInTicks );
}

static bool TimerExists( TimerEvent_t *obj )
{
  TimerEvent_t* cur = TimerListHead;

  while( cur != NULL )
  {
    if( cur == obj )
    {
      return true;
    }
    cur = cur->Next;
  }
  return false;
}
static void TimerSetTimeout( TimerEvent_t *obj )
{
  int32_t minTicks= rtc_get_min_timeout( );
  obj->IsNext2Expire = true;

  // In case deadline too soon
  if(obj->Timestamp  < (rtc_get_timer_elapsed_time(  ) + minTicks) )
  {
    obj->Timestamp = rtc_get_timer_elapsed_time(  ) + minTicks;
  }
  rtc_set_alarm( obj->Timestamp );
}

TimerTime_t TimerTempCompensation( TimerTime_t period, float temperature )
{
    return rtc_temperature_compensation( period, temperature );
}


static void TimerInsertTimer( TimerEvent_t *obj)
{
  TimerEvent_t* cur = TimerListHead;
  TimerEvent_t* next = TimerListHead->Next;

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

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

static void TimerInsertNewHeadTimer( TimerEvent_t *obj )
{
  TimerEvent_t* cur = TimerListHead;

  if( cur != NULL )
  {
    cur->IsNext2Expire = false;
  }

  obj->Next = cur;
  TimerListHead = obj;
  TimerSetTimeout( TimerListHead );
}
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
