//Simple demo of visitor pattern
//Uses raw poitner instead of smartpointers for demo purpose only

#include <iostream>
#include <string>
#include <map>
using namespace std;

class Visitor; 
struct Item        // items in inventory
{
	virtual Item* clone() const=0;  // duplicate an item for sotring in inventory
	virtual int code() const=0;     // code for the map
	virtual void acceptVisitor(Visitor &v)=0;   // allow for visitors
	virtual ~Item() {}              
};

class Book : public Item   // a concrete item
{
	int isbn;              // with its specific data (a dvd has no isbn)
	string name;
public: 
    Book() = default; 
    Book(const Book& b) = default; 
    Book(int isbn, const string &name);
    int code() const override ;    // implement item members
    Item* clone() const override ;
  	void acceptVisitor(Visitor &v) override;
  	int getIsbn() const ;          // provide getters to ensure encapsulation
  	const string& getTitle() const ;
};

class Inventory 
{
private:
    map<int, Item *> items; 
public:
    void addItem(const Item& i);     
    void acceptVisitor(Visitor &v);  
};

class Visitor {                   // generic visitor.  Can be used for report, for saving data, etc..
public:
    virtual void visitInventory(Inventory *iv) = 0;
    virtual void visitBook(Book*) = 0; 
    //... if you have new type of items, just add a function here    
    virtual void visitEnd() {}; 
};

class Report : public Visitor   // FIrst report:  just tell what to do fpor every class you use  
{
private:
    Inventory* i;
    int count=0; 
public:
    Report(Inventory& i);
    void visitInventory(Inventory *iv); 
    void visitBook(Book*b);
    void visitEnd();   // to end the report
};

Book::Book(int isbn, const string &name) : isbn(isbn), name(name) {}
int Book::code() const  { return isbn; }
Item* Book::clone() const  { return new Book(*this); }
void Book::acceptVisitor(Visitor &v) { v.visitBook(this); }
int Book::getIsbn() const { return(isbn); }
const string& Book::getTitle() const { return (name); }

void Inventory::addItem(const Item& i)
{
    items[i.code()] =i.clone();
}
void Inventory::acceptVisitor(Visitor &v) { 
    	v.visitInventory(this); 
    	for (auto &x : items)
    	    x.second->acceptVisitor(v);
    	v.visitEnd();
}

Report::Report(Inventory& i) : i(&i),count(0)
{ i.acceptVisitor(*this); }

void Report::visitInventory(Inventory *iv) {
	cout << "Nice report:"<<endl; 
	cout << "============"<<endl; 
}
void Report::visitBook(Book*b) {
	cout << "Book: "<< b->getTitle() << " (ISBN "<<b->getIsbn()<<" )"<<endl;
	count++; 
}
void Report::visitEnd() {
	cout << "Total of "<<count<<" items"<<endl;
}


int main()   // CORRECT: never main(void) !!!!
{
    Inventory i;

    i.addItem(Book(1234, "Foo Bar I"));
    i.addItem(Book(4567, "Foo Bar II"));

    Report r(i);

    return 0;
}
