#include <queue>
#include <ctime>
#include <cstdlib>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/xtime.hpp>
#include <iostream>
using namespace std; 

/*przydatne stałe, oraz mutex do synchronizacji wyjścia*/
const int SZ = 10;
enum {UP = 1, RD = 2};
boost::mutex io_mutex;

/*to jest nasz Servant*/
class Database;
/*proxy*/
class DBProxy;
/*planista*/
class Scheduler;
/*reprezentacja żądania wywołania metody*/
class Request;
/*dwie klasy reprezentujące żądania konkretnych metod*/
class Update;
class Read;
/*klasa opakowująca zwracaną wartość*/
class RetVal;
/*a to już klasy reprezentujące czytelnika i pisarza*/
class Writer;
class Reader;

class Database {
public:
	Database() {
		for(int i=0; i<SZ; ++i) {
			base[i] = 0;
		}
	}
	void update(int num, int cell) {
		//na wypadek przekroczenia zasięgu
		base[cell%SZ] = num;
	}
	int read(int cell) {
		return base[cell%SZ];
	}
private:
	int base[SZ];
};

class RetVal {
public:
	RetVal() {
		ready_ = 0;
		val = -1;
	}
	RetVal(const RetVal& r) {
		ready_ = r.ready_;
		val = r.val;
	}
	RetVal& operator=(const RetVal& r) {
		ready_ = r.ready_;
		val = r.val;
		return *this;
	}
	int getVal() const {
		return val;
	}
	/*funkcja ustawiająca wartość zwróconą*/
	void set(int num) {
		ready_ = 1;
		val = num;
	}
	/*funkja do sprawdzenia, czy wartośc została już przekazana*/
	int ready() const {
		return ready_;
	}
	/*blokujący operator rzutowania na int*/
	operator int() {
		while(!ready_) {
			;
		}
		return val;
	}
private:
	int ready_;
	int val;
};

class Request {
public:
	/*w tym przykładzie funkcja guard nic nie będzie robić.
	Standardowo jednak może ona zawierać elementy synchronizacji
	np jeżeli chcielibyśmy blokować czytanie z pustej kolejki*/
	virtual bool guard() const = 0;
	/*funkcja ta wywołuje faktyczną metodę z bazy danych*/
	virtual void call() = 0;
};

class Update : public Request {
public:
	Update(Database* d, int n, int c) : database(d), num(n), cell(c) {}
	bool guard() const {
		return true;
	}
	void call() {
		database->update(num, cell);
	}
public:
	Database *database;
	int num;
	int cell;
};

class Read : public Request {
public:
	Read(Database* d, RetVal* rv, int c) : database(d), cell(c), ret(rv) {}
	bool guard() const {
		return true;
	}
	/*tutaj jeszcze trzeba zapamiętać wynik*/
	void call() {
		int num = database->read(cell);
		ret->set(num);
	}
private:
	Database* database;
	int cell;
	RetVal* ret;
};

class Scheduler {
public:
	Scheduler() : upQueue(new queue<Request*>()), rdQueue(new queue<Request*>), sem_u(new boost::mutex())
		, sem_r(new boost::mutex()) {
			thrd = new boost::thread(*this);
	}
	/*funkcja wstawiająca elementy do kolejki
	zauważmy, że jest tu mały problem producent-konsumer,
	tak więc potrzebna jest synchronizacja*/
	void enqueue(Request* req, int type) {
		if(type == UP) {
			boost::mutex::scoped_lock lock_i(*sem_u);
			upQueue->push(req);
		}
		else {
			boost::mutex::scoped_lock lock_i(*sem_r);
			rdQueue->push(req);
		}
	}
	/*ta funkcja działa w nieskończoność w osobnym wątku
	pisarz ma priorytet nad czytelnikiem, dlatego są dwie kolejki
	i kolejka z pisarzami obsługiwana jest jako pierwsza
	zakładam sekwencyjny dostęp do bazy danych również dla czytelników,
	więc zdejmuję ich z kolejki pojedynczo*/
	void dequeue() {
		int flag = 0;
		while(true) {
			flag = 0;
			/*tu powstaje dodatkowy zasięg, po to aby jak najszybciej zniszczyć obiekt lock_i*/
			{
				boost::mutex::scoped_lock lock_i(*sem_u);
				if(!upQueue->empty()) {
					flag = 1;
					Request* req = upQueue->front();
					upQueue->pop();
					req->call();
				}
			}
			if(!flag) {
				boost::mutex::scoped_lock lock_i(*sem_r);
				if(!rdQueue->empty()) {
					Request* req = rdQueue->front();
					rdQueue->pop();
					req->call();
				}
			}
		}
	}
	void operator()(void) {
		this->dequeue();
	}
private:
	queue<Request*>* upQueue;
	queue<Request*>* rdQueue;
	boost::mutex* sem_u;
	boost::mutex* sem_r;
	boost::thread* thrd;
	/*do konstruktora thread można podać obiekt funkcyjny,
	powstaje kopia danego obiektu, a ponieważ składowe są wskaźnikami
	to operujemy wciąż na tych samych kolejkach i semaforach*/
};

class DBProxy {
public:
	/*tutaj faktycznie powstają planista oraz baza danych*/
	DBProxy() : scheduler(new Scheduler()), database(new Database()) {}
	/*przy wywołaniu metody, tworzone jest żądanie i odkładane na kolejkę*/
	void update(int num, int cell) {
		Request* req = new Update(database, num, cell);
		scheduler->enqueue(req, UP);
	}
	/*tutaj dodatkowo powstaje obiekt przechowujący wartość zwracaną z funkcji*/
	RetVal& read(int cell) {
		RetVal* ret = new RetVal();
		Request* req = new Read(database, ret, cell);
		scheduler->enqueue(req, RD);
		return *ret;
	}
private:
	Database* database;
	Scheduler* scheduler;
};

DBProxy base;

class Writer {
public:
	Writer(int n) : num(n) {
		thrd = new boost::thread(*this);
	}
	void update() {
		int val, cell;
		val = rand()%100 + 1;
		cell = (rand()%10 * num) % SZ;
		base.update(val, cell);
		boost::mutex::scoped_lock lock_i(io_mutex);
		cout << "wpisano " << val << " do " << cell << endl;
	}
	void join_() {
		thrd->join();
	}
	void operator()(void) {
		boost::xtime xt;
		while(true) {
			srand(time(0));
			this->update();
			boost::xtime_get(&xt, boost::TIME_UTC);
			xt.sec += rand()%10+1;
			boost::thread::sleep(xt);
		}
	}
private:
	boost::thread* thrd;
	int val;
	int num;
	int seed;
};

class Reader {
public:
	Reader(int s = 0) : seed(s) {
		thrd = new boost::thread(*this);
	}
	void read() {
		int cell = rand()%3;
		/*można też poprosić o obiekt RetVal, aby nie blokować wątku
		wtedy można np co jakiś czas sprawdzać, czy wartość już jest
		ale wymaga to ręcznego niszczenia tych obiektów*/
		int result = base.read(cell);
		boost::mutex::scoped_lock lock_i(io_mutex);
		cout << "Odczytano: " << result << " z " << cell << endl;
	}
	void join_() {
		thrd->join();
	}
	void operator()(void) {
		boost::xtime xt;
		while(true) {
			boost::xtime_get(&xt, boost::TIME_UTC);
			xt.sec += rand()%2+1;
			boost::thread::sleep(xt);
			this->read();
		}
	}
private:
	boost::thread* thrd;
	int seed;
};

int main() {
	Writer w1(1), w2(2);
	Reader r1, r2,r3;
	w1.join_();
	w2.join_();
	r1.join_();
	r2.join_();
	r3.join_();
	return 0;
}