#include "CVisitor.h"

#include <iostream>
#include <vector>
#include <string>


// a helper class to assign right tag for a class
// each class in the heirarchy needs to have this as 
// the last base class in its base class list
template< typename Base, typename Derv  >
struct Tagged { 
Tagged() 
{ 
	Base* b = static_cast< Derv* >( this );
	b->__set_tag( Dispatch< Base >::Tag< Derv >() ); 
}
};

class Shape : Tagged< Shape, Shape >  {
public:

Shape() {}

// the root class in the heirarchy
// needs to provide this function
void __set_tag( size_t t ) { tag_ = t; }
size_t __tag() const { return tag_; }

virtual ~Shape() { }

private:
// storage for the tag
	size_t tag_;
};

struct Curve {
	size_t data[ 1000 ];
	virtual ~Curve() {}
};

// an example for multiple inheritance: Curve is another polymorphic base
class Circle : public Curve , public Shape, Tagged< Shape, Circle > {
	float radius_;
public:

Circle( float r ) 
: radius_( r ){}

float radius() const { return radius_; }
};


 
class Rect : public Shape, Tagged< Shape, Rect > {
public:
Rect() {}
};


// a typedef to help ease implementing visitor
// a visit method is defined with this type to be of form
// std::ostream& visit( Shape*, std::ostream& )
// the first argument is not specified
typedef Dispatch< Shape >::Visitor< std::ostream&, std::ostream& > PrinterBase;


// implement the visitor
class Printer : public PrinterBase {
protected:
	const char* name_;
	int counter_;

// implement a default method
// called for a nomatch type
std::ostream& def( Shape*, std::ostream& out ) { out<< name_<< ++counter_<< ": unhandled Shape"<< std::endl; return out; }

// print a rect
std::ostream& print_rect( Rect*, std::ostream& out ) { out<< name_<< ++counter_<< ": Rect"<< std::endl; return out; }

public:

Printer() 
: PrinterBase( &PrinterBase::thunk< Printer, Shape, &Printer::def > ) // constructor must pass the default method as a thunk
, counter_( 0 )
, name_( "Printer1: " )
{
	// add methods as one wants
	// C++ requires specifying first 2 template parameters
	// eventhough the 3rd contains both
	add< Printer, Rect, &Printer::print_rect>();
}

};

// a visitor heirarchy
class Printer2 : public Printer {
protected:
	const char* name_; // not that these two members hide the members in the base class; just an example
	int counter_;

// implement visit for circle
std::ostream& print_cir( Circle* c, std::ostream& out ) { out<< name_<< ++counter_<< ": Circle : "<< c->radius()<< std::endl; return out; }

public:
Printer2() : counter_( 0 ), name_( "Printer2: " )
{
	// add the circle visit method
	add< Printer2, Circle, &Printer2::print_cir>();
}

};

typedef std::vector< Shape* > ShapeV;

// given a visitor and a ShapeV, visit each Shape*
void process( Printer* p, ShapeV* shapes )
{
	for( ShapeV::iterator itr = shapes->begin(); itr != shapes->end(); ++itr ) {
		(*p) ( *itr, std::cout ); // this is semantically: visitor->accept( obj )
	}
}

// add a new class on the fly
void example_add_new_class_and_update_visitor( ShapeV* shapes, Printer* p )
{
	// this class can be defined locally
	struct Square : public Rect, Tagged< Shape, Square > { };
	shapes->push_back( new Square() );

	// we define a class that is layout compatibly with Printer
	// this is just so that we can update the existing visitor
	struct Local : public Printer {
		// implement visit mehtod
		std::ostream& print_sq( Square*, std::ostream& out ) { out<< name_<< ++counter_<< ": Square"<< std::endl; return out; }
	};

	// add the visit method
	p->add< Local, Square, &Local::print_sq >();
}


int main()
{
	ShapeV shapes;

	shapes.push_back( new Rect() );
	shapes.push_back( new Circle( 3.1415f ) );

	Printer print;
	process( &print, &shapes );

	std::cout<<"\n\n\n------------------------------\n\n\n";

	Printer2 print2;
	process( &print2, &shapes );

	std::cout<<"\n\n\n------------------------------\n\n\n";

	print2 = Printer2();
	example_add_new_class_and_update_visitor( &shapes, &print2 );
	process( &print2, &shapes );

}

