/*
 *  ZThreads, a platform-independant, multithreading and 
 *  synchroniation library
 *
 *  Copyright (C) 2000-2002, Eric Crahen, See LGPL.TXT for details
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  SUNY @ Buffalo, hereby disclaims all copyright interest in the
 *  ZThreads library written by Eric Crahen
 */

#ifndef __ZTBLOCKINGQUEUE_H__
#define __ZTBLOCKINGQUEUE_H__

#include "zthread/ZThread.h"
#include "zthread/Condition.h"
#include "zthread/Guard.h"
#include "zthread/Mutex.h"
#include "zthread/Queue.h"

#include <deque>

namespace ZThread {

/**
 * @class BlockingQueue
 *
 * @author Eric Crahen <crahen@cse.buffalo.edu>
 * @date <2002-02-24T09:22:07-0500>
 * @version 1.3.4
 *
 * This is a slightly more useful queue than the LockedQueue. It's methods
 * are guarded, and it will block threads trying to withdraw an item from
 * an empty queue.
 *
 */

template <class T>
class BlockingQueue : public Queue<T> {
private:

  BlockingQueue(BlockingQueue*);
  BlockingQueue(const BlockingQueue&);
  BlockingQueue& operator=(const BlockingQueue&);

protected:

  //! Signaled when empty
  Condition _notEmpty;
  //! Serialize access
  Mutex _lock;
  //! Item queue
  std::deque<T> _data;
  //! Cancellation flag
  bool _canceled;

public:

  /**
   * Create a new BlockingQueue
   */
  BlockingQueue() throw()
    : _canceled(false) {}

  /**
   * Destroy this Queue
   */
  virtual ~BlockingQueue() throw() {}

  /**
   * Add a new object to this Queue
   *
   * @param T object to add
   * 
   * @exception InterruptedException
   * @exception CancelationException - the queue has 
   * been canceled.
   */
  virtual void add(T item) 
    throw(Interrupted_Exception, Cancelation_Exception) {

    Guard<Mutex> g(_lock);
    
    if(_canceled)
      throw Cancelation_Exception();

    _data.push_back(item);

    _notEmpty.signal();

  }

  /**
   * Get an object from this Queue
   *
   * @return T next available object
   * 
   * @exception InterruptedException 
   * @exception CancelationException - the queue is empty and has 
   * been canceled.
   */
  virtual T next()
    throw(Interrupted_Exception, Cancelation_Exception) {

    Guard<Mutex> g(_lock);

    while(_data.size() == 0 && !_canceled)
      _notEmpty.wait(_lock);

    if(_data.size() == 0 )
      throw Cancelation_Exception();
    
  
    T item = _data.front();
    _data.pop_front();
    
    return item;

  }


  /**
   * Get an object from this Queue within a given amount of time
   * 
   * @param unsigned long time limit 
   *
   * @return T next available object
   * 
   * @exception InterruptedException
   * @exception CancelationException - the queue is empty and has 
   * @exception Timeout_Exception - thrown if the given amount
   * of time has expired and the queue could not return an item
   * been canceled.
   */
  virtual T next(unsigned long timeout)
    throw(Interrupted_Exception, Cancelation_Exception, Timeout_Exception) {

    Guard<Mutex> g(_lock, timeout);

    while(_data.size() == 0 && !_canceled) {
      if(!_notEmpty.wait(_lock, timeout))
        throw Timeout_Exception();
    }

    if(_data.size() == 0 )
      throw Cancelation_Exception();
  
    T item = _data.front();
    _data.pop_front();
    
    return item;

  }


  /**
   * Cancel this queue. No more objects can be added to it
   *
   * @exception InterruptedException 
   *
   * @post - The next() method will continue to return objects until 
   * the queue has been emptied. After the queue is emptied, further 
   * calls to next() will throw CancelationException
   *
   * The add() method will begin to throw CancelationException and no
   * item will be accepted by this queue
   */
  virtual void cancel()
    throw(Interrupted_Exception) {

    Guard<Mutex> g(_lock);
    _notEmpty.broadcast();
    _canceled = true;

  }

  /**
   * Check the cancelation status of this Queue
   * 
   * @return bool - Queue cancled
   */
  inline virtual bool isCanceled()
    throw(Interrupted_Exception) {

    // Faster check since the queue will not become un-canceled
    if(_canceled)
      return true;
    
    Guard<Mutex> g(_lock);

    return _canceled;

  }

  /**
   * Test this queue to see if it is empty. This method may or may not
   * block the caller, it is completely up to the implementation to decide
   * weather or not it is appropriate to block the caller.
   *
   * @return boolean - true if empty  
   * @exception InterruptedException 
   */
  virtual bool empty()
    throw(Interrupted_Exception) {

    Guard<Mutex> g(_lock);
    return (_data.size() == 0);
  }

};

};

#endif
