package anastore.util;

/**
 * A synchronization barrier based on a monotonically increasing
 * variable.  The expected sequence of operations for using this
 * barrier is,
 *<ul>
 * <li>Wait for the variable to reach some expected value.  One of the
 * first waits performed on the barrier should wait for 0.
 *
 * <li>Do some operation.
 *
 * <li>Update the barrier to some higher value that either is or will
 * be waited for.
 *</ul>
 *<p>
 * The barrier asserts a number of invariants, such that no waiter is
 * ever surpassed and that two waits for the same goal value cannot
 * both complete.
 */
public class BatonBarrier
{
    private static final boolean LOG = true;
    private static final Log log = new Log(LOG);

    /**
     * The current value of the timestamp.  This is volatile so that
     * it can be read from without the need for synchronization.
     */
    private volatile long _timestamp = 0;
    /**
     * Whether or not a wait for the current value of the timestamp
     * has completed.  This is used to ensure that two waits for the
     * same goal cannot both complete.
     */
    private boolean _waited = false;
    /**
     * The lock for manipulating the timestamp.  This object is also
     * notified when there is a change to the timestamp.  We could use
     * 'this', but don't because the notification would be publicly
     * visible.
     */
    private final Object _lock = new Object();

    /**
     * Block until the current timestamp reaches the goal.
     *
     * @throws IllegalTimestampException if the goal is less than the
     * current timestamp at the beginning or if the current timestamp
     * is equal to the goal, but another call to wait for that goal
     * has already completed
     * @throws IllegalStateException if the timestamp surpasses the
     * goal while this is blocking
     */
    public void waitFor(long goal)
        throws IllegalTimestampException, IllegalStateException
    {
        synchronized (_lock) {
            if (_timestamp > goal)
                throw new IllegalTimestampException
                    (goal, "The current timestamp of " + _timestamp +
                     " has already surpassed the goal");

            while (_timestamp < goal) {
                if (LOG)
                    log.println("Waiting for timestamp " + _timestamp +
                                " to reach " + goal);
                try {
                    _lock.wait();
                } catch (InterruptedException e) {
                    // Shouldn't happen
                    throw new Bug
                        ("Interrupted while waiting for timestamp " + goal, e);
                }
            }

            if (_timestamp > goal)
                throw new IllegalStateException
                    ("Timestamp surpassed goal of " + goal +
                     " while blocking; it is now " + _timestamp);

            if (_waited)
                throw new IllegalTimestampException
                    (goal, "This goal has already been waited for");
            _waited = true;
        }
    }

    /**
     * Set the current timestamp and, if there is a thread that is
     * waiting for the timestamp to reach this goal, let it unblock.
     *
     * @throws IllegalTimestampException if the new timestamp is less
     * than or equal to the current timestamp
     * @throws IllegalStateException if this call was not preceded by
     * a call to {@link #waitFor(long)}.
     */
    public void update(long to)
        throws IllegalTimestampException, IllegalStateException
    {
        synchronized (_lock) {
            if (_timestamp >= to)
                throw new IllegalTimestampException
                    (to, "New timestamp is not greater than the " +
                     " current timestamp of " + _timestamp);
            if (!_waited)
                throw new IllegalStateException
                    ("Update to " + to + " was not preceded by a wait");
            if (LOG)
                log.println("Updating timestamp to " + to);
            _timestamp = to;
            _waited = false;
            _lock.notifyAll();
        }
    }

    /**
     * Retrieve the current value of the timestamp.
     */
    public long get()
    {
        return _timestamp;
    }
}
