//-----------------------------------------------------------------------------
///
/// \brief  Implement systick
///
///         Systick is used as timebase for software timers.
///         There is a high speed timer and a low speed timer.
///
///         The low speed timer has an resolution of 1ms. It can be used for
///         things like PWM-fading, sound playback. It has an range of
///         around 50 days.
///
///         The resolution of the high speed timer if 0x10000 times of the low
///         speed timer (1ms); Thats around 15ns. It is mostly needed for
///         signal decoding.
///         It has an range of around 1 minute.
///
///
/// \date   20150122
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


#include <biwak/biwak.h>               // exception
#include <HALWrapper/stm32_cortex.h>
#include <HALWrapper/stm32_hal.h>      // HAL_IncTick()
#include <biwak/rcc.h>                 // rccSysclkClockEnable()
#include <biwak/handler.h>             // declare SysTick_Handler()
#include <biwak/systick.h>
#include <biwak/nvic.h>                // set priority
#include <biwak/exception.h>
#include <biwak/dataEvent.h>
#include <lepto/lepto.h>


//---Implementation------------------------------------------------------------


lrtimer_t lrtPerMsec=-1;
int systickCounter=0;

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )
// Fixed point float to increase precission
constexpr int fpf=1000;
#endif

//volatile lrtimer_t lrtDivider=1;

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )
hrtimer_t hrtPerUsec=-1;
constexpr hrtimer_t hrtMultiplikator=0x10000;
// high-resulution ticks per one register tick
hrtimer_t hrtPerRt=-1;
#endif

/// \brief  COnfigures Systick
void initSystick( )
{
   // How much ticks do we need till one lrtimer-step/1ms is reached?
   systickCounter=( HAL_RCC_GetSysClockFreq() / MSECS_PER_SEC );
   
   // Calling HAL_SYSTICK_Config() and HAL_NVIC_SetPriority() is already done
   // via HAL_Init():
   //HAL_SYSTICK_Config( systickCounter );

   /*
      Example: 16Mhz, that means we have 16000000 Ticks in one second.
               Thats 16 Ticks per usec (16Mhz/USECS_PER_SEC)
               Thats 16000 Ticks per msec
               We use 16000 as lrtDivider to have 1 lrt per msec.

               lrt has to be shifted in order to handle overflows.
               e.g. multiplied with 0x10000.
               An value of 0x10000 is equal to 1ms.
               hrtPerUsec=0x10000 (1ms) / usec_per_msec=65
               hrtPerRt=multiplikator/systickCounter=0x10000/16000=4,096
   */
   
   #if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )
   hrtPerRt = (hrtMultiplikator*fpf)/systickCounter;

   // It is defined that hrtMultiplikator/0x10000 hr-ticks are on lr-tick.
   hrtPerUsec = ( hrtMultiplikator / USECS_PER_MSEC );
   #endif

   lrtPerMsec = 1;

   // Must have higher priority than anything using timer values.
   //HAL_NVIC_SetPriority( SysTick_IRQn, NVI_HIGH_PRIORITY, 1);

   return;
};

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"

IRQ_Funcion_decl(SysTick_Handler);
IRQ_Funcion_imp( SysTick_Handler )
{
   HAL_IncTick();
   // This would only call the callback which is not implemented in biwak:
   //HAL_SYSTICK_IRQHandler();
}

#pragma GCC diagnostic pop

uint32_t getSystick()
{
      return(HAL_GetTick());
}

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )

hrtimer_t getHrTimerValue()
{
   uint32_t systick1, systick2; // , value __attribute__((unused));
   int32_t nowValue, value, ctrl2;

	do
   {
      systick1=getSystick();
      value = (systickCounter-1) - SysTick->VAL;
      ctrl2 = SCB->ICSR;
      systick2=getSystick();
   }while(systick2!=systick1);

   if( ( ctrl2 >> 26 ) & 1 )
   {
      systick1++;
   }

   nowValue=(hrtimer_t)( ( systick1 * hrtMultiplikator ) + ( ( value * hrtPerRt) / fpf ) );

   return( nowValue );
}
#else
hrtimer_t getHrTimerValue()
{
   exception( );
}
#endif

lrtimer_t getLrTimerValue()
{
   return( (lrtimer_t)getSystick());
}

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )

int32_t hrtValueToUsec(hrtimer_t hrtValue)
{
   return( hrtValue / hrtPerUsec );
}

int32_t hrtValueToMsec(hrtimer_t hrtValue)
{
   return( ( hrtValue / USECS_PER_MSEC ) / ( hrtPerUsec ) );
}

hrtimer_t msecToHrtValue(int mseconds)
{
   return( ( mseconds * USECS_PER_MSEC ) * hrtPerUsec );
}

hrtimer_t usecToHrtValue(int useconds)
{
   return( useconds * hrtPerUsec );
}

#else

hrtimer_t msecToHrtValue(int mseconds)
{
   exception( );
   return( 0 );
}

#endif

int32_t lrtValueToMsec(lrtimer_t lrtValue)
{
   return( lrtValue / lrtPerMsec );
}

lrtimer_t msecToLrt(int32_t msec)
{
   return( msec * lrtPerMsec );
}

int32_t lrtValueToSec(lrtimer_t lrtValue)
{
   return( (lrtValue / lrtPerMsec) / MSECS_PER_SEC );
}

lrtimer_t secToLrt(int32_t sec)
{
   return( sec * lrtPerMsec * MSECS_PER_SEC );
}

int usleepLr(useconds_t usecDelayValue
            #if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
                         , bool breakOnData
            #endif
             )
{
   lrtimer_t nowValue;
   lrtimer_t startValue=getLrTimerValue();

   #if IS_ENABLED( CONFIG_BIWAK_DETECT_NESTED_USLEEP )
   static bool pending=false;
   
   if( pending )
   {
      // pending
      exception();
   }
   pending=true;
   #endif
   
   #if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
      static int biwakDataAvailableOnLastExit=0;
      biwakDataAvailable-=biwakDataAvailableOnLastExit;
      biwakDataAvailableOnLastExit=0;
      if( biwakDataAvailable < 0 )
      {
         exception();
      }
      if( breakOnData && biwakDataAvailable )
      {
         #if IS_ENABLED( CONFIG_BIWAK_DETECT_NESTED_USLEEP )
            pending=false;
         #endif
            
         return(0);
      }
      //printf( "DATA: %d/%d\n", biwakDataAvailable, biwakDataAvailableOnLastExit );
   #endif
       
   usecDelayValue/=USECS_PER_MSEC;
       
   while( ( (nowValue=getLrTimerValue()) - startValue)/lrtPerMsec < (int)usecDelayValue )
   {
// Low power modes can not be debugged on L100; Openocd communication
// fails
#if ( ! defined( STM32L1 ) ) || defined ( NDEBUG )
      HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFE);
#else
      __disable_irq();
      HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
      //HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
      __enable_irq();
#endif
      #if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
         if( breakOnData && biwakDataAvailable )
         {
            break;
         }
      #endif
   };
   
   #if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
      biwakDataAvailableOnLastExit=biwakDataAvailable;
   #endif
      
   #if IS_ENABLED( CONFIG_BIWAK_DETECT_NESTED_USLEEP )
      pending=false;
   #endif
      
   return(0);
}

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )

/// \brief   relative delay to given start value
///
/// \return current high resolution timer value
void usleepHr(hrtimer_t usecDelayValue, bool breakOnData /*=false*/ )
{
   hrtimer_t nowValue;
   hrtimer_t startValue=getHrTimerValue();
   /*
   SLEEP MODE: MPU core stopped, peripherals kept running
   STOP MODE -> all clocks are stopped
   STANDBY MODE -> 1.2 V domain powered off
   */
   while( ( ( (nowValue=getHrTimerValue()) - startValue)/hrtPerUsec
                  < usecDelayValue )
   )
   {
      // Low power modes can not be debugged on L100; Openocd communication
      // fails
      #if ( ! defined( STM32L1 ) ) || defined ( NDEBUG )
         HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFE);
      #else
         __disable_irq();
         HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
         //HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
         __enable_irq();
      #endif
         
      #if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
         if( breakOnData && biwakDataAvailable )
         {
            break;
         }
      #endif
   };
   
   return;
}

#endif // ? ! BIWAK_NO_HRT

#if ! IS_ENABLED( CONFIG_BIWAK_NO_HRT )

hrtimer_t getHRTUsec(void)
{
   return( getHrTimerValue() / hrtPerUsec );
}

int usleep(useconds_t usecDelayValue)
{
   usleepHr( usecDelayValue, false);
   
   return(0);
}

#if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
int usleepNoData(useconds_t usecDelayValue)
{
   usleepHr( usecDelayValue, true);
   
   return(0);
}
#endif

#else // ? ! BIWAK_NO_HRT

int usleep( useconds_t usecDelayValue )
{
   usleepLr( usecDelayValue
#if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
            , false /* breakOnData */
#endif
            );
   return(0);
}

#if IS_ENABLED( CONFIG_BIWAK_DATA_EVENT )
int usleepNoData( useconds_t usecDelayValue )
{
   usleepLr( usecDelayValue, true /* breakOnData */ );
   return(0);
};

#endif   // ? BIWAK_DATA_EVENT

#endif // ? ! BIWAK_NO_HRT

void standby()
{
    HAL_PWR_EnterSTANDBYMode();
}

void stop()
{
   HAL_SuspendTick();
   HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
   HAL_ResumeTick();
}

extern "C"
{
   void SystemClock_Config(int LSE);
}

void deepSleep()
{
   HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
   //SystemClock_Config( 1 /*use LSE*/ );
   //SystemCoreClockUpdate();
}

void clearWakeup()
{
   #if defined(STM32G0)
      while(1){};
   #else

   #pragma GCC diagnostic push
   #pragma GCC diagnostic ignored "-Wvolatile"

   if(__HAL_PWR_GET_FLAG(PWR_FLAG_WU) != RESET)
   {
       __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
   }
   #endif

   /* Check if the system was resumed from StandBy mode */
   if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
   {
       /* Clear StandBy flag */
       __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
   }

   #pragma GCC diagnostic pop
}

int getSystickCounter()
{
   return( systickCounter );
}


//---fin-----------------------------------------------------------------------
