// Spróbuj naprawić poniższy kod. Klient zamieszcza następujące wycinki z "logów":
//
// id: 4 data: 2
// id: 1276891168 data: 32518
// id: 7 data: -1
// id: 10 data: 0
// id: 3 data: 2
// id: 7 data: 1
// id: 4 data: 1
// id: 1 data: 1
// id: 3 data: 1
//
// done
// id: 3 data: 0
// a.out: kol2.cpp:18: void Task::update(): Assertion `data.load() > 3' failed.
//
// id: -1331408640 data: 32717
// terminate called after throwing an instance of 'std::out_of_range'
//   what():  map::at
// [1]    18826 IOT instruction (core dumped)  ./a.out

#include <assert.h>
#include <atomic>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <thread>

const int TASK_DONE = 3;
struct Task {
    int id;
    std::atomic<int> data;
    bool done;

    Task(int id) : id(id), data(0), done(false) {}

    void update() {
        if (done) {
            assert(data.load() > TASK_DONE);
            std::cout << "done" << std::endl;
        } else {
            std::cout << "id: " << id << " data: " << data.load() << std::endl;
        }
    }
};

class TaskMap {
public:
    std::shared_ptr<Task> get_or_insert(int id) {
        std::scoped_lock guard(m_);
        auto task = std::shared_ptr<Task>(new Task(id));
        return task_map.try_emplace(id, task).first->second;
    }

    std::shared_ptr<Task> pop(int id) {
        std::scoped_lock guard(m_);
        auto task = task_map.at(id);
        task_map.erase(id);
        return task;
    }

private:
    std::mutex m_;
    std::map<int, std::shared_ptr<Task>> task_map;
};

void updater(TaskMap& task_map) {
    for (int i = 0; i < 1000; ++i) {
        // get ID from client
        int id = rand() % 10 + 1;
        auto& task = *task_map.get_or_insert(id);

        // do some work
        int update = rand() % 4 - 1;
        task.data.fetch_add(update);
        if (task.data.load() > TASK_DONE) {
            task.done = true;
            task_map.pop(id);
        }
        task.update();
    }
}

int main() {
    TaskMap task_map;
    auto updater_ = [&task_map]{
        updater(task_map);
    };
    std::thread thrd1(updater_);
    std::thread thrd2(updater_);
    std::thread thrd3(updater_);
    thrd1.join();
    thrd2.join();
    thrd3.join();
    return 0;
}
