// Wzorzec projektowy "Stan"
// zadanie 15

// przygotowal: 
// Łukasz Karolewski, F1ISIIII


// 1. a'la UML ;)

// 
// KONTEKST /\______________1_\ abstract STAN
//          \/ 				  /	^
//                              |
//             --------------generalizacja-------------------
//             |			|                |              |
//          konkretny   konkretny         konkretny      konkretny
//           stan1        stan2		       stan3           stan4
//
// a tutaj mamy ladniejszy obrazek
// http://en.wikipedia.org/wiki/Image:State_Design_Pattern_UML_Class_Diagram.png



// 2. OPIS 

// Stan jest wzorcem z bechawioralnym, czyli takim ktory sluzy do wyrazania zachowania obiektow.
// W uogolnieniu chodzi o mozliwosc wydelegowania logiki zachowania z klasy kontekstu do konkretnych
// implementacji klasy stanu.
// Uzywajac tego wzorca unikamy niezarzadzalnych konstrukcji typu "switch" oraz latwiej podzielic sie 
// praca grupowa, gdyz rozne osoby moga w tym samym czasie implementowac zachowanie w rozych stanach.
//
// Jako przyklad niech posluza drzwi na szyf. 
// Bezsprzecznie dla drzwi jako obiektu mozemy wyroznic stany np: otwarte(OPENED), zamkniete(CLOSED), 
// zakluczone(LOCKED),w trakcie otwierania(UNLOCKING)
//
// Operacje jakie mozemy wykonywac na drzwiach to: otwieranie, zamykanie, zakluczanie, 
// sprawdzanie czy mozemy przez nie przejsc oraz odkluczanie.
// Dla urozmaicenia, w ponizszym przykladzie, postanowilem wyroznic stan 'w trakcie otwierania(UNLOCKING)'.
// Stan ten mozna wykozystac na przyklad w celu reprezentacji dlugotrwalego procesu uwiezytelniania 
// (wyslamy kod smsem i czekamy) 



// 3. Diagram stanow w przykladzie:

//
//       <----close()----                                                             
//OPENED ---- open()----> CLOSED ----lock()----> LOCKED ----startunlock()----> UNLOCKING 
//                          ^	(gdy haslo ok)	   ^(gdy haslo zle)	               |
//						    |----------------------|-------------------------- entercode()
//

#include <iostream>
#include <string>
using namespace std;

//bo nie ma plikow naglowkowych ...
class State;

//klasa wyrazajaca kontekst w ktorym uzywamy stanu. 
class Door 
{
private: 
	//drzwi posiadaja stan
	State* state_;
public:
	Door();
	void SetState(State* s);

	//interfejs drzwi (czyli to co mozemy robic z nimi zrobic)
	//logika tych metod zostanie wydelegowana do konkretnych stanow
	void Open();
	void Lock();
	void Close();
	void StartUnlock() ;
	void EnterCode();
	bool CanWalkThrough();
};


// abstrakcyjna klasa definiujaca interfejs logiki ktora delegujemy do obiektow stanu
// mozna rozwazyc deklaracje tej klasy jako friend dla drzwi, wtedy drzwi nie musialy by publicznie 
// udostepniac metody zmieniajacej ich stan
class State
{
protected:
	//nasze drzwi
	Door* door_;

public: 
	State(Door* d) : door_(d){};


	//interfejs drzwi
	virtual void Open() = 0;
	virtual void Close() = 0;
	virtual void Lock() = 0;
	virtual void StartUnlock() = 0;
	virtual void EnterCode() = 0;
	virtual bool CanWalkThrough() = 0;
};



//ponizej definicje konkretnych stanow
class Closed : public State
{
public:
	Closed(Door*);

	void Open();
	void Lock() ;
	void Close();
	void StartUnlock() ;
	void EnterCode() ;
	bool CanWalkThrough();
};

class Opened : public State
{
public:
	Opened(Door*);
	void Open();
	void Lock() ;
	void Close() ;
	void StartUnlock() ;
	void EnterCode() ;
	bool CanWalkThrough();
};

class Locked : public State
{
public:
	Locked(Door*);
	void Open();
	void Lock() ;
	void Close() ;
	void StartUnlock() ;
	void EnterCode() ;
	bool CanWalkThrough();
};

class Unlocking : public State
{
public:
	Unlocking(Door*);

	void Open();
	void Lock() ;
	void Close() ;
	void StartUnlock() ;
	void EnterCode() ;
	bool CanWalkThrough();
};
///////////////////////////////////////////////


////////////////
// implementacja klasy implementujacej stan Locked
Closed::Closed(Door* door) : State(door){cout << "CLOSED" << endl;}

// zamkniete drzwi po prostu mozna otworzyc
void Closed::Open() {door_->SetState(new Opened(this->door_));}

// zamkniete drzwi po prostu mozna zakluczyc
void Closed::Lock() {door_->SetState(new Locked(this->door_));}

//nie mozna zamknac zamknietych drzwi... a przynajmniej jest to nielogiczne
void Closed::Close() {throw exception("not possible");}

//nie sa zakluczone 
void Closed::StartUnlock() {throw exception("not possible");}

//nie sa w trakcie otwierania
void Closed::EnterCode() {throw exception("not possible");}

//przez zamkniete drzwi raczej nie przejdziemy ;) 
bool Closed::CanWalkThrough() {return false;}
////////////////

//analogicznie implementujemy pozostale klasy

////////////////
// implementacja klasy implementujacej stan Locked
Opened::Opened(Door* door) : State(door){cout << "OPENED" << endl;}

bool Opened::CanWalkThrough() {return true;}
void Opened::Close() {door_->SetState(new Closed(door_));}

void Opened::Open() {throw exception("not possible");}
void Opened::Lock() {throw exception("not possible");}
void Opened::StartUnlock() {throw exception("not possible");}
void Opened::EnterCode() {throw exception("not possible");}
////////////////


////////////////
// implementacja klasy implementujacej stan Locked
Locked::Locked(Door* door): State(door){cout << "LOCKED" << endl;}

void Locked::StartUnlock() {door_->SetState(new Unlocking(door_));}

void Locked::Close() {throw exception("not possible");}
void Locked::Open() {throw exception("not possible");}
void Locked::Lock() {throw exception("not possible");}
void Locked::EnterCode() {throw exception("not possible");}
bool Locked::CanWalkThrough() {return false;}
////////////////


////////////////
// implementacja klasy implementujacej stan Unlocking

Unlocking::Unlocking(Door* door): State(door){cout << "UNLOCKING" << endl;}

void Unlocking::EnterCode() 
{
	cout << ">>podaj haslo:";
	
	string buf;
	cin >> buf;
	if (buf == "haslo")
	{
		door_->SetState(new Closed(door_));
	}
	else
	{
		door_->SetState(new Locked(door_));
	}
};

void Unlocking::StartUnlock() {throw exception("not possible");}
void Unlocking::Close() {throw exception("not possible");}
void Unlocking::Open() {throw exception("not possible");}
void Unlocking::Lock() {throw exception("not possible");}
bool Unlocking::CanWalkThrough() {return false;}
////////////////



////////////////
// implementacja klasy Door
Door::Door()
{
	//drzwi domyslnie sa zamkniete
	this->state_= new Closed(this);
}

//publiczna metoda ustalajaca stan obiektu
//mozna by bylo uzyc zaprzyjaznionej metody wydelegowanej do klasy State aby zachowac czystosc interfejsu
void Door::SetState(State* s) { delete this->state_; this->state_ = s; }

//jak widac logika ponizszych metod jest delegowana do biezacego obiektu stanu
void Door::Open() {this->state_->Open();}
void Door::Lock() {this->state_->Lock();}
void Door::Close() {this->state_->Close();}
void Door::StartUnlock() {this->state_->StartUnlock();}
void Door::EnterCode() {this->state_->EnterCode();}
bool Door::CanWalkThrough() {return this->state_->CanWalkThrough();}
////////////////


//interaktywny test :)
int main()
{
	//bufor na input od usera
	string buf;

	//instancja drzwi z ktorymi bedziemy cos robic
	Door d;
	
	cout << "enter q to exit" << endl;
	//no i dopoki mamy ochote bedziemy meczyc drzwi
	while(true)
	{
		try
		{
			cout << ">";
			//czekamy na polecenie od uzytkownika
			cin >> buf;
	 
			//jezeli jest to znana komenda to ja wykonujemy
			if (buf == "open")
			{
				d.Open();
				continue;
			}
			if(buf == "close")
			{
				d.Close();
				continue;
			}
			if(buf == "lock")
			{
				d.Lock();
				continue;
			}
			if(buf == "startunlock")
			{
				d.StartUnlock();
				continue;
			}
			if(buf == "entercode")
			{
				d.EnterCode();
				continue;
			}
 
			if(buf == "walk")
			{
				if (d.CanWalkThrough())
				{
					cout << "mozesz przejsc" << endl;
				}
				else
				{
					cout << "najpierw otworz drzwi..."<< endl;
				};
				continue;
			}

			//konczymy prztwarzanie
			if(buf == "q") { break; }
			
			//informacja o podaniu blednego polecenia
			cout << "bad cmd" << endl;
		}
		catch(exception& e)
		{
			cout << e.what() << endl;
		}
	}
	return 0;
}