//----------------------------------------------------------------------------
//
// \brief   Heartbeat
//
//          Indicate system condition with LED
//
// \author  Maximilian Seesslen <mes@seesslen.net>
//
//----------------------------------------------------------------------------


//---Documentation------------------------------------------------------------


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


#include <biwak/heartbeat.hpp>
#include <biwak/led.hpp>
#include <biwak/output_blink_modes.h>
#include <stdlib.h>        // abort()


//---Defines------------------------------------------------------------------


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


CHeartBeat *heartBeat=nullptr;

extern "C"
{
   void logHeartBeat( ELogBlended code ) __attribute((weak));
}


void logHeartBeat( ELogBlended code )
{
   if(heartBeat)
   {
      if( toCategory( code ) == ELogCategory::Loop )
         heartBeat->eventLoop();
      else
         heartBeat->logCode( code );
   }
}


CHeartBeat::CHeartBeat( CLed &led )
   :m_heartBeat_led(led)
{
   heartBeat=this;
   updateLed();
}


CHeartBeat::~CHeartBeat(  )
{
   heartBeat=nullptr;
}


void CHeartBeat::eventLoop()
{
   if( ( m_status == ELogCategory::Warning ) || ( m_status == ELogCategory::Beckon ) )
   {
      if( lrElapsedMSeconds(&m_timerEvent, m_warnTimeSeconds * MSECS_PER_SEC ) )
      {
         // At the moment error-state will not be left at all
         // m_status = toCategory(m_lastError) >=
         //         ( m_status == ELogCategory::Error ) ? ELogCategory::Error : ELogCategory::Good;
         m_status = ELogCategory::Good;
         updateLed();
      }
   }
   m_heartBeat_led.eventLoop();
}


void CHeartBeat::updateLed()
{
   switch( m_status )
   {
      case ELogCategory::Calm:
         m_status=ELogCategory::Good;
         // Fallthrough
      case ELogCategory::Good:
         m_heartBeat_led.setBlinkMode( BlinkModes::_double );
         break;
      case ELogCategory::Warning:
         m_heartBeat_led.setBlinkMode( BlinkModes::error );
         break;
      case ELogCategory::Critical:
         m_heartBeat_led.setBlinkMode( BlinkModes::blink );
         break;
      case ELogCategory::Beckon:
         m_heartBeat_led.setBlinkMode( BlinkModes::triple );
         break;
      case ELogCategory::Fatal:
         // Fallthrough
      default:
         m_heartBeat_led.setValue( true );
         break;
   }
}


void CHeartBeat::logCode( ELogBlended logCode )
{
   ELogCategory logCategory = toCategory( logCode );
   switch( logCategory )
   {
      case ELogCategory::Debug:
      case ELogCategory::Caution:
         return;
      break;
      case ELogCategory::Warning:
         #if defined BIWAK_HEARTBEAT_WARNING_COUNTER
         m_warningCounter++;
         #endif
         // If there is already an error state ongoing, ignore the warning.
         // If the warning is already active, still reset the timer.
         if(   m_status > ELogCategory::Warning )
            //&& ( m_status <= ELogCategory::ErrorEnd ) )
         {
            return;
         }
         m_warnTimeSeconds = HEARTBEAT_WARNING_DURATION_SECONDS;
         break;
      case ELogCategory::Beckon:
         if( m_status > ELogCategory::Caution )
         {
            return;
         }
         m_warnTimeSeconds = HEARTBEAT_BECKON_DURATION_SECONDS;
         break;
      case ELogCategory::Critical:
      case ELogCategory::Fatal:
      case ELogCategory::Calm:
         break;
      default:
         #if defined ( STM32 )
            abort();
         #else
            throw( "Unknown log category" );
         #endif
         break;
   }
   m_timerEvent=lrNow();
   if( logCategory > ELogCategory::Info )
   {
      m_status=logCategory;
      if( logCategory < ELogCategory::Function )
      {
         if( m_status >= toCategory(m_lastError) )
         {
            m_lastError = logCode;
         }
      }
   }
   updateLed();
}


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