#include <iostream>
#include <cstdlib>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>

class Item
{
public:
    Item() : m_id(0) {}
    Item(const std::uint32_t id) : m_id(id) {}
    
    std::uint32_t GetId() const { return m_id; }
    
private:
    std::uint32_t m_id;
};

class Producer
{
public:
    Producer(): m_count(0) {}
    
    Item Produce()
    { 
        //std::this_thread::sleep_for(std::chrono::milliseconds(200));
        Item item(m_count++);
        std::cout << "Item №" << item.GetId() << "was produced" << std::endl;
        
        return item;
    }
    
    void Serve(std::vector<Item>& buffer,
               std::mutex& m,
               std::condition_variable& cv_is_not_full,
               std::condition_variable& cv_is_not_empty)
    {
        const auto is_not_full = [&buffer]() { return buffer.size() != 10; };
        while (true)
        {
            std::unique_lock<std::mutex> lock(m);
            cv_is_not_full.wait(lock, is_not_full);
                
            buffer.push_back(Produce());
            cv_is_not_empty.notify_one();
        }
    }
    
private:
    std::uint32_t m_count;
};

class Consumer
{
public:    
    void Consume(const Item& item)
    {
        //std::this_thread::sleep_for(std::chrono::milliseconds(200));
        std::cout << "Item №" << item.GetId() << "was consumed" << std::endl;
    }
    
    void Serve(std::vector<Item>& buffer,
               std::mutex& m,
               std::condition_variable& cv_is_not_full,
               std::condition_variable& cv_is_not_empty)
    {
        const auto is_not_empty = [&buffer]() { return !buffer.empty(); };
        while (true)
        {
            std::unique_lock<std::mutex> lock(m);
            cv_is_not_empty.wait(lock, is_not_empty);
                
            Consume(buffer.back());
            buffer.pop_back();
            cv_is_not_full.notify_one();
        }
    }
};

int main()
{
    std::vector<Item> buffer;

    std::mutex m;
    std::condition_variable cv_is_not_full;
    std::condition_variable cv_is_not_empty;
        
    std::thread producer_thread(&Producer::Serve,
                                Producer(),
                                std::ref(buffer),
                                std::ref(m),
                                std::ref(cv_is_not_full), 
                                std::ref(cv_is_not_empty));

    std::thread consumer_thread(&Consumer::Serve,
                                Consumer(),
                                std::ref(buffer),
                                std::ref(m),
                                std::ref(cv_is_not_full),
                                std::ref(cv_is_not_empty));
    
    producer_thread.join();
    consumer_thread.join();
}