// Purpose.  Visitor design pattern lab
//
// Problem.  "Open for extension, closed for modification" is a dominant
// principle of object-oriented design.  The Composite design pattern is an
// excellent strategy for designing recursively, or hierarchically, related
// collections of objects.  Once the Composite hierarchy is produced, adding
// new methods (that operate on the hierarchy) requires either: violating the
// "open-closed" principle, or, writing client code that has to do "type
// checking" and "type casting" in order to "recover lost type information".
// This latter approach is modeled below in the cat() and wc() methods of
// class Client.  The Visitor pattern specifies that a single access method
// be added to the Composite hierarchy.  Once this is done, many new methods
// can be added in the future, the "open-closed" principle can be enforced,
// and, the ugly code for "recovering lost type information" can be avoided.
//
// Assignment.
// o Define a Visitor base class with 3 pure virtual member functions:
//      void visit( Primitive* ) = 0;
//      void visit( Link* )      = 0;
//      void visit( Composite* ) = 0;
// o Define 2 derived classes of Visitor (CatVisitor and WcVisitor) that
//   encapsulate the ugly code in class Client.  The implementation of each of
//   the overloaded visit() methods will be the body of one of the conditional
//   clauses of the existing cat() and wc() methods.
// o Add a pure virtual member function to Component:
//      void accept( Visitor& ) = 0;
// o Define accept() in all 3 Component derived classes as:
//      void accept( Visitor& v ) { v.visit( this ); }
// o Create CatVisitor and WcVisitor objects in main().
// o The body of main()'s "for" loop will become:
//      array[i]->accept( aCatObj );
//      array[i]->accept( aWcObj );
// o Get rid of compType, returnType(), Client.

#include <iostream.h>
enum compType { PrimitiveT, LinkT, CompositeT };

class Component {
public:
   virtual compType  returnType()       = 0;
   virtual void      streamOut()        = 0;
   virtual void      add( Component* )  { };
};

class Primitive : public Component {
public:
   Primitive( int id )   { identity = id; }
   compType returnType() { return PrimitiveT; }
   void     streamOut()  { cout << identity << " "; }
private:
   int identity;
};

class Link : public Component {
public:
   Link( Component& ele ) : linkElement(ele)  { }
   compType   returnType()                    { return LinkT; }
   void       streamOut()                     { linkElement.streamOut(); }
   Component& getSubject()                    { return linkElement; }
private:
   Component& linkElement;
};

class Composite : public Component {
public:
   Composite()                    { index = 0; }
   compType returnType()          { return CompositeT; }
   void     add( Component* ele ) { array[index++] = ele; }
   void     streamOut() {
      for (int i=0; i < index; i++)
         array[i]->streamOut();
   }
private:
   int         index;
   Component*  array[20];
};

class Client {
public:
   void cat( Component* node ) {
      if (node->returnType() == PrimitiveT) {
         cout << "cat: ";   node->streamOut();   cout << endl;
      } else if (node->returnType() == CompositeT)
         cout << "cat: Can't cat a directory." << endl;
      else if (node->returnType() == LinkT)
         cat(&(((Link*)node)->getSubject()));
   }
   void wc( Component* node ) {
      if (node->returnType() == PrimitiveT) {
          cout << "wc: ";   node->streamOut();   cout << endl;
      } else if (node->returnType() == CompositeT)
         cout << "wc: Can't wc a directory." << endl;
      else if (node->returnType() == LinkT)
         wc(&(((Link*)node)->getSubject()));
   }
};

void main( void ) {
   Composite        dir;
   Primitive        file1(1), file2(2);
   Link             alias( file1 );
   Component*       array[4];
   Client           doFile;
                   
   dir.add( &file1 );
   dir.add( &file2 );
   dir.add( &alias );
   dir.streamOut();
   cout << endl;

   array[0] = &dir;
   array[1] = &file1;
   array[2] = &file2;
   array[3] = &alias;
   for (int i = 0; i < 4; i++) {
      doFile.cat( array[i] );
      doFile.wc( array[i] );
}  }

// 1 2 1
// cat: Can't cat a directory.
// wc: Can't wc a directory.
// cat: 1
// wc: 1
// cat: 2
// wc: 2
// cat: 1
// wc: 1