module visitor;

private import std.stdio;
//private import std.conv;

class NodeBase
{
  protected:
    NodeBase[] mInputs;
    char[]     mLabel;

  public:
    this(char[] label)
    {
      mLabel = label;
    }
  
    NodeBase[] inputs()
    {
      return mInputs;
    }
    
    char[] label()
    {
      return mLabel;
    }
  
    // add Nodes
    void append(NodeBase node)
    {
      mInputs ~= node;
    }

    // visitor support
    bool accept(Visitor visitor)   {  return visitor.visitBase(this);  }
}

class A : NodeBase
{
  public:
    this(char[] label)
    {
      super(label);
    }
  
    bool accept(Visitor visitor)   
    {  
      bool result = visitor.visitA(this);  
      if (!result)
      {
        // fallback to base class
        result = super.accept(visitor);
      }
      return result;
    }
}

class B : NodeBase
{
  public:
    this(char[] label)
    {
      super(label);
    }
    
    bool accept(Visitor visitor)
    {  
      bool result = visitor.visitB(this);  
      if (!result)
      {
        // fallback to base class
        result = super.accept(visitor);
      }
      return result;
    }
}

// visitor base class
abstract class Visitor
{
  // special method for NodeBase 
  bool visitBase(NodeBase base) { return true; }

  // return false to call accept() of base class
  bool visitA(A a) { return false; }
  bool visitB(B b) { return false; }

  // for edges
  void visitEdge(NodeBase from, NodeBase to, uint edgepos)
  {
    // do nothing
  }
};

// Node filter used to log already visited nodes
class NodeFilter
{
  protected:
    NodeBase[NodeBase] mVisitedNodes;

  public:
    bool isFiltered(NodeBase node)
    {
      return (null != (node in mVisitedNodes));
    }

    bool add(NodeBase node)
    {
      bool result = false;
      if (!(node in mVisitedNodes))
      {
        mVisitedNodes[node] = node;
        result = true;
      }
      return result;
    }
};

// Preorder visitor
class PreOrderVisitor : Visitor
{
  protected:
    NodeFilter mFilter;
    Visitor    mOperation;

  public:
    this(Visitor operation)
    {
      mFilter    = new NodeFilter;
      mOperation = operation;
    }

    bool visitBase(NodeBase      node)
    {
      bool bNotFiltered = mFilter.add(node);
      if (bNotFiltered)
      {
        node.accept(mOperation);

        foreach(int i, NodeBase dest; node.inputs)
        {
					if (null != dest)
					{
						dest.accept(this);
            
            // visit edge
   					mOperation.visitEdge(node, dest, i);
					}
        }
      }
      return bNotFiltered;
    }
}

// Print visitor
class PrintVisitor : Visitor
{
  bool visitA(A a)
  {
    writefln("A: ", a.label);
    return true;
  }

  bool visitB(B b)
  {
    writefln("B: ", b.label);
    return true;
  }
  
  void visitEdge(NodeBase from, NodeBase to, uint edgepos)
  {
    writefln("  ", from.label, "[", std.string.toString(edgepos), "] -> ", to.label);
  }
}

void main()
{
  // create graph:
  //
  //        node1 (A)
  //        /   \
  //     node2  node3 (B)
  //        \   /
  //        node4 (A)
  //
  A node1 = new A("node1");
  B node2 = new B("node2");
  B node3 = new B("node3");
  A node4 = new A("node4");
  
  node2.append(node1);
  node3.append(node1);
  node4.append(node2);
  node4.append(node3);

  // create visitor chain
  auto PrintVisitor printvisitor = new PrintVisitor();
  PreOrderVisitor   preorderVisitor = new PreOrderVisitor(printvisitor);
  
  // traverse
  node4.accept(preorderVisitor);
} 
