// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/EncodingProvider.java
// public interface EncodingProvider {
// 
//     /**
//      * Return a raw byte encoding of the given time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public byte[] encodeAsBytes(long time, int sequence);
// 
//     /**
//      * Return a Long encoding of the given the time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public long encodeAsLong(long time, int sequence);
// 
//     /**
//      * Return a String encoding of the given the time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public String encodeAsString(long time, int sequence);
// 
//     /**
//      * Return the maximum number of sequence numbers that can be encoded for a
//      * given time by this implementation.
//      */
//     public int maxSequenceNumbers();
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/Id.java
// public interface Id {
// 
//     /**
//      * Return a unique identifier as a long.
//      */
//     public long asLong();
// 
//     /**
//      * Return a unique identifier as bytes.
//      */
//     public byte[] asBytes();
// 
//     /**
//      * Return a unique identifier as a String.
//      */
//     public String asString();
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/IdGenerator.java
// public interface IdGenerator {
// 
//     /**
//      * Return a unique identifier. This should be resilient enough to handle
//      * leap seconds, however, backwards time that occurs when the system time is
//      * updated, as from NTP, will result in an {@link InterruptedException}.
//      *
//      * NOTE: Generating more id's at a time than can be handled may result in
//      * the current thread blocking for maxWait ms to ensure a new timestamp is
//      * available.
//      *
//      * @param maxWait
//      *          the maximum amount of time to wait in ms before giving up
//      * @throws InterruptedException
//      *          thrown when thread is interrupted, the maximum amount of time
//      *          to wait for a new timestamp to become available has expired, or
//      *          backwards time has been detected
//      */
//     public Id generateId(int maxWait) throws InterruptedException;
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/TimeProvider.java
// public interface TimeProvider {
// 
//     /**
//      * Return the number of milliseconds from the Unix epoch.
//      */
//     public long getCurrentTime();
// }


import com.github.rholder.fauxflake.api.EncodingProvider;
import com.github.rholder.fauxflake.api.Id;
import com.github.rholder.fauxflake.api.IdGenerator;
import com.github.rholder.fauxflake.api.TimeProvider;
            // backwards time, likely due to NTP updates for clock drift
            if(currentTime < lastTime) {
                long diff = lastTime - currentTime;
                throw new BackwardsTimeException("Backwards time detected, try again in " + diff + " ms", diff);
            }

            if(sequence == encodingProvider.maxSequenceNumbers()) {
                // out of sequence numbers for this clock tick, be evil and hold the lock until the time changes
                int currentWait = 0;
                while(currentTime <= lastTime) {
                    if(currentWait > maxWait) {
                        throw new WaitTimeExceededException("The maximum time to wait to generate an id has been exceeded") ;
                    }
                    Thread.sleep(1);
                    currentTime = timeProvider.getCurrentTime();
                    currentWait++;
                }
            }

            if(currentTime != lastTime) {
                lastTime = currentTime;
                sequence = 0;
            }
            currentSequence = sequence;
            sequence++;
        }
        return new Values(currentTime, (currentSequence + startingSequenceNumber) % encodingProvider.maxSequenceNumbers());
    }

    @Override
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/EncodingProvider.java
// public interface EncodingProvider {
// 
//     /**
//      * Return a raw byte encoding of the given time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public byte[] encodeAsBytes(long time, int sequence);
// 
//     /**
//      * Return a Long encoding of the given the time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public long encodeAsLong(long time, int sequence);
// 
//     /**
//      * Return a String encoding of the given the time and sequence number.
//      *
//      * @param time      a time value to encode
//      * @param sequence  a sequence value to encode
//      */
//     public String encodeAsString(long time, int sequence);
// 
//     /**
//      * Return the maximum number of sequence numbers that can be encoded for a
//      * given time by this implementation.
//      */
//     public int maxSequenceNumbers();
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/Id.java
// public interface Id {
// 
//     /**
//      * Return a unique identifier as a long.
//      */
//     public long asLong();
// 
//     /**
//      * Return a unique identifier as bytes.
//      */
//     public byte[] asBytes();
// 
//     /**
//      * Return a unique identifier as a String.
//      */
//     public String asString();
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/IdGenerator.java
// public interface IdGenerator {
// 
//     /**
//      * Return a unique identifier. This should be resilient enough to handle
//      * leap seconds, however, backwards time that occurs when the system time is
//      * updated, as from NTP, will result in an {@link InterruptedException}.
//      *
//      * NOTE: Generating more id's at a time than can be handled may result in
//      * the current thread blocking for maxWait ms to ensure a new timestamp is
//      * available.
//      *
//      * @param maxWait
//      *          the maximum amount of time to wait in ms before giving up
//      * @throws InterruptedException
//      *          thrown when thread is interrupted, the maximum amount of time
//      *          to wait for a new timestamp to become available has expired, or
//      *          backwards time has been detected
//      */
//     public Id generateId(int maxWait) throws InterruptedException;
// }
// 
// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/api/TimeProvider.java
// public interface TimeProvider {
// 
//     /**
//      * Return the number of milliseconds from the Unix epoch.
//      */
//     public long getCurrentTime();
// }


// Path: fauxflake-core/src/main/java/com/github/rholder/fauxflake/DefaultIdGenerator.java
import com.github.rholder.fauxflake.api.EncodingProvider;
import com.github.rholder.fauxflake.api.Id;
import com.github.rholder.fauxflake.api.IdGenerator;
import com.github.rholder.fauxflake.api.TimeProvider;

            // backwards time, likely due to NTP updates for clock drift
            if(currentTime < lastTime) {
                long diff = lastTime - currentTime;
                throw new BackwardsTimeException("Backwards time detected, try again in " + diff + " ms", diff);
            }

            if(sequence == encodingProvider.maxSequenceNumbers()) {
                // out of sequence numbers for this clock tick, be evil and hold the lock until the time changes
                int currentWait = 0;
                while(currentTime <= lastTime) {
                    if(currentWait > maxWait) {
                        throw new WaitTimeExceededException("The maximum time to wait to generate an id has been exceeded") ;
                    }
                    Thread.sleep(1);
                    currentTime = timeProvider.getCurrentTime();
                    currentWait++;
                }
            }

            if(currentTime != lastTime) {
                lastTime = currentTime;
                sequence = 0;
            }
            currentSequence = sequence;
            sequence++;
        }
        return new Values(currentTime, (currentSequence + startingSequenceNumber) % encodingProvider.maxSequenceNumbers());
    }

    @Override
    public Id generateId(int maxWait) throws InterruptedException {