#include <mutex>
#include <memory>
#include <condition_variable>
#include <chrono>
#include <thread> // std::this_thread::sleep_for

//   | tail
//   V push here
// +----+   +----+   +----+   +----+   +----+
// |    |<--|    |<--|    |<--|    |<--|    |<-- head
// +----+   +----+   +----+   +----+   +----+    pop here
template<typename T>
class queue_1t
{
private:
    struct node
    {
        T data;
        std::unique_ptr<node> next;
        node(T data_):
            data(std::move(data_))
        {}
    };
    std::unique_ptr<node> head;
    node* tail;
public:
    queue_1t() {}
    queue_1t(const queue_1t& other)=delete;
    queue_1t& operator=(const queue_1t& other)=delete;
    bool empty() const
    {
        return !head;
    }
    std::shared_ptr<T> try_pop()
    {
        if(!head)
        {
            return std::shared_ptr<T>();
        }
        std::shared_ptr<T> const res( std::make_shared<T>(std::move(head->data)));
        std::unique_ptr<node> const old_head=std::move(head);
        head=std::move(old_head->next);
        return res;
    }
    void push(T new_value)
    {
        std::unique_ptr<node> p(new node(std::move(new_value)));
        node* const new_tail=p.get();
        if(tail)
        {
            tail->next=std::move(p);
        }
        else
        {
            head=std::move(p);
        }
        tail=new_tail;
    }
};

template<typename T>
class threadsafe_queue
{
    private:
        struct node
        {
            std::shared_ptr<T> data;
            std::unique_ptr<node> next;
        };
        std::mutex head_mutex;
        std::unique_ptr<node> head;
        mutable std::mutex tail_mutex;
        node* tail;

        node* get_tail() const 
        {
            //std::lock_guard<std::mutex> tail_lock(tail_mutex); // !!!
            return tail;
        }
#if 1 // correct
        std::unique_ptr<node> pop_head()
        {
            std::lock_guard<std::mutex> head_lock(head_mutex);
            if(head.get()==get_tail()) // get_tail() is inside the lock
            {
                return nullptr;
            }
            std::unique_ptr<node> old_head=std::move(head);
            head=std::move(old_head->next);
            return old_head;
        }
#else // incorrect implementation
        std::unique_ptr<node> pop_head() 
        {
            node* const old_tail=get_tail(); // !!
            std::lock_guard<std::mutex> head_lock(head_mutex);
            if(head.get()==old_tail)
            {
                return nullptr;
            }
            std::unique_ptr<node> old_head=std::move(head);
            head=std::move(old_head->next);
            return old_head;
        }
#endif
    public:
        threadsafe_queue(): head(new node),tail(head.get()) {}
        threadsafe_queue(const threadsafe_queue& other)=delete;
        threadsafe_queue& operator=(const threadsafe_queue& other)=delete;
        std::shared_ptr<T> try_pop()
        {
            std::unique_ptr<node> old_head=pop_head();
            return old_head?old_head->data:std::shared_ptr<T>();
        }
        void push(T new_value)
        {
            // concurrency prepare data
            std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value)) );
            std::unique_ptr<node> p(new node);
            node* const new_tail=p.get();
            
            // now lock
            std::lock_guard<std::mutex> tail_lock(tail_mutex);
#if 0 // both are correct
            node* old_tail = tail;
            tail=new_tail;
            old_tail->data=new_data;
            old_tail->next=std::move(p);
#else 
            tail->data=new_data;
            tail->next=std::move(p);
            tail=new_tail;
            //std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
#endif
        }
        bool empty() const // single threaded - just for testing
        {
            return (head.get()==get_tail());
        }
};