/* JUG Java Uuid Generator
 *
 * Copyright (c) 2002 Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in the file LICENSE which is
 * included with the source code.
 * You may not use this file except in compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.safehaus.uuid;

import java.io.*;
import java.util.*;

/**
 * UUIDTimer produces the time stamps required for time-based UUIDs.
 * It works as outlined in the UUID specification, with following
 * implementation:
 *<ul>
 *<li>Java classes can only product time stamps with maximum resolution
 *   of one millisecond (at least before JDK 1.5).
 *   To compensate, an additional counter is used,
 *   so that more than one UUID can be generated between java clock
 *   updates. Counter may be used to generate up to 10000 UUIDs for
 *   each distrinct java clock value.
 *<li>Due to even lower clock resolution on some platforms (older
 *  Windows versions use 55 msec resolution), timestamp value can
 *  also advanced ahead of physical value within limits (by default,
 *  up 100 millisecond ahead of reported), iff necessary (ie. 10000
 *  instances created before clock time advances).
 *<li>As an additional precaution, counter is initialized not to 0
 *   but to a random 8-bit number, and each time clock changes, lowest
 *   8-bits of counter are preserved. The purpose it to make likelyhood
 *   of multi-JVM multi-instance generators to collide, without significantly
 *   reducing max. UUID generation speed. Note though that using more than
 *   one generator (from separate JVMs) is strongly discouraged, so
 *   hopefully this enhancement isn't needed.
 *   This 8-bit offset has to be reduced from total max. UUID count to
 *   preserve ordering property of UUIDs (ie. one can see which UUID
 *   was generated first for given UUID generator); the resulting
 *   9500 UUIDs isn't much different from the optimal choice.
 *<li>Finally, as of version 2.0 and onwards, optional external timestamp
 *   synchronization can be done. This is done similar to the way UUID
 *   specification suggests; except that since there is no way to 
 *   lock the whole system, file-based locking is used. This works
 *   between multiple JVMs and Jug instances.
 *</ul>
 *<p>
 *Some additional assumptions about calculating the timestamp:
 *<ul>
 *<li>System.currentTimeMillis() is assumed to give time offset in UTC,
 *   or at least close enough thing to get correct timestamps. The
 *   alternate route would have to go through calendar object, use
 *   TimeZone offset to get to UTC, and then modify. Using currentTimeMillis
 *   should be much faster to allow rapid UUID creation.
 *<li>Similarly, the constant used for time offset between 1.1.1970 and
 *  start of Gregorian calendar is assumed to be correct (which seems
 *  to be the case when testing with Java calendars).
 *</ul>
 *<p>
 * Note about synchronization: this class is assumed to always be called
 * from a synchronized context (caller locks on either this object, or
 * a similar timer lock), and so has no method synchronization.
 */
public class UUIDTimer
{
    // // // Constants

    /**
     * Since System.longTimeMillis() returns time from january 1st 1970,
     * and UUIDs need time from the beginning of gregorian calendar
     * (15-oct-1582), need to apply the offset:
     */
    private final static long kClockOffset = 0x01b21dd213814000L;
    /**
     * Also, instead of getting time in units of 100nsecs, we get something
     * with max resolution of 1 msec... and need the multiplier as well
     */
    private final static long kClockMultiplier = 10000;
    private final static long kClockMultiplierL = 10000L;

    /**
     * Let's allow "virtual" system time to advance at most 100 milliseconds
     * beyond actual physical system time, before adding delays.
     */
    private final static long kMaxClockAdvance = 100L;

    // // // Configuration

    private final Random mRnd;

    // // // Clock state:

    /**
     * Additional state information used to protect against anomalous
     * cases (clock time going backwards, node id getting mixed up).
     * Third byte is actually used for seeding counter on counter
     * overflow.
     */
    private final byte[] mClockSequence = new byte[3];

    /**
     * Last physical timestamp value <code>System.currentTimeMillis()</code>
     * returned: used to catch (and report) cases where system clock
     * goes backwards. Is also used to limit "drifting", that is, amount
     * timestamps used can differ from the system time value. This value
     * is not guaranteed to be monotonically increasing.
     */
    private long mLastSystemTimestamp = 0L;
    
    /**
     * Timestamp value last used for generating a UUID (along with
     * {@link #mClockCounter}. Usually the same as
     * {@link #mLastSystemTimestamp}, but not always (system clock
     * moved backwards). Note that this value is guaranteed to be
     * monotonically increasing; that is, at given absolute time points
     * t1 and t2 (where t2 is after t1), t1 <= t2 will always hold true.
     */
    private long mLastUsedTimestamp = 0L;

    /**
     * First timestamp that can NOT be used without synchronizing
     * using synchronization object ({@link #mSync}). Only used when
     * external timestamp synchronization (and persistence) is used,
     * ie. when {@link #mSync} is not null.
     */
    private long mFirstUnsafeTimestamp = Long.MAX_VALUE;

    /**
     * Counter used to compensate inadequate resolution of JDK system
     * timer.
     */
    private int mClockCounter = 0;

    /**
     * Object used to reliably ensure that no multiple JVMs
     * generate UUIDs, and also that the time stamp value used for
     * generating time-based UUIDs is monotonically increasing
     * even if system clock moves backwards over a reboot (usually
     * due to some system level problem).
     *<p>
     * See {@link TimestampSynchronizer} for details.
     */
    private TimestampSynchronizer mSync = null;

    UUIDTimer(Random rnd)
    {
        mRnd = rnd;
        initCounters(rnd);
        mLastSystemTimestamp = 0L;
        // This may get overwritten by the synchronizer
        mLastUsedTimestamp = 0L;
    }

    private void initCounters(Random rnd)
    {
        /* Let's generate the clock sequence field now; as with counter,
         * this reduces likelihood of collisions (as explained in UUID specs)
         */
        rnd.nextBytes(mClockSequence);
        /* Ok, let's also initialize the counter...
         * Counter is used to make it slightly less likely that
         * two instances of UUIDGenerator (from separate JVMs as no more
         * than one can be created in one JVM) would produce colliding
         * time-based UUIDs. The practice of using multiple generators,
         * is strongly discouraged, of course, but just in case...
         */
        mClockCounter = mClockSequence[2] & 0xFF;
    }

    public void getTimestamp(byte[] uuidData)
    {
        // First the clock sequence:
        uuidData[UUID.INDEX_CLOCK_SEQUENCE] = mClockSequence[0];
        uuidData[UUID.INDEX_CLOCK_SEQUENCE+1] = mClockSequence[1];

        long systime = System.currentTimeMillis();

        /* Let's first verify that the system time is not going backwards;
         * independent of whether we can use it:
         */
        if (systime < mLastSystemTimestamp) {
            Logger.logWarning("System time going backwards! (got value "+systime+", last "+mLastSystemTimestamp);
            // Let's write it down, still
            mLastSystemTimestamp = systime;
        }

        /* But even without it going backwards, it may be less than the
         * last one used (when generating UUIDs fast with coarse clock
         * resolution; or if clock has gone backwards over reboot etc).
         */
        if (systime <= mLastUsedTimestamp) {
            /* Can we just use the last time stamp (ok if the counter
             * hasn't hit max yet)
             */
            if (mClockCounter < kClockMultiplier) { // yup, still have room
                systime = mLastUsedTimestamp;
            } else { // nope, have to roll over to next value and maybe wait
                long actDiff = mLastUsedTimestamp - systime;
                long origTime = systime;
                systime = mLastUsedTimestamp + 1L;

                Logger.logWarning("Timestamp over-run: need to reinitialize random sequence");

                /* Clock counter is now at exactly the multiplier; no use
                 * just anding its value. So, we better get some random
                 * numbers instead...
                 */
                initCounters(mRnd);

                /* But do we also need to slow down? (to try to keep virtual
                 * time close to physical time; ie. either catch up when
                 * system clock has been moved backwards, or when coarse
                 * clock resolution has forced us to advance virtual timer
                 * too far)
                 */
                if (actDiff >= kMaxClockAdvance) {
                    slowDown(origTime, actDiff);
                }
            }
        } else {
            /* Clock has advanced normally; just need to make sure counter is
             * reset to a low value (need not be 0; good to leave a small
             * residual to further decrease collisions)
             */
            mClockCounter &= 0xFF;
        }

        mLastUsedTimestamp = systime;

        /* Ok, we have consistent clock (virtual or physical) value that
         * we can and should use.
         * But do we need to check external syncing now?
         */
        if (mSync != null && systime >= mFirstUnsafeTimestamp) {
            try {
                mFirstUnsafeTimestamp = mSync.update(systime);
            } catch (IOException ioe) {
                throw new RuntimeException("Failed to synchronize timestamp: "+ioe);
            }
        }

        /* Now, let's translate the timestamp to one UUID needs, 100ns
         * unit offset from the beginning of Gregorian calendar...
         */
        systime *= kClockMultiplierL;
        systime += kClockOffset;

        // Plus add the clock counter:
        systime += mClockCounter;
        // and then increase
        ++mClockCounter;

        /* Time fields are nicely split across the UUID, so can't just
         * linearly dump the stamp:
         */
        int clockHi = (int) (systime >>> 32);
        int clockLo = (int) systime;

        uuidData[UUID.INDEX_CLOCK_HI] = (byte) (clockHi >>> 24);
        uuidData[UUID.INDEX_CLOCK_HI+1] = (byte) (clockHi >>> 16);
        uuidData[UUID.INDEX_CLOCK_MID] = (byte) (clockHi >>> 8);
        uuidData[UUID.INDEX_CLOCK_MID+1] = (byte) clockHi;

        uuidData[UUID.INDEX_CLOCK_LO] = (byte) (clockLo >>> 24);
        uuidData[UUID.INDEX_CLOCK_LO+1] = (byte) (clockLo >>> 16);
        uuidData[UUID.INDEX_CLOCK_LO+2] = (byte) (clockLo >>> 8);
        uuidData[UUID.INDEX_CLOCK_LO+3] = (byte) clockLo;
    }

    public void setSynchronizer(TimestampSynchronizer sync)
        throws IOException
    {
        TimestampSynchronizer old = mSync;

        if (old != null) {
            try {
                old.deactivate();
            } catch (IOException ioe) {
                Logger.logError("Failed to deactivate the old synchronizer: "+ioe);
            }
        }

        mSync = sync;

        /* Ok, now; synchronizer can tell us what is the first timestamp
         * value that definitely was NOT used by the previous incarnation.
         * This can serve as the last used time stamp, assuming it is not
         * less than value we are using now.
         */
        if (sync != null) {
            long lastSaved = sync.initialize();
            if (lastSaved > mLastUsedTimestamp) {
                mLastUsedTimestamp = lastSaved;
            }
        }

        /* Also, we need to make sure there are now no safe values (since
         * synchronizer is not yet requested to allocate any):
         */
        mFirstUnsafeTimestamp = 0L; // ie. will always trigger sync.update()
    }

    /*
    ///////////////////////////////////////////////////////////
    // Private methods
    ///////////////////////////////////////////////////////////
     */

    private final static int MAX_WAIT_COUNT = 50;

    /**
     * Simple utility method to use to wait for couple of milliseconds,
     * to let system clock hopefully advance closer to the virtual
     * timestamps used. Delay is kept to just a millisecond or two,
     * to prevent excessive blocking; but that should be enough to
     * eventually synchronize physical clock with virtual clock values
     * used for UUIDs.
     *
     * @param msecs Number of milliseconds to wait for from current 
     *    time point
     */
    private final static void slowDown(long startTime, long actDiff)
    {
        /* First, let's determine how long we'd like to wait.
         * This is based on how far ahead are we as of now.
         */
        long ratio = actDiff / kMaxClockAdvance;
        long delay;
        
        if (ratio < 2L) { // 200 msecs or less
            delay = 1L;
        } else if (ratio < 10L) { // 1 second or less
            delay = 2L;
        } else if (ratio < 600L) { // 1 minute or less
            delay = 3L;
        } else {
            delay = 5L;
        }
        Logger.logWarning("Need to wait for "+delay+" milliseconds; virtual clock advanced too far in the future");
        long waitUntil = startTime + delay;
        int counter = 0;
        do {
            try {
                Thread.sleep(delay);
            } catch (InterruptedException ie) { }
            delay = 1L;
            /* This is just a sanity check: don't want an "infinite"
             * loop if clock happened to be moved backwards by, say,
             * an hour...
             */
            if (++counter > MAX_WAIT_COUNT) {
                break;
            }
        } while (System.currentTimeMillis() < waitUntil);
    }
}
