// w aplikacji występuje zakleszczenie. Proszę je wyeliminować dbając o to, aby rozwiązanie było poprawne (bez wyścigów).

// 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ą list<int>, 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;
const Data EMPTY = -1; //reprezentuje brak danych

class Process {
public:
    static const int NUM = 100; //liczba operacji

    Process(int id) : id_(id) {
        data_.push_back(id_);
        data_.push_back(id_);
    }

    void setPrev(Process* p) { prev_ = p; }

    Data get() {
        Data ret = EMPTY;
        lock_guard<mutex> lock(m_);
        if(!data_.empty() ) {
             ret = data_.front();
             data_.pop_front();
        }
        return ret;
    }
    void operator()() {

        if(!prev_) return; //nie ma zrodla danych

		// ROZWIAZANIE NIEPOPRANE - ZAKOMENTUJ
        for(int i=0;i<NUM;++i) {
            cout << "id:" << id_ << " loop:" << i <<  endl;
            lock_guard<mutex> lock(m_);
            std::this_thread::sleep_for( chrono::milliseconds(100));
            Data d = prev_->get();
            cout << "id:" << id_ << " data:" << d << endl;
            if( d != EMPTY ) {
                data_.push_back(d+1);

            }
        }
		// ROZWIAZANIE POPRANE - ODKOMENTUJ
        // for(int i=0;i<NUM;++i) {
        //  Data d = prev_->get();
        //  cout << "id:" << id_ << " loop:" << i << " data:" << d << endl;
        //  if( d != EMPTY ) {
        //      lock_guard<mutex> lock(m_);
        //      std::this_thread::sleep_for( chrono::milliseconds(100));
        //      data_.push_back(d+1);
        //  }
        // }
    }
private:
    int id_;
    Process* prev_ = nullptr;
    list<Data> data_;
    mutex m_;
};

int main () {
    Process p1(100), p2(200), p3(300);
    p1.setPrev(&p3);
    p2.setPrev(&p1);
    p3.setPrev(&p2);

    thread thrd1( ref(p1) );
    thread thrd2( ref(p2) );
    thread thrd3( ref(p3) );
    thrd1.join();
    thrd2.join();
    thrd3.join();

    cout << "thrd1: " << p1.get() << endl;
    cout << "thrd2: " << p2.get() << endl;
    cout << "thrd3: " << p3.get() << endl;

    return 0;
}
