/*
 * 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.
 */

/**
 * 
 *  UUIDGen adopted from the juddi project
 *  (http://sourceforge.net/projects/juddi/)
 * 
 */

package org.apache.axis.components.uuid;

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

/**
 * Used to create new universally unique identifiers or UUID's (sometimes called
 * GUID's).  UDDI UUID's are allways formmated according to DCE UUID conventions.
 *
 * @author  Maarten Coene
 * @author  Steve Viens
 * @version 0.3.2 3/25/2001
 * @since   JDK1.2.2
 */
public class SimpleUUIDGen implements UUIDGen {
    private static final BigInteger countStart = new BigInteger("-12219292800000");  // 15 October 1582
    private static final int clock_sequence = (new Random()).nextInt(16384);
    private static final byte ZERO = (byte) 48; // "0"
    private static final byte ONE  = (byte) 49; // "1"
    private static Random secureRandom = null;

    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();
        }
    }

    /**
     * utility method which returns a bitString with left zero padding
     * for as many places as necessary to reach <tt>len</tt>; otherwise
     * returns bitString unaltered.
     *
     * @return a left zero padded string of at least <tt>len</tt> chars
     * @param bitString a String to pad
     * @param len the length under which bitString needs padding
     */
    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;
    }

    /**
     * Creates a new UUID. The algorithm used is described by The Open Group.
     * See <a href="http://www.opengroup.org/onlinepubs/009629399/apdxa.htm">
     * Universal Unique Identifier</a> for more details.
     * <p>
     * Due to a lack of functionality in Java, a part of the UUID is a secure
     * random. This results in a long processing time when this method is called
     * for the first time.
     */
    public String nextUUID() {
        // TODO: this method has to be checked for it's correctness. I'm not sure the standard is
        // implemented correctly.

        // the count of 100-nanosecond intervals since 00:00:00.00 15 October 1582
        BigInteger count;

        // the number of milliseconds since 1 January 1970
        BigInteger current = BigInteger.valueOf(System.currentTimeMillis());

        // the number of milliseconds since 15 October 1582
        BigInteger countMillis = current.subtract(countStart);

        // the result
        count = countMillis.multiply(BigInteger.valueOf(10000));
        byte[] bits = leftZeroPadString(count.toString(2), 60).getBytes();

        // the time_low field
        byte[] time_low = new byte[32];
        for (int i = 0; i < 32; i++)
            time_low[i] = bits[bits.length - i - 1];

        // the time_mid field
        byte[] time_mid = new byte[16];
        for (int i = 0; i < 16; i++)
            time_mid[i] = bits[bits.length - 32 - i - 1];

        // the time_hi_and_version field
        byte[] time_hi_and_version = new byte[16];
        for (int i = 0; i < 12; i++)
            time_hi_and_version[i] = bits[bits.length - 48 - i - 1];

        time_hi_and_version[12] = ONE;
        time_hi_and_version[13] = ZERO;
        time_hi_and_version[14] = ZERO;
        time_hi_and_version[15] = ZERO;

        // the clock_seq_low field
        BigInteger clockSequence = BigInteger.valueOf(clock_sequence);
        byte[] clock_bits = leftZeroPadString(clockSequence.toString(2), 14).getBytes();
        byte[] clock_seq_low = new byte[8];
        for (int i = 0; i < 8; i++) {
            clock_seq_low[i] = clock_bits[clock_bits.length - i - 1];
        }

        // the clock_seq_hi_and_reserved
        byte[] clock_seq_hi_and_reserved = new byte[8];
        for (int i = 0; i < 6; i++)
            clock_seq_hi_and_reserved[i] = clock_bits[clock_bits.length - 8 - i - 1];

        clock_seq_hi_and_reserved[6] = ZERO;
        clock_seq_hi_and_reserved[7] = ONE;

        String timeLow = Long.toHexString((new BigInteger(new String(reverseArray(time_low)), 2)).longValue());
        timeLow = leftZeroPadString(timeLow, 8);

        String timeMid = Long.toHexString((new BigInteger(new String(reverseArray(time_mid)), 2)).longValue());
        timeMid = leftZeroPadString(timeMid, 4);

        String timeHiAndVersion = Long.toHexString((new BigInteger(new String(reverseArray(time_hi_and_version)), 2)).longValue());
        timeHiAndVersion = leftZeroPadString(timeHiAndVersion, 4);

        String clockSeqHiAndReserved = Long.toHexString((new BigInteger(new String(reverseArray(clock_seq_hi_and_reserved)), 2)).longValue());
        clockSeqHiAndReserved = leftZeroPadString(clockSeqHiAndReserved, 2);

        String clockSeqLow = Long.toHexString((new BigInteger(new String(reverseArray(clock_seq_low)), 2)).longValue());
        clockSeqLow = leftZeroPadString(clockSeqLow, 2);

        long nodeValue = secureRandom.nextLong();
        nodeValue = Math.abs(nodeValue);
        while (nodeValue > 140737488355328L) {
            nodeValue = secureRandom.nextLong();
            nodeValue = Math.abs(nodeValue);
        }

        BigInteger nodeInt = BigInteger.valueOf(nodeValue);

        byte[] node_bits = leftZeroPadString(nodeInt.toString(2), 47).getBytes();
        byte[] node = new byte[48];
        for (int i = 0; i < 47; i++)
            node[i] = node_bits[node_bits.length - i - 1];

        node[47] = ONE;
        String theNode = Long.toHexString((new BigInteger(new String(reverseArray(node)), 2)).longValue());
        theNode = leftZeroPadString(theNode, 12);

        StringBuffer result = new StringBuffer(timeLow);
        result.append("-");
        result.append(timeMid);
        result.append("-");
        result.append(timeHiAndVersion);
        result.append("-");
        result.append(clockSeqHiAndReserved);
        result.append(clockSeqLow);
        result.append("-");
        result.append(theNode);
        return result.toString().toUpperCase();
    }

    private static byte[] reverseArray(byte[] bits) {
        byte[] result = new byte[bits.length];
        for (int i = 0; i < result.length; i++)
            result[i] = bits[result.length - 1 - i];

        return result;
    }
}
