#ifndef CONCURRENTQUEUE_H
#define CONCURRENTQUEUE_H

#include <queue>
#include <mutex>
#include <condition_variable>

using namespace std;

template <typename T>
class ConcurrentQueue {
private:
    queue<T> queue_;
    mutable mutex mutex_;
    condition_variable condition_;

public:
    // Apparently, if you have a mutex in a class, you can't copy or assign the class to any other class.
    ConcurrentQueue() = default;
    ConcurrentQueue(const ConcurrentQueue&) = delete;             // Prevent copying
    ConcurrentQueue& operator=(const ConcurrentQueue&) = delete;  // Prevent assignment

    void push(T value) {
        lock_guard<mutex> lock(mutex_);
        queue_.push(move(value));
        condition_.notify_one();
    }

    bool try_pop(T& value) {
        lock_guard<mutex> lock(mutex_);
        if (queue_.empty()) {
            return false;
        }
        value = move(queue_.front());
        queue_.pop();
        return true;
    }

    T wait_and_pop() {
        unique_lock<mutex> lock(mutex_);
        condition_.wait(lock, [this] { return !queue_.empty(); });
        T value = move(queue_.front());
        queue_.pop();

        return value;
    }

    bool empty() const {
        lock_guard<mutex> lock(mutex_);
        return queue_.empty();
    }

    int size() {
        lock_guard<mutex> lock(mutex_);
        return queue_.size();
    }
};

#endif
