#pragma once

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

template<typename T>
class threadsafe_queue
{
public:
	threadsafe_queue() {}
	threadsafe_queue(threadsafe_queue const& other)
	{
		std::lock_guard<std::mutex> lk(other.m_mut);
		m_queue = other.m_queue;
	}

	void push(T data)
	{
		std::lock_guard<std::mutex> lk(m_mut);
		m_queue.push(data);
		m_cond.notify_one();
	}

	void wait_and_pop(T& data)
	{
		std::unique_lock<std::mutex> lk(m_mut);
		m_cond.wait
		(
			lk,
			[this]
			{
				return !m_queue.empty();
			}
		);
		data = m_queue.front();
		m_queue.pop();
	}
	std::shared_ptr<T> wait_and_pop()
	{
		std::unique_lock<std::mutex> lk(m_mut);
		m_cond.wait
		(
			lk,
			[this]
			{
				return !m_queue.empty();
			}
		);
		std::shared_ptr<T> res(std::make_shared<T>(m_queue.front()));
		m_queue.pop();
		return res;
	}

	bool try_pop(T& data)
	{
		std::lock_guard<std::mutex> lk(m_mut);

		if (m_queue.empty())
			return false;

		data = m_queue.front();
		m_queue.pop();
		return true;
	}
	std::shared_ptr<T> try_pop()
	{
		std::lock_guard<std::mutex> lk(m_mut);

		if (m_queue.empty())
			return std::shared_ptr<T>();

		std::shared_ptr<T> res(std::make_shared<T>(m_queue.front()));
		m_queue.pop();
		return res;
	}

	bool empty() const
	{
		std::lock_guard<std::mutex> lk(m_mut);
		return m_queue.empty();
	}
private:
	mutable std::mutex		m_mut;
	std::queue<T>			m_queue;
	std::condition_variable	m_cond;
};
