#include <memory>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <iostream>

using namespace std;

//--------ZAD2--------------------------------------------------------------------------
// brak skalowalności w aplikacji współbieżnej
// W systemie istnieje jeden obiekt typu Queue, do którego wstawia dane jeden wątek (kodu nie zamieszczono). Ilość danych w kolejce
// jest duża i rośnie. Aby temu zapobiec system przeniesiono na platformę wieloprocesorową  i zwiększono ilość wątków przetwarzających
// dane (wykonują one funcję calc_thrd). Niestety nie obserwuje się poprawy. Popraw kod funkcji calc_thrd.

using Data = int;
//class Data {}; //nie zamieszczono skladowych
using PData = std::shared_ptr<Data>;

class Queue {
public:
    Queue() {}
    bool empty_safe() const {
        lock_guard lock(m_);
        return q_.empty();
    }
    bool empty_impl() const { return q_.empty(); }

    PData front() const { //uchwyt do pierwszego elementu
        return q_.front();
    }
    void pop() { //usuwa pierwszy element z kolejki
        q_.pop();
    }
    mutable mutex m_;
    //nie zamieszczono metod do wstawiania elementow
private:
    std::queue<PData> q_;
public:
    void push(PData data) {
        lock_guard lock(m_);
        q_.push(data);
    }
};

void calculate(const Data& d) { //dlugotrwale obliczenia
    cout << "calc start d=" << d << endl;
    this_thread::sleep_for( chrono::milliseconds(1000) );
    cout << "calc end d= " << d << endl;
}

void calc_thrd(Queue& q) {
    while(!q.empty_save()) {
        lock_guard l(q.m_);
        calculate(*q.front());
        q.pop();
    }
}

void calc_thrd_better(Queue& q) {
    while(!q.empty_save()) {
        PData data;
        {
            lock_guard l(q.m_);
            if(!q.empty_impl()) {
                data = q.front();
                q.pop();
            }
        }
        //obliczenia poza sekcja krytyczna
        if(data)
            calculate(*data);
    }
}

int main() {
     Queue q;
     //pominięto tworzenie wątków, które wstawiają obiekty PData do kolejki
     for(int i=0;i<100;++i) {
         q.push(PData(new Data(i)));
     }

     const int NUM = std::thread::hardware_concurrency();
    std::cout << NUM << " concurrent threads are supported by platform.\n";
    std::vector<std::thread> thrds;
    thrds.reserve(NUM);
    for (int i=0; i < NUM; ++i) {
        thrds.emplace_back(&calc_thrd_better, ref(q));
    }
    //join_all
    for(int i=0; i< NUM; ++i)
        thrds[i].join();
}
