/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * 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.apache.axis.components.uuid;

import java.util.Random;
import java.security.SecureRandom;

/**
 * Creates time-based UUID's. See the <a href="http://www.ietf.org/internet-drafts/draft-mealling-uuid-urn-03.txt">UUID Internet Draft</a> for details.
 *
 * @author Jarek Gawor (gawor@apache.org)
 */
public class FastUUIDGen implements UUIDGen {

    private static Random secureRandom;

    private static String nodeStr;
    private static int clockSequence;

    private long lastTime = 0;

    static {
        // problem: the node should be the IEEE 802 ethernet address, but can not
        // be retrieved in Java yet.
        // see bug ID 4173528
        // workaround (also suggested in bug ID 4173528)
        // If a system wants to generate UUIDs but has no IEE 802 compliant
        // network card or other source of IEEE 802 addresses, then this section
        // describes how to generate one.
        // The ideal solution is to obtain a 47 bit cryptographic quality random
        // number, and use it as the low 47 bits of the node ID, with the most
        // significant bit of the first octet of the node ID set to 1. This bit
        // is the unicast/multicast bit, which will never be set in IEEE 802
        // addresses obtained from network cards; hence, there can never be a
        // conflict between UUIDs generated by machines with and without network
        // cards.
        try {
            secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");
        } catch (Exception e) {
            secureRandom = new Random();
        }

        nodeStr = getNodeHexValue();
        clockSequence = getClockSequence();
    }

    private static String getNodeHexValue() {
        long node = 0;
        long nodeValue = 0;
        while ( (node = getBitsValue(nodeValue, 47, 47)) == 0 ) {
            nodeValue = secureRandom.nextLong();
        }
        node = node | 0x800000000000L;
        return leftZeroPadString(Long.toHexString(node), 12);
    }

    private static int getClockSequence() {
        return secureRandom.nextInt(16384);
    }

    public String nextUUID() {
        long time = System.currentTimeMillis();

        long timestamp = time * 10000;
        timestamp += 0x01b21dd2L << 32;
        timestamp += 0x13814000;
        
        synchronized(this) {
            if (time - lastTime <= 0) {
                clockSequence = ((clockSequence + 1) & 16383);
            }
            lastTime = time;
        }

        long timeLow = getBitsValue(timestamp, 32, 32);
        long timeMid = getBitsValue(timestamp, 48, 16);
        long timeHi = getBitsValue(timestamp, 64, 16) | 0x1000;

        long clockSeqLow = getBitsValue(clockSequence, 8, 8);
        long clockSeqHi = getBitsValue(clockSequence, 16, 8) | 0x80;
        
        String timeLowStr = leftZeroPadString(Long.toHexString(timeLow), 8);
        String timeMidStr = leftZeroPadString(Long.toHexString(timeMid), 4);
        String timeHiStr = leftZeroPadString(Long.toHexString(timeHi), 4);

        String clockSeqHiStr = leftZeroPadString(Long.toHexString(clockSeqHi), 2);
        String clockSeqLowStr = leftZeroPadString(Long.toHexString(clockSeqLow), 2);

        StringBuffer result = new StringBuffer(36);
        result.append(timeLowStr).append("-");
        result.append(timeMidStr).append("-");
        result.append(timeHiStr).append("-");
        result.append(clockSeqHiStr).append(clockSeqLowStr);
        result.append("-").append(nodeStr);

        return result.toString();
    }

    private static long getBitsValue(long value, int startBit, int bitLen) {
        return ((value << (64-startBit)) >>> (64-bitLen));
    }

    private static final String leftZeroPadString(String bitString, int len) {
        if (bitString.length() < len) {
            int nbExtraZeros = len - bitString.length();
            StringBuffer extraZeros = new StringBuffer();
            for (int i = 0; i < nbExtraZeros; i++) {
                extraZeros.append("0");
            }
            extraZeros.append(bitString);
            bitString = extraZeros.toString();
        }
        return bitString;
    }

}
