// Purpose.  Table-driven approach to state-based design
//
// Discussion.  Cargill ("C++ Programming Style") uses a table-driven
// approach to impose structure on state-driven code.  For each state, a
// table maps every possible input to a succeeding state.  In effect, this
// approach converts conditional code (and virtual functions, in the case of
// the State pattern) into a table look-up.  The main advantage of tables is
// their regularity: you can change the transition criteria by modifying data
// instead of changing code.  The main disadvantages are:  table look-up is
// less efficient than a virtual function call, the transition logic is less
// explicit (harder to understand), and it is difficult to add actions to
// accompany the state transitions.  The key difference between the two is:
// the State pattern models state-specific behavior, whereas the table-driven
// approach focuses on defining state transitions.

#include <iostream.h>
#include <string.h>

enum States { Cmd, Insert, CmdLine };
enum Events { Escape, LetterI, Colon, CR, Arrow };
class State;
typedef void (State::* Action)();
#define MAX_STATES 3
#define MAX_EVENTS 5

class State {
public:
   State( char* name ) { strcpy( name_, name ); }
   void enter() { cout << name_ << ":" << endl; }
   void beep()  { cout << "    BEEP" << endl; }
   void nsc()   { cout << "    no state change" << endl; }
protected:
   char    name_[10];
private:
   friend  class FSM;
   State*  transition_[MAX_EVENTS];     //////// Here is the "table" /////////
   Action  before_[MAX_EVENTS];         //////// Here is the "table" /////////
   Action  after_[MAX_EVENTS];          //////// Here is the "table" /////////
};

struct Mapping {
   States  from;
   Events  input;
   States  to;
   Action  before;
   Action  after;
};

class FSM {
public:
   FSM( Mapping* map) {
      graph_[0] = new State( "Cmd" );
      graph_[1] = new State( "Insert" );
      graph_[2] = new State( "CmdLine" );
      for (int i=0;i<MAX_STATES * MAX_EVENTS; i++, map++) {
         (graph_[map->from])->transition_[map->input] = graph_[map->to];
         (graph_[map->from])->before_[map->input]     = map->before;
         (graph_[map->from])->after_[map->input]      = map->after; }
      current_ = graph_[0];
      current_->enter();
   }
   //////// traverse the table ////////
   void next( char* input ) {
      Action action;
      //////// Do "before" action ////////
      action = current_->before_[convert(input)];
      if (action != NULL) (current_->*action)();
      //////// Prepare for "after" action ////////
      action = current_->after_[convert(input)];
      //////// Do state transition ////////
      current_ = current_->transition_[convert(input)];
      //////// Do "after" action ////////
      if (action != NULL) (current_->*action)(); }
private:
   State*  graph_[MAX_STATES];
   State*  current_;
   Events convert( char* input ) {
      if ( ! strcmp( input, "i" )) return LetterI;
      else if ( ! strcmp( input, ":" )) return Colon;
      else if ( ! strcmp( input, "arr" )) return Arrow;
      else if ( ! strcmp( input, "esc" )) return Escape;
      else if ( ! strcmp( input, "cr" )) return CR;
      else return CR; }
};

class FsmSample : public FSM {
public:
   FsmSample() : FSM( cells ) { }
private:
   static Mapping cells[ MAX_STATES * MAX_EVENTS ];
};

//////// Here is the table "data" ////////
/* static */ Mapping FsmSample::cells[] = {
   {Cmd,     Escape,  Cmd,     &State::beep, NULL},
   {Cmd,     LetterI, Insert,  NULL,         &State::enter},
   {Cmd,     Colon,   CmdLine, NULL,         &State::enter},
   {Cmd,     CR,      Cmd,     &State::nsc,  NULL},
   {Cmd,     Arrow,   Cmd,     &State::nsc,  NULL},
   {Insert,  Escape,  Cmd,     NULL,         &State::enter},
   {Insert,  LetterI, Insert,  &State::nsc,  NULL},
   {Insert,  Colon,   Insert,  &State::nsc,  NULL},
   {Insert,  CR,      Insert,  &State::nsc,  NULL},
   {Insert,  Arrow,   Insert,  &State::beep, NULL},
   {CmdLine, Escape,  Cmd,     NULL,         &State::enter},
   {CmdLine, LetterI, CmdLine, &State::nsc,  NULL},
   {CmdLine, Colon,   CmdLine, &State::nsc,  NULL},
   {CmdLine, CR,      Cmd,     NULL,         &State::enter},
   {CmdLine, Arrow,   CmdLine, &State::beep, NULL}, };

main()
{
   FsmSample  fsm;
   char       input[20];

   while (1) {
      cout << "  ";
      cin >> input;
      fsm.next( input ); }
}

// Cmd:                       CmdLine:
//   i                          arr
// Insert:                        BEEP
//   :                          i
//     no state change            no state change
//   arr                        cr
//     BEEP                   Cmd:
//   esc                        cr
// Cmd:                           no state change
//   :                          esc
//                                BEEP

