/*
 * Copyright (C) 2019 CERN (www.cern.ch)
 * Author: Jean-Claude BAU - CERN
 *
 * Released according to the GNU LGPL, version 2.1 or any later version.
 *
 *
 */

#ifndef GENERIC_FSM_H
#define GENERIC_FSM_H

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

#define FSM_GEN_DEBUG 0

#define FSM_SET_FCT_NAME(name) .handler=name, .fctName=#name

/*
 * Used to define the callback to call for a givent state
 */

struct fsm_s;

/* States used by the generic FSM */
typedef struct {
	int state;
	int nextState;
} fsm_state_t;

typedef int (*fsm_state_handler_t)( struct fsm_s* fsm, int eventMsk, int newState);
typedef int (*fsm_build_event_handler_t)( struct fsm_s *fsm );

typedef struct {
	int state;
	char * stateName;
	fsm_state_handler_t handler;
	char *fctName;
} fsm_state_table_entry_t;

typedef struct {
	int evtMask;
	char * evtName;
} fsm_event_table_entry_t;

/*
 * Structure containing all information needed by the state machine
 */

typedef struct fsm_s {
	char * fsm_name;
	fsm_build_event_handler_t build_events_handler;
	fsm_state_table_entry_t *state_table;
	fsm_event_table_entry_t *event_table;
	fsm_state_t st;
	int enabled;
	int lastEventMask;
	void *priv;
} fsm_t;


/* returns current state of the FSM */
static inline int fsm_get_state(fsm_t *pfg) 
{
	return pfg->st.state;
}

/* sets the current (immediate) state of the FSM */
static inline int fsm_set_state(fsm_t *pfg, int newState) 
{
	pfg->st.state = newState;
	return pfg->st.state;
}

/* gets the next (scheduled) state of the FSM */
static inline int fsm_get_next_state(fsm_t *pfg) 
{
	return pfg->st.nextState;
}

/* returns a human-readable state name for a given state */
static inline const char * fsm_get_state_name(fsm_t *pfg) 
{
	static const char *unk_msg = "<unknown>";
	int st = fsm_get_state(pfg);
	if (st < 0)
		return unk_msg;
	fsm_state_table_entry_t *pt = &pfg->state_table[ fsm_get_state(pfg) ];
	return pt->stateName;
}

static inline const char *fsm_get_event_mask_as_string(fsm_t *pfg)
{
	static char str[1024];
	fsm_event_table_entry_t *evt;

	strcpy(str, "");

	for( evt = pfg->event_table; evt->evtMask > 0; evt++ )
	{
		if (pfg->lastEventMask & evt->evtMask)
		{
			strcat(str, evt->evtName);
			strcat(str, " ");
		}
	}

	return str;
}


/* updates the current state of the FSM with the next (scheduled) state */
static inline void fsm_consume_state(fsm_t *pfg)
{
	pfg->st.state = fsm_get_next_state(pfg);
	if (FSM_GEN_DEBUG)
		printf("%s: Enter state %s\n", pfg->fsm_name, fsm_get_state_name(pfg));
}

/* schedules next state of the FSM to newState */
static inline void fsm_fire_state(fsm_t *pfg, int newState) 
{
	pfg->st.nextState=newState;
}

/* returns nonzero if there's a next (scheduled) state present */
static inline int fsm_has_pending_state(fsm_t *pfg) 
{
	return fsm_get_state(pfg) != fsm_get_next_state(pfg);
}

/* prototypes */

/* Does a single turn of the FSM */

fsm_t fsm_generic_create(
	const char *name,
	fsm_build_event_handler_t build_events_handler,
	fsm_state_table_entry_t *state_table,
	fsm_event_table_entry_t *event_table,
	void *priv);

int fsm_generic_run ( fsm_t *pfg );


#endif
