/*
    libmaus2
    Copyright (C) 2009-2013 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#if ! defined(SYNCHRONOUSCONDITIONQUEUE_HPP)
#define SYNCHRONOUSCONDITIONQUEUE_HPP

#include <libmaus2/LibMausConfig.hpp>

#include <libmaus2/parallel/StdMutex.hpp>
#include <libmaus2/parallel/StdSpinLock.hpp>
#include <libmaus2/parallel/StdSemaphore.hpp>
#include <deque>

namespace libmaus2
{
        namespace parallel
        {
                template<typename _value_type>
                struct StdSynchronousConditionQueue
                {
                	typedef _value_type value_type;
                	typedef StdSynchronousConditionQueue<value_type> this_type;
                	typedef std::unique_ptr<this_type> unique_ptr_type;

                        std::deque < value_type > Q;
                        StdSpinLock lock;
                        libmaus2::parallel::StdSemaphore semaphore;

                        this_type * parent;

                        StdSynchronousConditionQueue() : Q(), lock(), semaphore(), parent(0)
                        {

                        }

                        virtual ~StdSynchronousConditionQueue()
                        {

                        }

                        bool empty()
                        {
                        	return getFillState() == 0;
                        }

			size_t size()
                        {
                        	return getFillState();
                        }

			size_t getFillState()
                        {
                        	libmaus2::parallel::ScopeStdSpinLock llock(lock);
                                size_t const fill = Q.size();
                                return fill;
                        }

                        void enque(value_type const q)
                        {
                        	{
	                        	libmaus2::parallel::ScopeStdSpinLock llock(lock);
        	                        Q.push_back(q);
				}

                                semaphore.post();

                                if ( parent )
	                                parent->enque(q);
                        }
                        virtual value_type deque()
                        {
                                semaphore.wait();

                        	libmaus2::parallel::ScopeStdSpinLock llock(lock);
                                value_type const v = Q.front();
                                Q.pop_front();
                                return v;
                        }
                        virtual bool timeddeque(value_type & v)
                        {
                                if ( semaphore.timedWait() )
                                {
	                        	libmaus2::parallel::ScopeStdSpinLock llock(lock);
        	                        v = Q.front();
                	                Q.pop_front();
                	                return true;
				}
				else
				{
					return false;
				}
                        }
                        virtual bool trydeque(value_type & v)
                        {
                        	bool const ok = semaphore.trywait();

                                if ( ok )
                                {
	                        	libmaus2::parallel::ScopeStdSpinLock llock(lock);
	                        	v = Q.front();
                	                Q.pop_front();
                	                return true;
				}
				else
				{
					return false;
				}
                        }
                        value_type peek()
                        {
                                {
                                        libmaus2::parallel::ScopeStdSpinLock slock(lock);
                                        if ( Q.size() )
                                        {
        	                        	value_type const v = Q.front();
        	                        	return v;
                                         }
                                }

				::libmaus2::exception::LibMausException se;
				se.getStream() << "SynchronousConditionQueue::peek() called on empty queue." << std::endl;
				se.finish();
				throw se;
                        }

                        bool peek(value_type & v)
                        {
                                libmaus2::parallel::ScopeStdSpinLock slock(lock);
                        	bool ok = Q.size() != 0;
                        	if ( ok )
                        		v = Q.front();
				return ok;
                        }
                };
        }
}
#endif

