// Purpose......State design pattern demo
// 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() );
}

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

