/*
 *  ZThreads, a platform-independant, multithreading and 
 *  synchroniation library
 *
 *  Copyright (C) 2001, 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 __ZTPOSIXMONITOR_H__
#define __ZTPOSIXMONITOR_H__

#include "Time.h"

#include <zthread/ZThread.h>
#include <pthread.h>
#include <errno.h>

#ifndef NDEBUG
#include <algorithm>
#include <vector>
#endif

#ifdef USE_STRESS
#include <unistd.h>
#endif

namespace ZThread {

/**
 * @class Monitor
 * @author Eric Crahen <crahen@cse.buffalo.edu>
 * @date <2002-03-06T19:50:31-0500>
 * @version 2.0.4
 *
 * The Monitor template can be used to wrap an object with a 
 * simple non-recursive, mesa-style monitor.
 *
 * The monitor can be acquire()d and release()d by no more than
 * one thread at a given time.
 *
 * A thread that has acquire()d the lock can wait() on the monitor.
 * The lock is released and the thread wait()ing will be blocked 
 * until notify() is invoked by some other thread, 'setting' the monitor.
 *
 * Being 'set' is a sticky property, (making this object behave somewhat 
 * like a Win32 auto-reset event object) meaning a notify() is garunteed
 * to unblock a thread. If there is no thread waiting on this monitor, 
 * the next wait() will not block.

 */
template <class T>
class Monitor {

  //! Condition variable used to block a thread.
  pthread_cond_t _cond;

  //! Underlying lock to serialze access to this monitor.
  pthread_mutex_t _lock;

  //! Waiting flag, to avoid uneccessary signals
  volatile bool _waiting; 

  //! Sticky flag, so the signals aren't lost
  volatile bool _set; 

  //! Object guarded by this Monitor 
  T _instance;

#ifndef NDEBUG
  typedef std::vector<pthread_t> WaiterList;
  WaiterList _waiters;

  pthread_mutex_t _waiterLock;
#endif

 public:
  
  /**
   * Create a new monitor.
   */
  Monitor();

  /**
   * Destroy the monitor.
   */
  ~Monitor();

  /**
   * Acquire the lock for this monitor. This will block the caller
   * until the lock can be acquired.
   */
  void acquire();

  /**
   * Try to acquire the lock for this monitor. This will not block the caller,
   * but rather acquire the lock or not and return an indication of weather or
   * not that operator succeeded.
   *
   * @bool
   */
  bool tryAcquire();

  /**
   * Release the lock for this monitor. This will fail if
   * the lock is released without first having been acquire()d.
   */
  void release();
  
  /**
   * When the lock is has been acquire()d, a thread can wait on
   * this monitor object for an indefinent amount of time. This will block
   * the calling thread until this monitor is 'set', releasing the lock
   * while it waits. Once set, the caller is unblocked and the lock 
   * will be re-acquire()d.
   */
  inline void wait() {
    wait(0);
  }

  /**
   * When the lock is has been acquire()d, a thread can wait on
   * this monitor object for a given amount of time. This will block
   * the calling thread until this monitor is 'set', releasing the lock
   * while it waits. Once set, the caller is unblocked and the lock 
   * will be re-acquire()d.
   *
   * @param ms Maximum milliseconds to wait for a signal. '0' will
   * wait forever.
   * @return true unless the maximum wait time expired.
   */
  bool wait(unsigned long);

  /**
   * Set this monitor. If there is a thread blocked on this monitor object
   * it will be signaled and released. If there is no waiter, the monitors
   * status is 'set' and the next attempt to wait() will return w/o blocking.
   *
   * @pre the caller *MUST* be holding the lock for this monitor. Ideally, the 
   * lock could be recursive, and this requirement could be enforced nicely;
   * but not all systems implement a reliable recurisve mutex that can be 
   * used with a condition variable (solaris, possibly others..)
   */
  void notify();

  /**
   * Test the Monitor, to check if the monitor is blocking a thread.
   */
  inline bool hasWaiter() const {
    return _waiting;
  }

  /**
   * Overload the casting operator on this Monitor object to expose the 
   * object that it is guarding.
   *
   * @return T
   */
  inline operator T() {
    return _instance;
  }

  /**
   * Provide access to the object guarded by this Monitor
   *
   * @return T*
   */
  inline T* operator->() {
    return &_instance;
  }


};

template <class T>
  Monitor<T>::Monitor() : _waiting(false), _set(false) {
  
  ZTCHECK(pthread_cond_init(&_cond, 0));
  ZTCHECK(pthread_mutex_init(&_lock, 0));

#ifndef NDEBUG
  ZTCHECK(pthread_mutex_init(&_waiterLock, 0));
#endif

}
 
template <class T>
  Monitor<T>::~Monitor() {
  
  ZTCHECK(pthread_cond_destroy(&_cond));
  ZTCHECK(pthread_mutex_destroy(&_lock));
  
#ifndef NDEBUG
  ZTCHECK(pthread_mutex_destroy(&_waiterLock));
#endif

}

template <class T>
  inline void Monitor<T>::acquire() {

#ifndef NDEBUG
  ZTCHECK(pthread_mutex_lock(&_waiterLock));
  
  pthread_t self = pthread_self();
  assert(std::find(_waiters.begin(), _waiters.end(), self) == _waiters.end());
  _waiters.push_back(self);

  ZTCHECK(pthread_mutex_unlock(&_waiterLock));
#endif

  ZTCHECK(pthread_mutex_lock(&_lock));

}

template <class T>
  inline bool Monitor<T>::tryAcquire() {

  return pthread_mutex_trylock(&_lock) == 0;

}

template <class T>
  inline void Monitor<T>::release() {

#ifndef NDEBUG
  ZTCHECK(pthread_mutex_lock(&_waiterLock));
  
  pthread_t self = pthread_self();
  WaiterList::iterator i = std::find(_waiters.begin(), _waiters.end(), self);
  assert(i != _waiters.end()); 
  _waiters.erase(i);

  ZTCHECK(pthread_mutex_unlock(&_waiterLock));
#endif

  ZTCHECK(pthread_mutex_unlock(&_lock));

#ifdef USE_STRESS
  // Simulate some stress with an uninterruptable wait
  usleep(100000*rand()%10);
#endif

}

template <class T>
  bool Monitor<T>::wait(unsigned long ms) {

#ifndef NDEBUG  
  ZTCHECK(pthread_mutex_lock(&_waiterLock));
  
  pthread_t self = pthread_self();
  assert(std::find(_waiters.begin(), _waiters.end(), self) != _waiters.end());

  ZTCHECK(pthread_mutex_unlock(&_waiterLock));

  assert(this == &ThreadImpl::current()->getMonitor());
  assert(!_waiting);

#endif
  
  // Check the flag, if set toggle the flag and return
  // as if this object was signaled.
  if(_set) {
    
    _set = false;
    return true;
    
  }

  // Wait, ignoring signals
  _waiting = true;
  int status = 0;

  // Wait forever if the timeout is 0
  if(ms == 0) {   
    
    do {
      status = pthread_cond_wait(&_cond, &_lock);
    } while(status == EINTR && !_set);
      
  } else {

    // Find the target time
    Time t; 
    t += ms;

    // Convert to a timespec
    struct ::timespec timeout;   
         
    timeout.tv_sec = t.seconds();
    timeout.tv_nsec = t.milliseconds()*1000000;

    // Wait with a timeout
    do {    
      status = pthread_cond_timedwait(&_cond, &_lock, &timeout);
    } while(status == EINTR && !_set);
      
  }

  // Update the waiting status
  _waiting = false;
  _set = false;

  // Check the results. Either it timeouts or gets a signal
  assert(status == ETIMEDOUT || status == 0);

  // Return the results
  return (status != ETIMEDOUT);

}

template <class T>
  void Monitor<T>::notify() {

#ifndef NDEBUG
  ZTCHECK(pthread_mutex_lock(&_waiterLock));
  
  pthread_t self = pthread_self();
  assert(std::find(_waiters.begin(), _waiters.end(), self) != _waiters.end());

  ZTCHECK(pthread_mutex_unlock(&_waiterLock));
#endif
 
  // Set the flag if there are no waiters (sticky).
  if(!_waiting) 
    _set = true;

  // If there is a waiter on this object then
  // send the signal.
  else 
    ZTCHECK(pthread_cond_signal(&_cond));

}

};

#endif
