/* Przemysław Boruta, grupa 4I1
 *
 * Wzorzec projektowy wizytator
 *
 * Plik zawiera przykład użycia wzorca projektowego "wizytator".
 * Wzorzec ten umożliwia definiowanie działań na strukturze obiektowej
 * bez modyfikacji klas należących do tej struktury. Osiągane jest to
 * przez "kapsułkowanie" operacji wykonywanych na obiektach w oddzielnej
 * hierarchii klas wizytatorów.
 *
 * Hierarchia klas odwiedzanych reprezentuje uproszczony asortyment sklepu spożywczego.
 * Na jej górze znajduje się abstrakcyjna klasa Produkt, deklarująca czysto wirtualną
 * metodę akceptuj, odpowiedzialną za wywołanie algoryutmu wizytacji, zdefiniowanego 
 * w klasie wizytatora. Klasy z niej dziedziczące przedefiniowują tą metodę 
 * w odpowiedni dla siebie sposób.
 *
 * Konkretne produkty (chleb, piwo, wielopak) mogą być odwiedzane przez obiekty klas
 * znajdujących sie w hierarchii klas wizytujących. Na jej górze znajduje się abstrakcyjna
 * klasa Wizytator, deklarująca czysto wirtualne metody 'wizytuj', odpowiedzialne za
 * wywołanie algorytmu wizytacji dla każdej z klas. Klasy konkretnych wizytatorów (tu:
 * wizytatory odpowiedzialne za wypisanie produktu na ekran oraz zliczenie wartości
 * produktów znajdujących się w koszyku) definiują każdą z tych metod w odpowiedni 
 * dla siebie sposób.
 * */

#include <iostream>
#include <vector>
using namespace std;

class Chleb;
class Piwo;
class Wielopak;

/* Klasa bazowa dla hierarchii klas wizytatorów. Czysto wirtualne metody 'wizytuj'
 * są definiowane w konkretnych klasach wizytatorów.
 *
 */
class Wizytator{
	public:
		virtual void wizytuj(Chleb&) = 0;
		virtual void wizytuj(Piwo&) = 0;
		virtual void wizytuj(Wielopak&) = 0;
};

/* Klasa bazowa hierarchii klas produktów. Czysto wirtualna metoda 'akceptuj' jest 
 * definiowana w każdej z konkretnych klas produktów. Podanie jako jej argumentu 
 * referencji na klasę Wizytator umożliwia przyjęcie przez klasę reprezentującą
 * produkt dowolnego wizytatora z hierarchii wizytatorów.
 *
 * Klasa uwzględnia także cenę danego produktu.
 */
class Produkt{
	public:
		virtual void akceptuj(Wizytator& w) = 0;
		double podajCene() { 
			return cena;
		}
	protected:
	
		Produkt(double gcena) : cena(gcena) {}
	private:
		double cena;
};

/* Klasa reprezentująca konkretny produkt, w tym wypadku chleb. Jej metoda 'akceptuj' polega
 * na prostym wywołaniu metody 'wizytuj' obiektu wizytującego. Obiekt typu Chleb nie wie,
 * jakiego typu wizytator został przyjęty. 
 *
 * Obiekt wizytujący posiada dostęp do obiektu wizytowanego poprzez referencję, przekazaną mu
 * w wywołaniu funkcji 'wizytuj'. Dzięki temu ma możliwość wykonania zdefiniowanego
 * algorytmu.
 */
class Chleb : public Produkt{
	public:
		Chleb(double gcena, double gwaga) : Produkt(gcena), waga(gwaga) {}
		// Definicja odziedziczonej funkcji czysto wirtualnej
		virtual void akceptuj(Wizytator& w){
			// Wywołanie algorytmu wizytacji, zdefiniowanego w obiekcie wizytatora
			w.wizytuj(*this);
		}
		double podajWage() {
			return waga;
		}

	private:
		double waga;
};

/* Klasa podobna pod względem omawianych zagadnień do klasy Chleb. Wywołanie algorytmu wizytacji również
 * polego na prostym wywołaniu funkcji 'wizytuj' obiektu wizytującego z argumentem będącym referencją
 * do obiektu wizytowanego, co umożliwia wizytatorowi wykonanie działania.
 */
class Piwo : public Produkt{
	public:
		Piwo(double gcena, double gwielkoscPuszki) : Produkt(gcena), wielkoscPuszki(gwielkoscPuszki) {}
		virtual void akceptuj(Wizytator& w){
			w.wizytuj(*this);
		}
		double podajWielkoscPuszki(){
			return wielkoscPuszki;
		}
	private:
		double wielkoscPuszki;
};

/* Klasa ta demonstruje wywołanie obiektu wizytującego dla obiektu będącego zbiorem innych obiektów.
 * W tym przypadku najpierw wywoływany jest algorytm wizytacji dla samej klasy kontenera (np. wypisanie
 * na ekranie informacji o 'zbiorczym' charakterze obiektu) a następnie wywoływana jest wizytacja dla 
 * wszystkich obiektów znajdujących się w danym kontenerze.
 */
class Wielopak : public Produkt{
	public:
		Wielopak(double gcena, int gilosc) : Produkt(0), ilosc(gilosc) {
			for(int i=1; i<= gilosc; i++)
				wek.push_back(Piwo(gcena/gilosc,500));
		}
		virtual void akceptuj(Wizytator& w){
			//Wizytacja samego kontenera
			w.wizytuj(*this);
			for(vector<Piwo>::iterator i = wek.begin(); i != wek.end(); i++)
				//Wizytacja składowych
				w.wizytuj(*i);
		}
		int podajIlosc() {
			return ilosc;
		}
	private:
		vector<Piwo> wek;
		int ilosc;
};

/* Klasa wizytatora odpowiedzialnego za wypisanie na ekran informacji o obiekcie podlegającym wizytacji.
 * Definiuje ona wszystkie funkcje związene z wizytacją konkretnych typów produktów, wypisując informacje
 * nieco inaczej jeśli wypisywany obiekt jest chlebem, piwem lub wielopakiem.
 */
class Wypisanie : public Wizytator{
	public:
		// Metoda wywoływana podczas wizytacji obiektu klasy Chleb
		virtual void wizytuj(Chleb& c){
			cout << "+ Chleb o wadze " << c.podajWage() << " gramow  w cenie " << c.podajCene() << " zl" <<  endl;
		}
		// Metoda wywoływana podczas wizytacji obiektu klasy Piwo 
		virtual void wizytuj(Piwo& p){
			cout << "+ Piwo w puszce o wielkosci " << p.podajWielkoscPuszki() << " mililitrow w cenie " << p.podajCene() << " zl" << endl;
		}
		// Metoda wywoływana podczas wizytacji obiektu klasy Wielopak
		virtual void wizytuj(Wielopak& w){
			cout << "+ Wielopak zawierajacy " << w.podajIlosc() << " nastepujacych artykulow: " << endl;
		}
};

/* Klasa demonstrująca użycia wizytatora jako obiektu kumulującego stan obiektów wizytowanych. W tym przypadku
 * zliczana jest sumaryczna cena wszystkich odwiedzanych obiektów poprzez wywołanie metody 'podajCene', zdefiniowanej
 * w każdej z wizytowanych klas.
 */
class Zliczenie : public Wizytator{
	public:
		Zliczenie(){ suma = 0; }
		virtual void wizytuj(Chleb& c){
			suma += c.podajCene();
		}
		virtual void wizytuj(Piwo& p){
			suma += p.podajCene();
		}
		virtual void wizytuj(Wielopak& w){ // Sam wielopak nic nie kosztuje - cena rozłożona jest na produkty
		}
		double podajSume(){
			return suma;
		}
	private:
		double suma;
};

int main(){
	vector<Produkt*> zakupy;
	Chleb c1(2.5,500);
	Piwo p1(2.9,600);
	Wielopak w1(10,4);
	// Utworzenie obiektu klasy vector reprezentującego listę zakupów.
	zakupy.push_back(&c1);
	zakupy.push_back(&p1);
	zakupy.push_back(&w1);
	// Obiekty wizytatorów zliczania wartości i wypisywania
	Zliczenie zl;
	Wypisanie wp;
	// Wypisanie listy zakupów przy użyciu wizytatora wypisywania
	cout << "Lista zakupow: " << endl;
	for(vector<Produkt*>::iterator i = zakupy.begin(); i != zakupy.end(); i++)
		(*i)->akceptuj(wp);
	// Zliczenie sumarycznej wartości produktów w koszyku przy użyciu wizytatora zlicznia
	for(vector<Produkt*>::iterator i = zakupy.begin(); i != zakupy.end(); i++)
		(*i)->akceptuj(zl);
	cout << "Sumaryczna wartosc produktow to: " << zl.podajSume() << " zl" << endl;
	return 0;
}

/* Wzorca wizytator wygodnie używa się, gdy struktora obiektów wizytowanych jest stała, mogą natomiast
 * zmieniać się interfejsy poszczególnych jej klas, np. poprzez dodanie nowej metody.
 * Jeśli jednak to interfejs jest stały, natomiast zmianie podlego sama struktura klas, wygoniej jest
 * zdefiniować odpowienie wywołania w samych klasach niż zmieniać całą hierarchię klas wizytatorów
 */

