// Purpose.  State design pattern versus table-driven approach
//
// Intent.  Allow an object to alter its behavior when its internal state
// changes.  The object will appear to change its class.
//
// Discussion.  The State pattern models state-specific behavior (easy to
// add actions that accompany or augment the state transitions), whereas
// a table-driven approach focuses on defining state transitions.
//
// vi editor  escape  letterI  colon    cReturn  arrow
// States     ------  -------  -----    -------  -----
// Cmd        BEEP    Insert   CmdLine  nsc      nsc (no state change)
// Insert     Cmd     nsc      nsc      nsc      BEEP
// CmdLine    Cmd     nsc      nsc      Cmd      BEEP

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

class FSM;

// State base class.  Implements default behavior for all its methods.
class FSMstate {   // Could be: empty implementation, or error message.
public:
   virtual void escape(  FSM* )  { cout << "    no state change" << endl; }
   virtual void letterI( FSM* )  { cout << "    no state change" << endl; }
   virtual void colon(   FSM* )  { cout << "    no state change" << endl; }
   virtual void cReturn( FSM* )  { cout << "    no state change" << endl; }
   virtual void arrow(   FSM* )  { cout << "    no state change" << endl; }
protected:
   void changeState( FSM*, FSMstate* );
};

// State machine.  Reproduces the State interface, and delegates all
// behavior to the State derived class private data member.
class FSM {
public:
   FSM();
   void escape()   { _state->escape(  this ); }
   void letterI()  { _state->letterI( this ); }
   void colon()    { _state->colon(   this ); }
   void cReturn()  { _state->cReturn( this ); }
   void arrow()    { _state->arrow(   this ); }
private:
   friend class FSMstate;
   void changeState( FSMstate* s )  { _state = s; }   // page 310
   FSMstate*  _state;                                 // page 310
};

void FSMstate::changeState( FSM* fsm, FSMstate* s ) { // page 311
   fsm->changeState( s ); }

// State derived class.  Each state overrides only the messages it responds to.
class Cmd : public FSMstate {
public:                     
   static FSMstate* instance() {
      if ( ! _instance ) _instance = new Cmd;   return _instance; };
   virtual void escape( FSM* );
   virtual void letterI( FSM* );
   virtual void colon( FSM* );
private:
   static FSMstate* _instance; };
FSMstate* Cmd::_instance = 0;

class Insert : public FSMstate {  // State derived class
public:
   static FSMstate* instance() {
      if ( ! _instance ) _instance = new Insert;   return _instance; };
   virtual void escape( FSM* );
   virtual void arrow( FSM* );
private:
   static FSMstate* _instance; };
FSMstate* Insert::_instance = 0;

class CmdLine : public FSMstate {  // State derived class
public:
   static FSMstate* instance() {
      if ( ! _instance ) _instance = new CmdLine;   return _instance; };
   virtual void escape( FSM* );
   virtual void cReturn( FSM* );
   virtual void arrow( FSM* );
private:
   static FSMstate* _instance; };
FSMstate* CmdLine::_instance = 0;

void Cmd::escape( FSM* fsm ) { cout << "    BEEP" << endl; }
void Cmd::letterI( FSM* fsm ) {
   cout << "Insert:" << endl;
   changeState( fsm, Insert::instance()); };
void Cmd::colon( FSM* fsm ) {
   cout << "CmdLine:" << endl;
   changeState( fsm, CmdLine::instance()); };
void Insert::escape( FSM* fsm ) {
   cout << "Cmd:" << endl;
   changeState( fsm, Cmd::instance()); };
void Insert::arrow( FSM* fsm ) { cout << "    BEEP" << endl; }
void CmdLine::escape( FSM* fsm ) {
   cout << "Cmd:" << endl;
   changeState( fsm, Cmd::instance()); };
void CmdLine::cReturn( FSM* fsm ) {
   cout << "Cmd:" << endl;
   changeState( fsm, Cmd::instance()); };
void CmdLine::arrow( FSM* fsm ) { cout << "    BEEP" << endl; }

// Start state is Cmd
FSM::FSM() {
   cout << "Cmd:" << endl;
   changeState( Cmd::instance() );
}

void main() {
   FSM   vi;
   char  input[20];

   while (1) {
      cout << "  ";                      cin >> input;
      if ( ! strcmp(input,"esc"))        vi.escape();
      else if ( ! strcmp(input,"i"))     vi.letterI();
      else if ( ! strcmp(input,":"))     vi.colon();
      else if ( ! strcmp(input,"cr"))    vi.cReturn();
      else if ( ! strcmp(input,"arr"))   vi.arrow(); }
}

// 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



// 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}, };

void 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
