/**********************************************************************************************************************
 *  FILE DESCRIPTION
 *  -----------------------------------------------------------------------------------------------------------------*/
/** \file
 *  \brief        Event-driven hierarchical finite state machine implementation
 *
 *  -------------------------------------------------------------------------------------------------------------------
 *  COPYRIGHT
 *  -------------------------------------------------------------------------------------------------------------------
 *  \par Copyright
 *  \verbatim
 *  Copyright (c) 2023 by Vector Informatik GmbH.                                                  All rights reserved.
 *
 *                This software is copyright protected and proprietary to Vector Informatik GmbH.
 *                Vector Informatik GmbH grants to you only those rights as set out in the license conditions.
 *                All other rights remain with Vector Informatik GmbH.
 *  \endverbatim
 */
/*********************************************************************************************************************/

/**********************************************************************************************************************
 *  REVISION HISTORY
 *  -------------------------------------------------------------------------------------------------------------------
 *  Version   Date        Author  Change Id          Description
 *  -------------------------------------------------------------------------------------------------------------------
 *  01.00.00  2012-10-30  visjhg  -                  First implementation
 *  01.00.01  2012-12-10  visjhg  -                  No changes
 *  01.01.00  2013-12-11  visjhg  ESCAN00071901      Renamed package to FblLib_Fsm
 *                        visase  -                  Performed MISRA checks
 *  01.01.01  2019-01-24  visrie  ESCAN00101864      No changes
 *  01.02.00  2023-12-15  vistbe  FBL-7986           Perform MISRA analysis
 *                                                   Added/adapted MemMap sections
 *********************************************************************************************************************/

#define FBL_FSM_SOURCE

/**********************************************************************************************************************
 *  INCLUDES
 *********************************************************************************************************************/

#include "fbl_fsm_inc.h"

/**********************************************************************************************************************
 *  VERSION
 *********************************************************************************************************************/

/* --- Version check --- */
#if ( FBLLIB_FSM_VERSION != 0x0102u ) || \
    ( FBLLIB_FSM_RELEASE_VERSION != 0x00u )
# error "Error in FBL_FSM.C: Source and header file are inconsistent!"
#endif

#if ( FBLLIB_FSM_VERSION != _FBLLIB_FSM_VERSION ) || \
    ( FBLLIB_FSM_RELEASE_VERSION != _FBLLIB_FSM_RELEASE_VERSION )
# error "Error in FBL_FSM.C: Source and v_ver.h are inconsistent!"
#endif

/**********************************************************************************************************************
 *  LOCAL DATA TYPES AND STRUCTURES
 *********************************************************************************************************************/

/** Type to select whether super-states should be iterated in case no matching event handler was found */
typedef enum
{
   kFsmIterateHierarchy_Disabled,   /**< Do not iterate super-states */
   kFsmIterateHierarchy_Enabled     /**< Iterate super-states until matching event handler was found */
} tFsmIterateHierarchy;

/**********************************************************************************************************************
 *  LOCAL FUNCTION PROTOTYPES
 **********************************************************************************************************************/
#define FBLFSM_START_SEC_CODE
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */

static void FblFsmHandleEvent( tFblFsmContextPtr const pFsmContext, tFblFsmEvent event, tFsmIterateHierarchy iterate);
static vuint8 FblFsmGetStateHierachy( const V_MEMRAM1 tFblFsmContext V_MEMRAM2 V_MEMRAM3 * const pFsmContext, tFblFsmState state,
                                      V_MEMRAM1 tFblFsmState V_MEMRAM2 V_MEMRAM3 * hierarchy );
#define FBLFSM_STOP_SEC_CODE
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */

/**********************************************************************************************************************
 *  LOCAL DATA
 **********************************************************************************************************************/

#if defined( FBL_FSM_ENABLE_DEBUGGING )
#define FBLFSM_START_SEC_VAR
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */
/** Global state machine instance counter */
static tFblFsmInstance g_Instance;
#define FBLFSM_STOP_SEC_VAR
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */
#endif

/**********************************************************************************************************************
 *  LOCAL FUNCTIONS
 **********************************************************************************************************************/
#define FBLFSM_START_SEC_CODE
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */
/**********************************************************************************************************************
 *  FblFsmGetStateHierachy
 *********************************************************************************************************************/
/*! \brief        Provides the state hierarchy starting with the given state
 *  \param[in]    pFsmContext Pointer to context of state machine
 *  \param[in]    state The current state
 *  \param[out]   hierarchy The returned state hierarchy
 *********************************************************************************************************************/
static vuint8 FblFsmGetStateHierachy( const V_MEMRAM1 tFblFsmContext V_MEMRAM2 V_MEMRAM3 * const pFsmContext,
                                      tFblFsmState state,
                                      V_MEMRAM1 tFblFsmState V_MEMRAM2 V_MEMRAM3 * hierarchy )
{
   vuint8 index = 0u;
   tFblFsmState localState = state;

   while (FBL_FSM_DEFAULT_STATE != localState)
   {
      /* Check for hierarchy depth and stateCount to prevent buffer overrun(s) */
      if ( (index >= FBL_FSM_MAX_HIERARCHY_DEPTH)
        || (localState >= pFsmContext->stateCount))
      {
         FBL_FSM_DEBUG_ERROR_PRINT(pFsmContext->instance);

         index = 0u;
         localState = FBL_FSM_DEFAULT_STATE;
      }
      else
      {
         hierarchy[index] = localState;
         index++;

         localState = pFsmContext->stateDefinitions[localState].superState;
      }
   }

   return index;
}

/**********************************************************************************************************************
 *  FblFsmHandleEvent
 *********************************************************************************************************************/
/*! \brief        Handles the given event
 *  \param[in]    pFsmContext Pointer to context of state machine
 *  \param[in]    event The event to be handled
 *  \param[out]   iterate Indicates whether state hierarchy iteration is allowed
 *********************************************************************************************************************/
static void FblFsmHandleEvent( tFblFsmContextPtr const pFsmContext, tFblFsmEvent event, tFsmIterateHierarchy iterate )
{
   vuint16 index;
   vuint16 hierarchy = 0u;
   tFblFsmState state = pFsmContext->state;
   tFblFsmGuard guard;
   V_MEMROM1 tFblFsmStateDefinition V_MEMROM2 V_MEMROM3 * stateDef;
   V_MEMROM1 tFblFsmEventEntry V_MEMROM2 V_MEMROM3 * triggers;

   FBL_FSM_DEBUG_PRINT2(pFsmContext->instance, event, state);

   while ( (FBL_FSM_DEFAULT_STATE != state)
        && (state < pFsmContext->stateCount))
   {
      stateDef = &pFsmContext->stateDefinitions[state];
      triggers = stateDef->triggers;

      /* Search for matching event handler */
      for (index = 0u; index < stateDef->triggerCount; index++)
      {
         if ( (triggers[index].trigger == event)
           || (triggers[index].trigger == (tFblFsmEvent)kFblFsmDefaultEvent_Any) )
         {
            /* Handler matching event found, now execute handler */
            guard = triggers[index].handler(pFsmContext, event);

            FBL_FSM_DEBUG_PRINT3(pFsmContext->instance, guard);

            if (kFblFsmGuard_False != guard)
            {
               if (kFblFsmGuard_True == guard)
               {
                  /* Pending state may be "none" */
                  pFsmContext->pendingState = triggers[index].nextState;
               }
               else /* kFblFsmGuard_Overwrite */
               {
                  /* Do not set pendingState, this is (has to be) done by the event handler */
               }

               /* Stop searching for event handlers as event was handled */
               state = FBL_FSM_DEFAULT_STATE;
               break;
            }
            else /* kFblFsmGuard_False */
            {
               /* State shall not be left, continue to search for other matching event handlers */
            }
         }
      }

      /* Check if event has been already handled */
      if (FBL_FSM_DEFAULT_STATE != state)
      {
         /* No matching event handler returning kFblFsmGuard_True could be found for current state,
          * continue search in (encapsulating) super state, if possible */
         if (kFsmIterateHierarchy_Enabled == iterate)
         {
            hierarchy++;

            if (hierarchy < FBL_FSM_MAX_HIERARCHY_DEPTH)
            {
               state = stateDef->superState;
            }
            else
            {
               /* Abort search to avoid (potential) infinite loop */
               FBL_FSM_DEBUG_ERROR_PRINT(pFsmContext->instance);

               state = FBL_FSM_DEFAULT_STATE;
            }
         }
         else
         {
            /* State traversing not allowed, abort search at this point */
            state = FBL_FSM_DEFAULT_STATE;
         }
      }
   }

#if defined( FBL_FSM_ENABLE_DEBUGGING )
   if ( (state >= pFsmContext->stateCount)
     && (FBL_FSM_DEFAULT_STATE != state) )
   {
      FBL_FSM_DEBUG_ERROR_PRINT(pFsmContext->instance);
   }
#endif /* FBL_FSM_ENABLE_DEBUGGING */
}

/**********************************************************************************************************************
 *  GLOBAL FUNCTIONS
 **********************************************************************************************************************/

/**********************************************************************************************************************
 *  FblFsmInitPowerOn
 *********************************************************************************************************************/
/*! \brief        Module initialization
 *********************************************************************************************************************/
void FblFsmInitPowerOn( void )
{
#if defined( FBL_FSM_ENABLE_DEBUGGING )
   g_Instance = 0u;
#endif
}

/**********************************************************************************************************************
 *  FblFsmInit
 *********************************************************************************************************************/
/*! \brief        State machine initialization
 *  \param[in]    pFsmContext Pointer to context of state machine
 *  \param[in]    initialState The initial state of the state machine
 *********************************************************************************************************************/
void FblFsmInit( tFblFsmContextPtr const pFsmContext, tFblFsmState initialState )
{
#if defined( FBL_FSM_ENABLE_DEBUGGING )
   pFsmContext->instance = g_Instance;
   g_Instance++;
#endif

   /* Start with internal default state */
   pFsmContext->state = FBL_FSM_DEFAULT_STATE;
   pFsmContext->pendingState = initialState;
   pFsmContext->pendingEvent = (tFblFsmEvent)kFblFsmDefaultEvent_None;

   /* Enter provided initial state */
   FblFsmStateTask(pFsmContext);
}

/**********************************************************************************************************************
 *  FblFsmDeinit
 *********************************************************************************************************************/
/*! \brief        State machine deinitialization
 *  \param[in]    pFsmContext Pointer to context of state machine
 *********************************************************************************************************************/
void FblFsmDeinit( tFblFsmContextPtr const pFsmContext )
{
   /* Switch to internal default state to avoid further processing */
   pFsmContext->state = FBL_FSM_DEFAULT_STATE;
   pFsmContext->pendingState = FBL_FSM_DEFAULT_STATE;
   pFsmContext->pendingEvent = (tFblFsmEvent)kFblFsmDefaultEvent_None;
}

/**********************************************************************************************************************
 *  FblFsmStateTask
 *********************************************************************************************************************/
/*! \brief        State machine handling
 *  \details      This function checks for pending events / state transitions and handles them appropriately.
 *  \param[in]    pFsmContext Pointer to context of state machine
 *********************************************************************************************************************/
void FblFsmStateTask( tFblFsmContextPtr const pFsmContext )
{
   tFblFsmState   stateHierarchyExit[FBL_FSM_MAX_HIERARCHY_DEPTH];
   tFblFsmState   stateHierarchyEntry[FBL_FSM_MAX_HIERARCHY_DEPTH];

   tFblFsmState   currentState;
   tFblFsmState   pendingState;

   vsint8         index;
   vuint8         exitCount;
   vuint8         entryCount;
   vsint8         exitIndex;
   vsint8         entryIndex;

   /* Check for pending event, which could cause state change */
   tFblFsmEvent event = pFsmContext->pendingEvent;

   if ((tFblFsmEvent)kFblFsmDefaultEvent_None != event)
   {
      /* Reset event */
      pFsmContext->pendingEvent = (tFblFsmEvent)kFblFsmDefaultEvent_None;

      /* Process event and trigger state change if necessary */
      FblFsmHandleEvent(pFsmContext, event, kFsmIterateHierarchy_Enabled);
   }

   /* Check whether state has to be changed */
   while (FBL_FSM_DEFAULT_STATE != pFsmContext->pendingState)
   {
      currentState = pFsmContext->state;
      pendingState = pFsmContext->pendingState;

      FBL_FSM_DEBUG_PRINT1(pFsmContext->instance, currentState, pendingState);

      /* Reset event which triggered state change */
      pFsmContext->pendingState = FBL_FSM_DEFAULT_STATE;

      /* Get state hierarchy of current state and pending state */
      exitCount = FblFsmGetStateHierachy(pFsmContext, currentState, stateHierarchyExit);
      entryCount = FblFsmGetStateHierachy(pFsmContext, pendingState, stateHierarchyEntry);

      /* Check for re-entry of current state */
      if (currentState == pendingState)
      {
         exitIndex = 0;
         entryIndex = 0;
      }
      else
      {
         exitIndex = ((vsint8)exitCount - 1);
         entryIndex = ((vsint8)entryCount - 1);

         /* Search for common super state of current state and pending state */
         while ( (exitIndex >= 0)
               && (stateHierarchyExit[exitIndex] == stateHierarchyEntry[entryIndex]) )
         {
            exitIndex--;
            entryIndex--;
         }

         /* An exitIndex / entryIndex larger than 0 indicates that both state have a common super state */
      }

      /* Leave all super-states
       * A state is always left, even if the pending state is the same (re-entry),
       * but not if the target state is a sub-state of the current state.
       * An exit handler is executed after the transition action has been executed */
      for (index = 0; index <= exitIndex; index++)
      {
         pFsmContext->state = stateHierarchyExit[index];

         /* Leave previous state by calling exit handler */
         FblFsmHandleEvent(pFsmContext, (tFblFsmEvent)kFblFsmDefaultEvent_Exit, kFsmIterateHierarchy_Disabled);
      }

      /* Enter all super-states
       * A pending state is only entered if it is not a previous super-state */
      for (index = entryIndex; index >= 0; index--)
      {
         pFsmContext->state = stateHierarchyEntry[index];

         /* Now enter new state by calling entry handler */
         FblFsmHandleEvent(pFsmContext, (tFblFsmEvent)kFblFsmDefaultEvent_Entry, kFsmIterateHierarchy_Disabled);
      }

      /* Set final state, necessary if new state is super-state of previous state (no re-entry) */
      pFsmContext->state = pendingState;
   }
}

/**********************************************************************************************************************
 *  FblFsmTriggerEvent
 *********************************************************************************************************************/
/*! \brief        Triggers event for given state machine
 *  \param[in]    pFsmContext Pointer to context of state machine
 *  \param[in]    event The event to be triggered
 *********************************************************************************************************************/
void FblFsmTriggerEvent( tFblFsmContextPtr const pFsmContext, tFblFsmEvent event )
{
   pFsmContext->pendingEvent = event;
}

/**********************************************************************************************************************
 *  FblFsmEventHandlerDefault
 *********************************************************************************************************************/
/*! \brief        Default event handler
 *  \param[in]    pFsmContext Pointer to context of state machine
 *  \param[in]    event The event that triggered the handler execution
 *********************************************************************************************************************/
tFblFsmGuard FblFsmEventHandlerDefault( tFblFsmContextPtr const pFsmContext, tFblFsmEvent event ) /* PRQA S 3673 */ /* MD_MSR_Rule8.13 */
{
#if defined( V_ENABLE_USE_DUMMY_STATEMENT )
   (void)pFsmContext;
   (void)event;
#endif /* V_ENABLE_USE_DUMMY_STATEMENT */

   /* Do nothing, but allow state transition */

   return kFblFsmGuard_True;
}
#define FBLFSM_STOP_SEC_CODE
#include "MemMap.h" /* PRQA S 5087 */ /* MD_MSR_MemMap */

/**********************************************************************************************************************
 *  END OF FILE: FBL_FSM.C
 *********************************************************************************************************************/
