/*
	Jakub Lewczyński, 3T2

	Opis: Obiekt copy-on-write proxy, tak jak zwykły proxy, pośredniczy miedzy klientem a rzeczywistym obiektem.
	Jego zadaniem jest wykonanie kopiowania obiektu dopiero w momencie wprowadzania do niego zmian.
	Każda kolejna kopia obiektu nie zajmuje dodatkowej pamięci. Gdy mamy do czynienia z obiektami o znacznym rozmiarze
	jest to duża oszczędność. Wadą tego rozwiązania jest dłuższy czas modyfikacji obiektu.

	Działanie: Klasa BigClassProxy przechowuje wskaźniki na: obiekt BigClass i licznik odwołań do tego obiektu.
	Kiedy obiekt ma być zmodyfikowany proxy sprawdza licznik odwołań. Jeśli do danego obiektu odowłuje się tylko
	jedno proxy - obiekt jest modyfikowany. Jeśli do obiektu odwołuje się więcej proxy - proxy modyfikujące obiekt
	zmniejsza dotychczasowy licznik odwołań, tworzy nowy obiekt na podstawie dotychczasowego, tworzy nowy licznik
	i na sam koniec wykonuje żądane zmiany. W trakcie przypisywania proxy działa analogicznie, tyle że sprawdza czy
	zmniejszany licznik odwołań nie wynosi 0. Jeśli tak jest obiekt, do którego nie odwołuje się żadne proxy jest zwalniany.
*/

#include <iostream>

/*Abstrakcyjna klasa bazowa dla klas BigClass i Big classProxy. Zapewnia im wspólny interfejs.*/
class BigClassInterface {
public:
	virtual ~BigClassInterface() {}
	virtual void setValue(int value) = 0;
	virtual int getValue() const = 0;
protected:
	BigClassInterface() {}
};

/*Klasa, której kopiowanie następuje przy zmianie jej parametrów. Dziedziczy po abstrakcyjnej klasie interfejsu.*/
class BigClass: public BigClassInterface {
public:
	BigClass(int value): value_(value) { std::cout << "Konstruktor duzego obiektu" << std::endl; }	//konstruktor dużej klasy
	BigClass(const BigClass& source): value_(source.value_) { std::cout << "Konstruktor duzego obiektu" << std::endl; }	//konstruktor kopiujący
	void setValue(int value) { value_ = value; }	//funkcja zapisująca przechowywane dane (w tym wypadku int)
	int getValue() const { return value_; }				//funkcja zwracająca przechowywane dane (w tym wypadku int)
	~BigClass() { std::cout << "Destruktor proxy." << std::endl; }	//destruktor
private:
	int value_;	//przechowywane dane
};

/*Proxy klasy BigClass. Dziedziczy po abstrakcyjnej klasie interfejsu.*/
class BigClassProxy: public BigClassInterface {
public:
	BigClassProxy(int value);	//konstruktor
	BigClassProxy(const BigClassProxy& source); //konstruktor kopiujący

	void setValue(int value) { real()->setValue(value); }	//funkcja kopiująca obiekt i wprowadzająca do niego zmiany
	int getValue() const { return realPtr_->getValue(); }	//funkcja zwracająca dane przechowywane w obiekcie

	BigClassProxy& operator=(const BigClassProxy& source);	//operator przypisania

	~BigClassProxy();	//destruktor proxy
private:
	BigClass* realPtr_;	//wskaźnik na obiekt
	int* counter_;		//wskaźnik na licznik odniesień do obiektu
	/*Funkcja pomocnicza obsługująca ewentualne kopiowanie obiektu i zwracająca do niego wskaźnik*/
	BigClass* real() {
		//jeśli więcej niż jedno proxy odwołuje się do obiektu...
		if(*counter_ > 1) {
			//zmniejsz licznik odwołań
			(*counter_)--;

			//stworzenie nowego licznika odniesień i obiekt BigClass na podstawie starego obiektu
			counter_ = new int(1);
			realPtr_ = new BigClass(*realPtr_);
		}
		//jeśli tylko jedno proxy korzysta z obiektu nie trzeba tworzyć nowego obiektu; wystarczy zmodyfikować obiekt
		//zwróć aktualny wskaźnik
		return realPtr_;
	}
};

/*Konstruktor proxy*/
BigClassProxy::BigClassProxy(int value){
	//komunikat informacyjny
	std::cout << "Konstruktor proxy." << std::endl;

	//stworzenie nowego obiektu i licznika odniesień
	realPtr_ = new BigClass(value);
	counter_ = new int(1);
}

/*Konstruktor kopiujący proxy*/
BigClassProxy::BigClassProxy(const BigClassProxy& source){
	//komunikat informacyjny
	std::cout << "Konstruktor kopiujący proxy." << std::endl;

	//przypisanie adresu obiektu i licznika odniesień
	realPtr_ = source.realPtr_;
	counter_ = source.counter_;

	//zwiększenie licznika odniesień
	(*counter_)++;
}

/*Operator przypisania proxy*/
BigClassProxy& BigClassProxy::operator=(const BigClassProxy& source) {
	//tymczsowe zmienne przechowujące wskaźniki do obiektu i licznika
	int* old_counter = counter_;
	BigClass* old_real = realPtr_;

	//przypisanie nowego obiektu i wskaźnika do wskaźników
	counter_ = source.counter_;
	realPtr_ = source.realPtr_;

	//zmniejszenie licznika odniesień do starego obiektu
	(*old_counter)--;
	//zwiększenie licznika odniesień do nowego obiektu
	(*counter_)++;

	//jeśli nikt nie używa starego obiektu zwalnia pamięć
	if(*old_counter == 0) {
		delete old_counter;
		delete old_real;
	}

	//zwraca referencję do siebie
	return *this;
}

/*Destrukto proxy*/
BigClassProxy::~BigClassProxy(){
	//komunikat informacyjny
	std::cout << "Destruktor proxy." << std::endl;

	//zmiejszenie licznika odniesień
	(*counter_)--;

	//jeśli nikt nie używa obiektu zwalnia pamięć
	if(*counter_ == 0) {
		delete realPtr_;
		delete counter_;
	}
}

int main()
{
	std::cout << "Poczatek programu" << std::endl;
	std::cout << "Stworzenie proxy 'a' o wartości 10" << std::endl;
	BigClassProxy a(10);
	std::cout << "Stworzenie proxy 'b' na podstawie 'a'" << std::endl;
	BigClassProxy b(a);
	std::cout << "Stworzenie proxy 'c' na podstawie 'a'" << std::endl;
	BigClassProxy c(a);
	std::cout << "Stworzenie proxy 'd' na podstawie 'a'" << std::endl;
	BigClassProxy d(a);

	std::cout << "Wypisanie wartości:" << std::endl;
	std::cout << "'a': " << a.getValue() << std::endl;
	std::cout << "'b': " << b.getValue() << std::endl;
	std::cout << "'c': " << c.getValue() << std::endl;
	std::cout << "'d': " << d.getValue() << std::endl;

	std::cout << "Ustawienie wartości 'b' na 50" << std::endl;
	b.setValue(50);
	std::cout << "Przypisanie 'b' do 'c'" << std::endl;
	c = b;
	std::cout << "Ustawienie wartości 'd' na 100" << std::endl;
	d.setValue(100);
	std::cout << "Przypisanie 'd' do 'a'" << std::endl;
	a = d;	

	std::cout << "Wypisanie wartości:" << std::endl;
	std::cout << "'a': " << a.getValue() << std::endl;
	std::cout << "'b': " << b.getValue() << std::endl;
	std::cout << "'c': " << c.getValue() << std::endl;
	std::cout << "'d': " << d.getValue() << std::endl;
	return 0;
}

