#include <list>
#include <thread>
#include <mutex>
#include <shared_mutex>

#include <iostream>

using namespace std;

using Data = int; //symuluje dane do obróbki
static const Data END_DATA = 0;
static const Data VALID_DATA = 1;

//dane do obrobki w roznych watkach
// struct DataSynch {
//     Data data_;
//     mutex read_;
//     mutex write_;
// };

// better!
struct DataSynch {
    Data data_;
 shared_mutex mutex_;
};


struct Reader {

    Reader(int id, DataSynch& data) : id_(id), data_(data) {}

    void operator()() {
        bool finish = false;
        while(!finish) {
			//symuluje dzialania poza sekcja krytyczna
			this_thread::sleep_for(chrono::milliseconds(10));

			//lock_guard<mutex> lock(data_.read_);
			shared_lock lock(data_.mutex_);

			//symuluje odczyt i obrobka
			this_thread::sleep_for(chrono::milliseconds(10));
			finish = (data_.data_ == END_DATA);

			cout << id_ << flush;
        }
    }
    int id_;
    DataSynch& data_;
};

struct Writer {

    Writer(int id, DataSynch& data) : id_(id), data_(data) {}

    void operator()() {
        static const int NUM = 10; //symuluje wielkosc danych do obrobki
        for(int i=0;i<NUM;++i) {
			//symuluje dzialania poza sekcja krytyczna
			this_thread::sleep_for(chrono::milliseconds(100));

			// lock_guard<mutex> lock1(data_.read_);
			// lock_guard<mutex> lock2(data_.write_);
			unique_lock lock(data_.mutex_);

			//symuluje obrobke
			this_thread::sleep_for(chrono::milliseconds(50));
			cout << endl << "writer:" << id_ << endl;
			data_.data_ = id_; //not zero!
        }
        //koniec zapisu, wstawiana dana 'pusta'
        {
            // lock_guard<mutex> lock1(data_.read_);
            // lock_guard<mutex> lock2(data_.write_);
            unique_lock lock(data_.mutex_);
            data_.data_ = END_DATA;
        }
    }
    int id_;
    DataSynch& data_;
};


int main() {
    DataSynch data;
    data.data_ = 1;

    Writer w1(1, data), w2(2, data);

    Reader r1(3, data), r2(4, data), r3(5, data), r4(6, data);

    thread th1( ref(w1) ), th2( ref(w2) );

    thread th3( ref(r1) ), th4( ref(r2) ), th5( ref(r3) ), th6( ref(r4) );

    th1.join();
    th2.join();
    th3.join();
    th4.join();
    th5.join();
    th6.join();
    return 0;
}
