/* ---------------------------------------------------------------------
   (c) ED 1998-2008
   Project      : CLIB
   Function     : Finite State Machine (FSM) manager
   Module       : CL_FSM
   File         : CL_FSM.C
   Created      : 15-02-1998
   Modified     : 20-05-2008
   --------------------------------------------------------------------- */
/* *INDENT-OFF* */
/* ---------------------------------------------------------------------
   Log

    2.15 20-05-2008 'this' becomes 'self' (C++ compatible)
    2.14 04-12-2003 Implementation : explicit pointer to functions removed on
                    interface.
    2.13 28-05-2003 Reset command 'FSM_EVT_RESET' added to FSM_engine().
    2.12 15-04-2003 Cast added for '-1'. Minor comments fixes
    2.11 16-10-2002 Translated in English. Layout
                    FSM_list() debugging.
                    Initial state added to FSM_init().
                    Error status added to FSM_transition()
                    info management redesigned
    2.10 19-09-2002 "sysalloc.h" removed from header. sys_*() removed
    2.9  13-07-2002 LCLint
    2.8  01-07-2002 Object renamed in FSM
    2.7  25-04-2002 Object renamed in CL_FSM
    2.6  20-02-2002 Static version
                   Define the FSM_DYN macro for a dynamic version
                  'pFsm' becomes 'this'
    2.5  03-09-2001 Macro FSM_MEMCHK added (well, supposed to be...)
    2.4  18-01-2001 sELEM becomes sFSM_ELEM
    2.3  16-01-2001 Data become public.
    2.2  16-11-2000 Function FSM_dbg_str_dyn() added
                   sys_calloc() replaced by sys_malloc()
    2.1  31-05-2000 Function FSM_info() added
    2.0  20-03-2000 Data become private. Cloning
    1.9  07-12-1999 szTag = "ERR" on error
    1.8  04-12-1999 AUT becomes FSM
    1.7  13-10-1999 calloc() and free() replaced by sys_*
    1.6  06-10-1999 <aut.h> becomes "aut.h"
    1.5  15-01-1999 The context is passed in the debug transition
    1.4  16-12-1998 Port to Win32 (return types changed from WORD to int)
    1.3  08-09-1998 Integration to the CLIB project
    1.2  26-05-1998 Adaptation to H.320
    1.1  15-02-1998 Data context added
    1.0  15-02-1998 Initial version
    0.0  15-02-1998 Creation

   Internal organisation of the FSM matrix:

       <-------------nb_evt----------->
       |  ev0  |  ev1  |  ev2  |  ev3  |
   ----|-------|-------|-------|-------|
   st0 |sts,act|sts,act|sts,act|sts,act|\
   st1 |sts,act|sts,act|sts,act|sts,act| nb_sts
   st2 |sts,act|sts,act|sts,act|sts,act|/
   ----|-------|-------|-------|-------|

   --------------------------------------------------------------------- */
/* *INDENT-ON* */

#ifdef __cplusplus
#error This source file is not C++ but rather C. Please use a C-compiler
#endif

#include "ed/inc/fsm.h"
#include "ed/inc/sys.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>

#ifdef FSM_DYN
#include "ed/inc/sysalloc.h"
#endif

/* public data ========================================================= */
/* macros ============================================================== */

#define DBG 1                   /* 0=normal | 1=debug */

#ifdef FSM_DYN
#define DYN " DYN"
#else
#define DYN ""
#endif

#ifdef FSM_LIGHT
#define LIGHT " LIGHT"
#else
#define LIGHT ""
#endif

#define VER "2.14" DYN LIGHT

#define ID "FSM Module \"C\" (c) ED 1998-2008"

/* Nothing to do, no change... */
#define NOP ((size_t) -1)

/* constants =========================================================== */

/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   GetAdr()
   ---------------------------------------------------------------------
   Role : Retourne l'adresse d'un element en fonction de ses coordonnees
   ---------------------------------------------------------------------
   E : contexte
   E : etat
   E : evenement
   S : Pointeur de chaine ASCIIZ
   --------------------------------------------------------------------- */
static sFSM_ELEM *GetAdr (sFSM * const this
                          ,size_t const sts
                          ,size_t const evt)
{
   sFSM_ELEM *p_elem;

   STACK_CHK ();

   ASSERT (this != NULL);
   ASSERT (evt < this->Nb_evt);
   ASSERT (sts < this->Nb_sts);

   {
      size_t const i_evt = evt;
      size_t const i_sts = sts * this->Nb_evt;
      size_t const i = i_sts + i_evt;

      ASSERT (i < (this->Nb_sts * this->Nb_evt));

      p_elem = this->tab + i;

      ASSERT (p_elem != NULL);
      ASSERT (p_elem < this->tab + (this->Nb_sts * this->Nb_evt));
   }
   return p_elem;
}

/* ---------------------------------------------------------------------
   info_init()
   ---------------------------------------------------------------------
   Role : inits the 'info' context
   ---------------------------------------------------------------------
   E : info context
   S :
   --------------------------------------------------------------------- */
static void info_init (sFSM_INFO * const this)
{
   STACK_CHK ();
   this->Ini = (uint) -1;
   this->Fin = (uint) -1;
   this->Evt = (uint) -1;
   this->szTag = NULL;
   this->pData = NULL;
}

/* internal public data ================================================ */
/* internal public functions =========================================== */
/* entry points ======================================================== */

/* ---------------------------------------------------------------------
   FSM_sver()
   ---------------------------------------------------------------------
   Role : Returns a "Version" string
   ---------------------------------------------------------------------
   E :
   S : constant string pointer
   --------------------------------------------------------------------- */
const char *FSM_sver (void)
{
   STACK_CHK ();
   return VER;
}

/* ---------------------------------------------------------------------
   FSM_sid()
   ---------------------------------------------------------------------
   Role : Returns an "Identification" string
   ---------------------------------------------------------------------
   E :
   S : constant string pointer
   --------------------------------------------------------------------- */
const char *FSM_sid (void)
{
   STACK_CHK ();
   return ID;
}

/* ---------------------------------------------------------------------
   FSM_mode()
   ---------------------------------------------------------------------
   Role : Returns the compile mode
   ---------------------------------------------------------------------
   E :
   S : compile mode :
   .   - FSM_MODE_STA
   .   - FSM_MODE_DYN
   --------------------------------------------------------------------- */
eFSM_MODE FSM_mode (void)
{
   STACK_CHK ();
#ifdef FSM_DYN
   return FSM_MODE_DYN;
#else
   return FSM_MODE_STA;
#endif
}

#ifdef FSM_DYN
/* ---------------------------------------------------------------------
   FSM_init()
   ---------------------------------------------------------------------
   Creation and Initialisation (Dynamic)
   ---------------------------------------------------------------------
   E : events number
   E : status number
   E : debug function
   E : initial state
   S : object pointer
   --------------------------------------------------------------------- */
sFSM *FSM_init (size_t const Nb_evt
                ,size_t const Nb_sts
                ,fFSM_DEBUG * const cb_debug
                ,size_t const initial_state
)
#else
/* ---------------------------------------------------------------------
   FSM_init()
   ---------------------------------------------------------------------
   Role : Initialisation (Static)
   ---------------------------------------------------------------------
   E : object pointer
   E : FSM matrix address (1D matrix)
   E : events number
   E : status number
   E : debug function
   E : initial state
   S : object pointer
   --------------------------------------------------------------------- */
void FSM_init (sFSM * const this
               ,sFSM_ELEM * const tab
               ,size_t const Nb_evt
               ,size_t const Nb_sts
               ,fFSM_DEBUG * const cb_debug
               ,size_t const initial_state
)
#endif
{
#ifdef FSM_DYN
   sFSM *this = NULL;
#endif

   STACK_CHK ();
   ENUM_CHECK ();

#ifdef FSM_DYN
   this = malloc (sizeof *this);
#endif

   if (this)
   {
      /* tout a 0 */
      SYS_CLR (this, sFSM);

      GARBAGE (this);

      {
         size_t size = Nb_evt * Nb_sts;
#ifdef FSM_DYN
         sFSM_ELEM *tab = NULL;

         this->tab = NULL;

         tab = malloc (size * sizeof *tab);
#endif

         if (tab != NULL)
         {

            /* Tous les sts a NOP */
            {
               size_t i;

               for (i = 0; i < size; i++)
               {
                  sFSM_ELEM *const p_elem = tab + i;

                  /* configuration */
                  p_elem->sts = NOP;
                  p_elem->pfTransition = NULL;
                  p_elem->stag = NULL;
               }
            }

            /* Enregistrer les parametres */
            this->Nb_evt = Nb_evt;
            this->Nb_sts = Nb_sts;
            this->sts0 = initial_state;
            this->sts = this->sts0;
            this->cb_debug = cb_debug;
            this->tab = tab;
#ifdef FSM_DYN
            this->dyn = 1;
#else
            this->dyn = 0;
#endif
            this->stag = NULL;
            this->Tra = 0;      /* transitions counter */
            this->last_err = FSM_OK;

            info_init (&this->info);
         }

#ifdef FSM_DYN
         else
         {
            FSM_end (this);
            this = NULL;
         }
#endif

      }
   }
#ifdef FSM_DYN
   return this;
#endif
}

/* ---------------------------------------------------------------------
   FSM_clone()
   ---------------------------------------------------------------------
   Role : Clonage d'un objet existant
   ---------------------------------------------------------------------
   E : Objet de reference
   S : Objet cree
   --------------------------------------------------------------------- */
#ifdef FSM_DYN
sFSM *FSM_clone (sFSM const *const pFsmRef)
#else
void FSM_clone (sFSM * const this, sFSM const *const pFsmRef)
#endif
{
#ifdef FSM_DYN
   sFSM *this = NULL;
#endif

   STACK_CHK ();

   if (pFsmRef)
   {
#ifdef FSM_DYN
      this = malloc (sizeof *this);
#endif

      if (this)
      {
         /* all to 0 */
         SYS_CLR (this, sFSM);

         GARBAGE (this);

         /* cloning */
         if (pFsmRef->tab)
         {
            /* copy */
            *this = *pFsmRef;

            /* I'm only a clone!
             * I'm not able to free the FSM matrix
             */
            this->dyn = 0;

         }
#ifdef FSM_DYN
         else
         {
            FSM_end (this);
            this = NULL;
         }
#endif
      }
   }
#ifdef FSM_DYN
   return this;
#endif
}

/* ---------------------------------------------------------------------
   FSM_free()
   ---------------------------------------------------------------------
   Role : Suppression
   ---------------------------------------------------------------------
   E : Pointeur de donnees
   S :
   --------------------------------------------------------------------- */
void FSM_end (sFSM * const this)
{
   STACK_CHK ();
#ifdef FSM_DYN
   if (this != NULL)
   {
      if (this->tab)
      {
         if (this->dyn)
         {
            free (this->tab);
         }
         this->tab = NULL;
      }
      free (this);
   }
#else
   (void) this;
#endif
}

/* ---------------------------------------------------------------------
   FSM_transition()
   ---------------------------------------------------------------------
   Role : Definition of a transition
   ---------------------------------------------------------------------
   E : Object
   E : associated action
   E : transition name
   E : Current status
   E : Next status
   E : Event
   S : err :
   .   - 0 = OK
   .   - 1 = error
   --------------------------------------------------------------------- */
int FSM_transition (sFSM * const this
                    ,fFSM_TRANSITION Transition
                    ,char const *const stag
                    ,size_t const Curr
                    ,size_t const Next
                    ,size_t const Evt)
{
   int err = 0;
   STACK_CHK ();

   if (this != NULL)
   {
      this->last_err = FSM_OK;

      ASSERT (Curr < this->Nb_sts);

      if (Curr < this->Nb_sts)
      {
         if (Next != NOP)
         {
            ASSERT (Next < this->Nb_sts);

            if (Next < this->Nb_sts)
            {
            }
            else
            {
               this->last_err = FSM_ERR_STATUS_TOO_LARGE;
            }
         }

         if (this->last_err == FSM_OK)
         {
            ASSERT (Evt < this->Nb_evt);
            if (Evt < this->Nb_evt)
            {
               ASSERT (this->Tra < this->Nb_evt * this->Nb_sts);

               if (this->Tra < this->Nb_evt * this->Nb_sts)
               {
                  /* Count the transitions */
                  this->Tra++;
                  {
                     /* calcul de l'adresse de l'element d'automate */
                     sFSM_ELEM *p_elem = GetAdr (this, Curr, Evt);

                     if (Next != NOP)
                     {
                        ASSERT (p_elem->sts == NOP);
                        ASSERT (p_elem->stag == NULL);
                        ASSERT (p_elem->pfTransition == NULL);

                        if (p_elem->sts == NOP
                            && p_elem->stag == NULL
                            && p_elem->pfTransition == NULL)
                        {
                        }
                        else
                        {
                           this->last_err = FSM_ERR_TRANS_ALREADY_DEFINED;
                        }
                     }

                     /* Modification de l'element d'automate */
                     if (this->last_err == FSM_OK)
                     {
                        /* Etat suivant */
                        p_elem->sts = Next;

                        /* Nom de la transition */
                        p_elem->stag = stag;

                        /* Transition */
                        p_elem->pfTransition = Transition;
                     }
                  }
               }
               else
               {
                  this->last_err = FSM_ERR_ARRAY_TOO_SMALL;
               }
            }
            else
            {
               this->last_err = FSM_ERR_EVENT_TOO_LARGE;
            }
         }
      }
      else
      {
         this->last_err = FSM_ERR_STATUS_TOO_LARGE;
      }
      err = this->last_err != FSM_OK;
   }
   return err;
}

/* ---------------------------------------------------------------------
   FSM_engine()
   ---------------------------------------------------------------------
   Role : Traitement d'un evenement (Moteur de l'automate)
   ---------------------------------------------------------------------
   E : Pointeur d'automate
   E : Evenement
   E : Pointeur de donnees associees a l'evenement (strictement applicatif)
   S : -1 : transition non autorisee. Autre : code retour de l'action
   --------------------------------------------------------------------- */
int FSM_engine (sFSM * const this
                ,size_t const evt
                ,void *const p_user)
{
   int cr = -1;                 /* default or context error status */
   STACK_CHK ();

   if (this != NULL)
   {
      ASSERT (this->sts < this->Nb_sts);

      if (evt < this->Nb_evt)
      {
         /* recuperer l'adresse de l'element d'automate */
         sFSM_ELEM *p_elem = GetAdr (this, this->sts, evt);
         /* save the current status (for info) */
         size_t cur_sts = this->sts;

         /* If required, state change */
         if (p_elem->sts != NOP)
         {
            this->sts = p_elem->sts;
         }

         /* Record the name of the transition */
         if (p_elem->stag)
         {
            this->stag = p_elem->stag;
         }
         else
         {
            this->stag = "ERR";
         }

         /* callback (debug...) */
         if (this->cb_debug)
         {
            sFSM_INFO *pInfo = &this->info;

            /* Store the initial status (FROM) */
            pInfo->Ini = cur_sts;

            pInfo->Evt = evt;
            pInfo->szTag = p_elem->stag;
            pInfo->pData = p_user;

            /* Store the final status (TO) */
            pInfo->Fin = this->sts;

            /* LCLint */
            (*this->cb_debug) (pInfo);
         }

         /* transition */
         if (p_elem->pfTransition)
         {
            /* LCLint */
            cr = (*p_elem->pfTransition) (p_user);
         }
      }
      else
      {
         if (evt == FSM_EVT_RESET)
         {
            /* reset */
            this->sts = this->sts0;
            cr = 0;
         }
      }
   }

   return cr;
}

/* ---------------------------------------------------------------------
   FSM_last_err()
   ---------------------------------------------------------------------
   Role : Get the last error
   ---------------------------------------------------------------------
   E : Objet
   S :
   --------------------------------------------------------------------- */
eFSM_ERR FSM_last_err (sFSM const *const this)
{
   eFSM_ERR err = (eFSM_ERR) - 1;

   if (this)
   {
      err = this->last_err;
   }
   return err;
}

#ifndef FSM_LIGHT

/* ---------------------------------------------------------------------
   FSM_list()
   ---------------------------------------------------------------------
   Role : Liste la structure du tableau vers stdout
   ---------------------------------------------------------------------
   E : Pointeur d'automate
   --------------------------------------------------------------------- */
void FSM_list (sFSM * const this)
{

   STACK_CHK ();
#if DBG
#define FMT "%-20s"

   printf (FMT "             : sts  -> sts  / evt\n", "Transitions");

   if (this)
   {
      size_t nb_tra = 0;
      size_t sts;

      for (sts = 0; sts < this->Nb_sts; sts++)
      {
         size_t evt;
         for (evt = 0; evt < this->Nb_evt; evt++)
         {
            /* calcul de l'adresse de l'element d'automate */
            sFSM_ELEM *p_elem = GetAdr (this, sts, evt);

            if (!(p_elem->sts == NOP
                  && p_elem->pfTransition == NULL)
               )
            {
               printf (FMT " %04X -> %04X / %04X\n"
                       ,(char const *) p_elem->stag
                       ,(uint) sts
                       ,(uint) p_elem->sts
                       ,(uint) evt
                  );
               nb_tra++;
            }
         }
      }

      printf ("%u active transition%s on %u possible\n"
              ,(uint) nb_tra
              ,(nb_tra > 1) ? "s" : ""
              ,(uint) (this->Nb_sts * this->Nb_evt)
         );

      printf ("\n"
              "List of inactive transitions:"
              "\n");

      nb_tra = 0;

      for (sts = 0; sts < this->Nb_sts; sts++)
      {
         size_t evt;
         for (evt = 0; evt < this->Nb_evt; evt++)
         {
            /* calcul de l'adresse de l'element d'automate */
            sFSM_ELEM *p_elem = GetAdr (this, sts, evt);

            if (p_elem->sts == NOP
                && p_elem->pfTransition == NULL
               )
            {
               printf ("Sts_%u.Evt_%u\n"
                       ,(uint) sts
                       ,(uint) evt
                  );
               nb_tra++;
            }
         }
      }

      printf ("%u inactive transition%s on %u possible\n"
              ,(uint) nb_tra
              ,(nb_tra > 1) ? "s" : ""
              ,(uint) (this->Nb_sts * this->Nb_evt)
         );
   }
#undef FMT
#else
   (void) this;
#endif
}

/* ---------------------------------------------------------------------
   FSM_info()
   ---------------------------------------------------------------------
   Role : Lire l'etat courant (DEBUG)
   ---------------------------------------------------------------------
   E : Objet
   E : Infos
   S :
   --------------------------------------------------------------------- */
void FSM_info (sFSM const *const this, sFSM_INFO * const pInfo)
{
   STACK_CHK ();

   if (pInfo)
   {
      if (this != NULL)
      {
         *pInfo = this->info;
      }
   }
}

#ifdef FSM_DYN
/* ---------------------------------------------------------------------
   FSM_dbg_str_dyn()
   ---------------------------------------------------------------------
   Role : Fabriquer une chaine de debug (DEBUG). Chaine dynamique
   ---------------------------------------------------------------------
   E : Infos
   S : Chaine
   --------------------------------------------------------------------- */
char *FSM_dbg_str_dyn (sFSM_INFO const *const pInfo)
{
#define FMT "%u -> %u / %u (%s)"
#define ERR "ERR"
   size_t const len = (3 * strlen (DEC_INTEGER_MAX))
   + (pInfo->szTag ? strlen (pInfo->szTag) : 0)
   + sizeof (FMT)
    ;
   char *s = malloc (len);

   STACK_CHK ();

   if (s != NULL)
   {
      size_t n = sprintf (s, FMT
                          ,(uint) pInfo->Ini
                          ,(uint) pInfo->Fin
                          ,(uint) pInfo->Evt
                          ,(pInfo->szTag ? pInfo->szTag : ERR)
      );
      ASSERT (n < len);
      (void) n;
   }
#undef ERR
#undef FMT
   return s;
}
#endif

/* ---------------------------------------------------------------------
   FSM_dbg_str()
   ---------------------------------------------------------------------
   Role : Fabriquer une chaine de debug (DEBUG).
   Chaine fournie par l'utilisateur
   ---------------------------------------------------------------------
   E : Infos
   S : Chaine
   --------------------------------------------------------------------- */
void FSM_dbg_str (sFSM_INFO const *const pInfo, char *s, size_t const size)
{
#define FMT "%u -> %u / %u (%s)"
#define ERR "ERR"
   size_t const len = (3 * strlen (DEC_INTEGER_MAX))
   + (pInfo->szTag ? strlen (pInfo->szTag) : 0)
   + sizeof (FMT)
    ;

   STACK_CHK ();

   if (s != NULL && size >= len)
   {
      sprintf (s, FMT
               ,(uint) pInfo->Ini
               ,(uint) pInfo->Fin
               ,(uint) pInfo->Evt
               ,(pInfo->szTag ? pInfo->szTag : ERR)
         );
   }
   else
   {
      *s = 0;
      strncat (s, ERR, size);
   }
#undef ERR
#undef FMT
}

/* ---------------------------------------------------------------------
   FSM_last_err_str()
   ---------------------------------------------------------------------
   Role : Get the last error string
   ---------------------------------------------------------------------
   E : Objet
   S :
   --------------------------------------------------------------------- */
char const *FSM_last_err_str (sFSM const *const this)
{
   char const *serr = NULL;
   static char const *const a_serr[] =
   {
      "FSM_OK",
#define ITEM(e, s) \
   #s,
#include "ed/inc/fsm_err.itm"
#undef ITEM
   };

   STACK_CHK ();

   if (this)
   {
      eFSM_ERR err = FSM_last_err (this);
      if (err < FSM_ERR_NB)
      {
         serr = a_serr[err];
      }
      else
      {
         serr = "Unknown error";
      }
   }
   return serr;
}
#endif /* /FSM_LIGHT */

/* File generated by 'NEW.EXE' Ver 1.2 (c) ED 1998 */
