// Wyścig. 

// W funkcji 'main' występują obiekty 'ref(x)'. Są to obiekty pomocnicze, które zapobiegają tworzeniu kopii obiektu, więc np. 'thread thrd1( ref(p1) );'
// oznacza, że konstruktor 'std::thread' nie wykona kopii obiektu 'p1'. Nie jest to kluczowe do znalezienia rozwiązania.

// Posługujemy się także listą dwukierunkową std::list, z operacjami 'push_back' -- wstawiania na koniec, 'pop_front' -- usuwanie początkowego elementu,
// 'front' -- zwraca początkowy element listy


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

#include <iostream>

using namespace std;

using Data = int; //Data to paczka danych do przetwarzania, wielkość paczki to ok. 1MB.
const Data EMPTY = -1; //reprezentuje brak danych

struct Queue {
    list<Data> dane_;
    mutex mutex_;

    bool isEmpty() {
        lock_guard<mutex> lock(mutex_);
        return dane_.empty();
    }

    Data read() {
        Data out = EMPTY;
        lock_guard<mutex> lock(mutex_);
        if(!dane_.empty() ) {
            out = dane_.front();
            dane_.pop_front();
        }
        return out;
    }
    void write(Data d) {
        lock_guard<mutex> lock(mutex_);
        dane_.push_back(d);
    }
};

class Reader {
public:
    Reader(Queue& q) : finish_(false), queue_(q) {}

    void finish() { finish_ = true; }

    void operator()() {
        while(!finish_) {
            if(! queue_.isEmpty() ) {
                Data d = queue_.read();
                //process data
                std::this_thread::sleep_for( chrono::milliseconds(100));
            }
        }
    }
private:
    Queue& queue_;
    volatile bool finish_;
};


class Writer {
public:
    Writer(Queue& q) : finish_(false), queue_(q) {}
    void finish() { finish_ = true; }
    void operator()() {
        int i = 0;
        while(!finish_) {
            Data d = ++i; //obliczenia dla danej d
            std::this_thread::sleep_for( chrono::milliseconds(100));
            queue_.write(d);
        }
    }
private:
    Queue& queue_;
    volatile bool finish_;
};


int main () {
    Queue q;
    Reader r1(q), r2(q);
    Writer w1(q);

    thread thrd1( ref(r1) );
    thread thrd2( ref(r2) );
    thread thrd3( ref(w1) );

    std::this_thread::sleep_for( chrono::milliseconds(2000));

    r1.finish();
    r2.finish();
    w1.finish();

    thrd1.join();
    thrd2.join();
    thrd3.join();
    return 0;
}
