#ifndef BOUNDED_BUFFER_H_
#define BOUNDED_BUFFER_H_

#include <pthread.h>		// has pthread_ routines

// ---- Class for buffer of fixed size, with synchronization ---------

// This version uses mutex locks and condition variables to perform 
//   the needed synchronization.

// If compiled with DEBUG flag:
//   Prints informational message to cerr.
//   Requires "outLock" declared as a global variable in the calling
//     program.
#ifdef DEBUG
#include "pthreads-lock.h"
extern lockObj outLock;
#endif // DEBUG

template <typename T>
class boundedBuffer {
public:

  // Constructor.
  boundedBuffer(const int sz) {
    bufferSize = sz;
    buffer = new T[sz];
    nextToRemove = 0;
    numElements = 0;
    pthread_mutex_init(&bufferLock, NULL);
    pthread_cond_init(&bufferNotEmpty, NULL);
    pthread_cond_init(&bufferNotFull, NULL);
  }

  // Destructor.
  ~boundedBuffer(void) {
    delete [] buffer;
    pthread_mutex_destroy(&bufferLock);
    pthread_cond_destroy(&bufferNotEmpty);
    pthread_cond_destroy(&bufferNotFull);
  }

  // Put "itm" into buffer, with appropriate synchronization.
  void put(const T itm) {
    pthread_mutex_lock(&bufferLock);
    if (numElements == bufferSize)
      pthread_cond_wait(&bufferNotFull, &bufferLock);
#ifdef DEBUG
    outLock.lock();
    cerr << "**Adding item to buffer, numElements = " << numElements
	 << endl;
    outLock.unlock();
#endif // DEBUG
    unsigned int next = (nextToRemove + numElements) % bufferSize;
    buffer[next] = itm;
    ++numElements;
    pthread_cond_signal(&bufferNotEmpty);
    pthread_mutex_unlock(&bufferLock);
  }

  // Get item from buffer and return, with appropriate synchronization.
  T get(void) {
    pthread_mutex_lock(&bufferLock);
    if (numElements == 0)
      pthread_cond_wait(&bufferNotEmpty, &bufferLock);
#ifdef DEBUG
    outLock.lock();
    cerr << "**Removing item from buffer, numElements = " << numElements
	 << endl;
    outLock.unlock();
#endif // DEBUG
    T returnVal = buffer[nextToRemove];
    nextToRemove = (nextToRemove + 1) % bufferSize;
    --numElements;
    pthread_cond_signal(&bufferNotFull);
    pthread_mutex_unlock(&bufferLock);
    return returnVal;
  }

private:
  // Make copy constructor and assignment operator private
  //   so they can't be used (since it's not clear this would 
  //   make sense).
  boundedBuffer(const boundedBuffer & bb);
  boundedBuffer & operator= (const boundedBuffer & bb);

  // Member variables.
  // Variables for buffer -- implemented as a circular array:
  T * buffer;
  unsigned int bufferSize;
  unsigned int nextToRemove;
  unsigned int numElements;
  // Variables for synchronization:
  pthread_mutex_t bufferLock;	 // lock for shared variables
  pthread_cond_t bufferNotEmpty; // get() waits on this condition 
				 //   variable if buffer is empty
  pthread_cond_t bufferNotFull;	 // put() waits on this condition
				 //   variable if buffer is full
};
#endif // BOUNDED_BUFFER_H_