/*
 * This is an implementation of condition variables built using
 * semaphores, following the same scheme presented in the course
 * notes.
 *
 * NB the use of InterruptedException here is somewhat problematic.
 * As the code stands the condition variable may be left in a 
 * broken state -- we don't make any attempt to correct variables
 * such as num_waiters, the value of the cv_sleep semaphore or
 * the question of whether the caller of CVWait is left holding
 * or not holding the mutex.
 */

public class CondVar {

	/*
	 * num_waiters is a count of the number of threads that have
	 * invoked a CVWait method on this condition variable.  We
	 * use it when a thread calls CVNotify in order to work out
	 * whether any thread is ready to be woken.
	 */

	int num_waiters;

	/*
	 * cv_lock is used internally in the condition variable's
	 * implementation in order to make updates to num_waiters
	 * occur atomically and in order to allow that field to 
	 * be updated atomically with the calling thread releasing
	 * the lock to the mutual exclusion lock it associates with
	 * this condition variable.
	 */

	Mutex cv_lock;

	/*
	 * cv_sleep is used internally as the mechanism for getting
	 * a waiting thread to block and for allowing a waiting thread
	 * to be woken.
	 */

	CountingSemaphore cv_sleep;

	public CondVar() {
		num_waiters = 0;
		cv_lock = new Mutex();
		cv_sleep = new CountingSemaphore(0);
	}

	/*
	 * The CVWait operation is intended to atomically
	 *
	 *    - Release the caller's lock on "m": m.release()
	 *
	 *    - Register that the caller is now waiting to be woken
	 *      up again: num_waiters++
	 *
	 *    - Block the caller: cv_sleep.P()
	 *
	 * The caller is blocked until someone calls "notify" on the
	 * same condition variable and the system selects the caller as
	 * the thread to wake.  At that point the thread that called
	 * CVWait must re-acquire the mutex m: m.acquire().
	 */

	public void CVWait(Mutex m) throws InterruptedException {
		cv_lock.acquire();
		num_waiters++;
		m.release();
		cv_lock.release();
		cv_sleep.P();
		m.acquire();
	}

	/*
	 * The CVNotify operation selects one thread blocked on the 
	 * same condition variable and wakes it using cv_sleep.V().
	 * Note that the lock cv_lock is needed here to make sure that
	 * the various updates to num_waiters are not interfered with.
	 */

	public void CVNotify() throws InterruptedException {
		cv_lock.acquire();
		if (num_waiters > 0) {
			cv_sleep.V();
			num_waiters--;
		}
		cv_lock.release();
	}

	public void CVNotifyAll() throws InterruptedException {
		cv_lock.acquire();
		while (num_waiters > 0) {
			cv_sleep.V();
			num_waiters--;
		}
		cv_lock.release();
	}
}
