#ifndef __PROTECTEDANDSYNCHRONIZEDQUEUE_H__
#define __PROTECTEDANDSYNCHRONIZEDQUEUE_H__

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

    
template<class T>
class ProtectedAndSynchronizedQueue
{
public:
        ProtectedAndSynchronizedQueue()
        {
            m_terminateFlag = false;
        }
        
        virtual ~ProtectedAndSynchronizedQueue() {};
        
        /*
         * add a new element to the back of the queue
         */
        void push_back(T newElement)
        {
            {
                std::lock_guard<std::mutex> lock(m_mutex);
                m_queue.push(newElement);
            }
           m_CV.notify_one();
        }
        
        /*
         * retrieve the first element of the queue and remove it
         * from the queue
         */
        T pop_front()
        {
            // Wait on queue blocking
            std::unique_lock<std::mutex> muxLock(m_mutex);
            while (m_queue.empty())
            {
                m_CV.wait(muxLock);

                // If we've been asked to terminate, break out and return
                // NOTE: the return value is garbage if terminate has been 
                // called prior this call returning
                if (m_terminateFlag)
                {
                    T elementToReturn;
                    return elementToReturn;
                }
            }

            T elementToReturn = m_queue.front();
            m_queue.pop();
            muxLock.unlock();
            
            return elementToReturn;
        }

        /*
         * retrieve the first element of the queue and remove it
         * from the queue
         */
        T pop_front(bool waitOnEmptyQueue, bool& elementReturned)
        {
            // Wait on queue blocking
            std::unique_lock<std::mutex> muxLock(m_mutex);
            if (waitOnEmptyQueue)
            {
                while (m_queue.empty())
                {
                    m_CV.wait(muxLock);

                    // If we've been asked to terminate, break out and return
                    // NOTE: the return value is garbage if terminate has been 
                    // called prior this call returning
                    if (m_terminateFlag)
                    {
                        break;
                    }
                }
            }

            T elementToReturn;
            if (m_queue.size() > 0)
            {
                elementReturned = true;
                elementToReturn = m_queue.front();
                m_queue.pop();
            }
            else
            {
                elementReturned = false;
                elementToReturn = m_queue.front();
            }


            return elementToReturn;
        }
        
        /*
         * terminate any waiting pop_front() requests
         */
        void terminate()   
        { 
            m_terminateFlag = true; 
            m_CV.notify_all(); 
        }
        
        bool empty()
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            return m_queue.empty();
        }
        
        uint64_t size()
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            return m_queue.size();
        }
        
private:
    bool m_terminateFlag;
    
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_CV;
    
};

