#ifndef GLIB_ASYNC_QUEUE_H
#define GLIB_ASYNC_QUEUE_H

namespace Glib {
	
	/**
	\brief classe generique permettant de gérer un pile de manière thread safe
	*/
	template<typename T>
	class AsyncQueue {
		std::queue<T> async_queue;
		Glib::Mutex   queue_mutex;
		Glib::Cond    queue_cond;
		int           waiting_threads;
		bool          interrupted;
	public:
		AsyncQueue():
		waiting_threads(0),
		interrupted(false)
		{
		}
		/**
		\brief ajoute l'élément en bas de la pile
		\param item l'élément a ajouter
		*/
		void push(const T& item){
			Glib::Mutex::Lock queue_guard(queue_mutex);
			push_unlocked(item);
		}
		void push_unlocked(const T& item){
			async_queue.push(item);
			if(waiting_threads>0)
				queue_cond.signal(); //un seul thread réveillé à la fois
		}
		/**
		\brief renvoit l'élément en haut de la pile, bloque jusqu'à ce qu'il y ait un élément
		\throw une exception est levée si l'opérationa été interrompue par interrupt
		*/
		T pop(){
			Glib::Mutex::Lock queue_guard(queue_mutex);
			return pop_unlocked();
		}
		T pop_unlocked(){
			if(async_queue.empty()){
				waiting_threads++;
				while ((!interrupted)&&async_queue.empty())
					queue_cond.wait(queue_mutex);
				queue->waiting_threads--;
				if(async_queue.empty())
					throw std::logic_error("pop interrupted");
			}
			T result(async_queue.front());
			async_queue.pop();
			return result;
		}
		/**
		\brief essaye de récupérer un élément en haut de la pile
		\return l'element en haut de la pile s'il existe
		\throw génère une exception si aucun élément n'est disponible, ou si l'opération a été interrompue
		*/
		T try_pop(){
			Glib::Mutex::Lock queue_guard(queue_mutex);
			return try_pop_unlocked();
		}
		T try_pop_unlocked(){
			if(async_queue.empty()){
				throw std::logic_error("queue empty");
			}
			T result(async_queue.front());
			async_queue.pop();
			return result;
		}
		/**
		\brief renvoit l'élément en haut de la pile, bloque jusqu'à ce qu'il y ait un élément ou que la durée d'attente soit dépassée
		\throw une exception est levée si l'opérationa été interrompue par interrupt ou si la durée d'attente est dépassée
		*/
		T timed_pop(const Glib::TimeVal& endtime){
			Glib::Mutex::Lock queue_guard(queue_mutex);
			return timed_pop_unlocked(endtime);
		}
		T timed_pop_unlocked(const Glib::TimeVal& endtime){
			if(async_queue.empty()){
				waiting_threads++;
				while ((!interrupted)&&async_queue.empty())
					if(!queue_cond.timed_wait(queue_mutex,endtime))
						break;
				queue->waiting_threads--;
				if(interrupted&&async_queue.empty())
					throw std::logic_error("pop interrupted");
				if(async_queue.empty())
					throw std::logic_error("pop timeout");
			}
			T result(async_queue.front());
			async_queue.pop();
			return result;
		}
		/**
		\brief acquière le verrou sur la pile
		*/
		void lock(){
			queue_mutex.lock();
		}
		/**
		\brief libère le verrour sur la pile
		*/
		void unlock(){
			queue_mutex.unlock();
		}
		
		void interrupt(){
			Glib::Mutex::Lock queue_guard(queue_mutex);
			interrupted = true;
			queue_cond.broadcast(); //tous les threads sont interrompus
		}
		
	};
	
}

#endif

