/*
    libmaus2
    Copyright (C) 2009-2015 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(STDTERMINATABLESYNCHRONOUSQUEUE_HPP)
#define STDTERMINATABLESYNCHRONOUSQUEUE_HPP

#include <libmaus2/parallel/StdSynchronousQueue.hpp>

namespace libmaus2
{
        namespace parallel
        {
                /**
                 * condition variable based version
                 **/
                template<typename _value_type>
                struct StdTerminatableSynchronousQueue
                {
                        typedef _value_type value_type;
                        typedef StdTerminatableSynchronousQueue<value_type> this_type;

                        std::atomic<int> terminated;
                        libmaus2::parallel::StdSpinLock terminatedlock;

                        std::atomic<int> qsize;
                        libmaus2::parallel::StdSpinLock qsizelock;

                        std::atomic<int> numwaiting;
                        libmaus2::parallel::StdSpinLock numwaitinglock;

                        libmaus2::parallel::StdMutex mutex;
                        libmaus2::parallel::StdSemaphore cond;
                        std::deque<value_type> Q;

                        StdTerminatableSynchronousQueue()
                        : terminated(0), terminatedlock(), qsize(0), qsizelock(), numwaiting(0), numwaitinglock(), Q()
                        {
                        }

                        int getNumWaiting()
                        {
				int lnumwaiting;

                                {
				libmaus2::parallel::StdSpinLock::scope_lock_type snumwaitinglock(numwaitinglock);
				lnumwaiting = static_cast<int>(numwaiting);
				}

				return lnumwaiting;
                        }

                        void setTerminated()
                        {
				libmaus2::parallel::StdSpinLock::scope_lock_type sterminatedlock(terminatedlock);
				terminated = 1;
                        }


                        int getTerminated()
                        {
				int lterminated;
				{
				libmaus2::parallel::StdSpinLock::scope_lock_type sterminatedlock(terminatedlock);
				lterminated = static_cast<int>(terminated);
				}
				return lterminated;
                        }

                        void incrementQSize()
                        {
				libmaus2::parallel::StdSpinLock::scope_lock_type sqsizelock(qsizelock);
				qsize += 1;
                        }

                        void decrementQSize()
                        {
				libmaus2::parallel::StdSpinLock::scope_lock_type sqsizelock(qsizelock);
				qsize -= 1;
                        }

                        int getQSize()
                        {
				int lqsize;

				{
        				libmaus2::parallel::StdSpinLock::scope_lock_type sqsizelock(qsizelock);
	        			lqsize = static_cast<int>(qsize);
				}

				return lqsize;
                        }

                        ~StdTerminatableSynchronousQueue()
                        {
                        }

                        void enque(value_type const v)
                        {
				if ( isTerminated() )
					throw std::runtime_error("StdTerminatableSynchronousQueue::enque: Queue is terminated");

				{
	                                libmaus2::parallel::ScopeStdMutex M(mutex);
        	                        Q.push_back(v);
                                }

                                cond.post();

                                incrementQSize();
                        }

                        void putback(value_type const v)
                        {
				{
	                                libmaus2::parallel::ScopeStdMutex M(mutex);
        	                        Q.push_front(v);
                                }

                                cond.post();

                                incrementQSize();
                        }

                        size_t getFillState()
                        {
				return getQSize();
                        }

                        bool isTerminated()
                        {
				return getTerminated();
			}

                        void terminate()
                        {
				setTerminated();

				while ( getNumWaiting() > 0 )
					cond.post();
                        }

                        struct NumWaitingObject
                        {
				std::atomic<int> & numwaiting;
				libmaus2::parallel::StdSpinLock & numwaitinglock;

				NumWaitingObject(
					std::atomic<int> & rnumwaiting,
					libmaus2::parallel::StdSpinLock & rnumwaitinglock
				) : numwaiting(rnumwaiting), numwaitinglock(rnumwaitinglock)
				{
					libmaus2::parallel::ScopeStdSpinLock slock(numwaitinglock);
					numwaiting += 1;
				}

				~NumWaitingObject()
				{
					libmaus2::parallel::ScopeStdSpinLock slock(numwaitinglock);
					numwaiting -= 1;
				}
			};

                        value_type deque()
                        {
				NumWaitingObject NWO(numwaiting,numwaitinglock);

                                while ( true )
                                {
                                        {
                                                libmaus2::parallel::ScopeStdMutex M(mutex);

                                                if ( getQSize() )
                                                {
                                                        value_type v = Q.front();
                                                        Q.pop_front();
                                                        decrementQSize();
                                                        return v;
                                                }
                                        }

					if ( isTerminated() )
					{
						throw std::runtime_error("StdTerminatableSynchronousQueue::deque: Queue is terminated");
					}
					else
					{
						cond.timedWait();
					}
                                }
                        }
                };
        }
}
#endif
