/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.utils;

import java.io.*;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.GregorianCalendar;
import java.util.Random;

public class UniversalUniqueIdentifier implements java.io.Serializable {

	/* INSTANCE FIELDS =============================================== */

	private byte[] fBits = new byte[BYTES_SIZE];

	/* NON-FINAL PRIVATE STATIC FIELDS =============================== */

	private static BigInteger fgPreviousClockValue;
	private static int fgClockAdjustment = 0;
	private static int fgClockSequence = -1;
	private static byte[] nodeAddress;

	static {
		nodeAddress = computeNodeAddress();
	}

	/* PRIVATE STATIC FINAL FIELDS =================================== */

	private static Random fgRandomNumberGenerator = new Random();

	/* PUBLIC STATIC FINAL FIELDS ==================================== */

	public static final int BYTES_SIZE = 16;
	public static final byte[] UNDEFINED_UUID_BYTES = new byte[16];
	public static final int MAX_CLOCK_SEQUENCE = 0x4000;
	public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF;
	public static final int TIME_FIELD_START = 0;
	public static final int TIME_FIELD_STOP = 6;
	public static final int TIME_HIGH_AND_VERSION = 7;
	public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8;
	public static final int CLOCK_SEQUENCE_LOW = 9;
	public static final int NODE_ADDRESS_START = 10;
	public static final int NODE_ADDRESS_STOP = 13;
	public static final int HOST_ADDRESS_BYTE_SIZE = 4;
	public static final int NODE_ADDRESS_BYTE_SIZE = 6;

	public static final int BYTE_MASK = 0xFF;

	public static final int MOST_SIGINIFICANT_BIT_MASK = 0x80;

	public static final int MOST_SIGNIFICANT_TWO_BITS_MASK = 0xC0;

	public static final int MOST_SIGNIFICANT_THREE_BITS_MASK = 0xE0;

	public static final int HIGH_NIBBLE_MASK = 0xF0;

	public static final int LOW_NIBBLE_MASK = 0x0F;

	public static final int SHIFT_NIBBLE = 4;

	public static final int ShiftByte = 8;
	public static final int PrintStringSize = 32;
	public static final int[] PrintStringDashPositions = new int[] {8, 13, 18, 23};
	public static final char[] ValidPrintStringCharacters = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '-'};

	/**
	 UniversalUniqueIdentifier default constructor returns a
	 new instance that has been initialized to a unique value.
	 */
	public UniversalUniqueIdentifier() {
		this.setVersion(1);
		this.setVariant(1);
		this.setTimeValues();
		this.setNode(getNodeAddress());
	}

	/**
	 Constructor that accepts the bytes to use for the instance.&nbsp;&nbsp; The format
	 of the byte array is compatible with the <code>toBytes()</code> method.

	 <p>The constructor returns the undefined uuid if the byte array is invalid.

	 @see #toBytes()
	 @see #BYTES_SIZE
	 */
	public UniversalUniqueIdentifier(byte[] byteValue) {
		fBits = new byte[BYTES_SIZE];
		if (byteValue.length == BYTES_SIZE)
			System.arraycopy(byteValue, 0, fBits, 0, byteValue.length);
	}

	/**
	 Construct an instance whose internal representation is defined by the given string.  
	 The format of the string is that of the <code>toString()</code> instance method.

	 <p>It is useful to compare the <code>fromHex(String)</code> method in the
	 Java class <code>HexConverter</code>.

	 @see       #toString()
	 */
	public UniversalUniqueIdentifier(String string) {
		// Check to ensure it is a String of the right length.
		Assert.isTrue(string.length() == PrintStringSize, Policy.bind("utils.wrongLength", string)); //$NON-NLS-1$

		char[] newChars = string.toCharArray();

		// Convert to uppercase.
		for (int i = 0; i < newChars.length; i++)
			newChars[i] = Character.toUpperCase(newChars[i]);

		// LoadUp a new instance.
		for (int i = 0; i < BYTES_SIZE; i++) {
			int characterOffset = i * 2;
			int hi = Character.digit(newChars[characterOffset], 16);
			int lo = Character.digit(newChars[characterOffset + 1], 16);
			fBits[i] = new Integer((hi * 16 + lo)).byteValue();
		}
	}

	private void appendByteString(StringBuffer buffer, byte value) {
		String hexString;

		if (value < 0)
			hexString = Integer.toHexString(256 + value);
		else
			hexString = Integer.toHexString(value);
		if (hexString.length() == 1)
			buffer.append("0"); //$NON-NLS-1$
		buffer.append(hexString);
	}

	private static BigInteger clockValueNow() {
		GregorianCalendar now = new GregorianCalendar();
		BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime());
		BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime());

		return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L)));
	}

	/**
	 Simply increases the visibility of <code>Object</code>'s clone.
	 Otherwise, no new behaviour.
	 */
	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			Assert.isTrue(false, Policy.bind("utils.clone")); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Answers the node address attempting to mask the IP
	 * address of this machine.
	 * 
	 * @return byte[] the node address
	 */
	private static byte[] computeNodeAddress() {

		byte[] nodeAddress = new byte[NODE_ADDRESS_BYTE_SIZE];

		// Seed the secure randomizer with some oft-varying inputs
		int thread = Thread.currentThread().hashCode();
		long time = System.currentTimeMillis();
		int objectId = System.identityHashCode(new String());
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(byteOut);
		byte[] ipAddress = getIPAddress();

		try {
			if (ipAddress != null)
				out.write(ipAddress);
			out.write(thread);
			out.writeLong(time);
			out.write(objectId);
			out.close();
		} catch (IOException exc) {
			//ignore the failure, we're just trying to come up with a random seed
		}
		byte[] rand = byteOut.toByteArray();

		SecureRandom randomizer = new SecureRandom(rand);
		randomizer.nextBytes(nodeAddress);

		// set the MSB of the first octet to 1 to distinguish from IEEE node addresses
		nodeAddress[0] = (byte) (nodeAddress[0] | (byte) 0x80);

		return nodeAddress;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!(obj instanceof UniversalUniqueIdentifier))
			return false;

		byte[] other = ((UniversalUniqueIdentifier) obj).fBits;
		if (fBits == other)
			return true;
		if (fBits.length != other.length)
			return false;
		for (int i = 0; i < fBits.length; i++) {
			if (fBits[i] != other[i])
				return false;
		}
		return true;
	}

	/**
	 Answers the IP address of the local machine using the
	 Java API class <code>InetAddress</code>.

	 @return byte[] the network address in network order
	 @see    java.net.InetAddress#getLocalHost()
	 @see    java.net.InetAddress#getAddress()
	 */
	protected static byte[] getIPAddress() {
		try {
			return InetAddress.getLocalHost().getAddress();
		} catch (UnknownHostException e) {
			//valid for this to be thrown be a machine with no IP connection
			//It is VERY important NOT to throw this exception
			return null;
		}
	}

	public byte[] getNode() {
		byte[] nodeValue = new byte[HOST_ADDRESS_BYTE_SIZE];

		System.arraycopy(fBits, NODE_ADDRESS_START, nodeValue, 0, HOST_ADDRESS_BYTE_SIZE);
		return nodeValue;
	}

	private static byte[] getNodeAddress() {
		return nodeAddress;
	}

	public int getVariant() {
		byte flags = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED];

		if ((flags & MOST_SIGINIFICANT_BIT_MASK) == 0) {
			// HP/Apollo NCS 1.x and DEC RPC Version 1.
			return (0);
		}

		if ((flags & MOST_SIGNIFICANT_TWO_BITS_MASK) == MOST_SIGINIFICANT_BIT_MASK) {
			// HP/DEC OSF 'DEC UID Architecture Functional Specification Version X1.0.4' from NCS 2.0.
			return (1);
		}

		if ((flags & MOST_SIGNIFICANT_THREE_BITS_MASK) == MOST_SIGNIFICANT_TWO_BITS_MASK) {
			// Microsoft GUID
			return (2);
		}

		if ((flags & MOST_SIGNIFICANT_THREE_BITS_MASK) == MOST_SIGNIFICANT_THREE_BITS_MASK) {
			// reserved
			return (3);
		}

		// unknown
		return (-1);
	}

	public int hashCode() {
		return fBits[0] + fBits[3] + fBits[7] + fBits[11] + fBits[15];
	}

	/**
	 * Tests to see if the receiver is anonymous.
	 * <p>
	 * If the receiver was constrcted from a string or bytes it
	 * may not really be anonymous.<p>
	 *
	 * @return boolean true if the receiver is anonymous
	 */
	public boolean isAnonymous() {
		return isUndefined() || ((getNode()[0] & MOST_SIGINIFICANT_BIT_MASK) == MOST_SIGINIFICANT_BIT_MASK);
	}

	public boolean isUndefined() {
		return this.equals(UniversalUniqueIdentifier.newUndefined());
	}

	/**
	 Returns a new instance of <code>UniversalUniqueIdentifier</code>
	 that represents the single undefined state.

	 <p>The undefined <code>UniversalUniqueIdentifier</code> is used
	 to represent cases where a unique identifier is not required.

	 @return the undefined instance of <code>UniversalUniqueIdentifier</code>
	 */
	public static UniversalUniqueIdentifier newUndefined() {
		return new UniversalUniqueIdentifier(UNDEFINED_UUID_BYTES);
	}

	private static int nextClockSequence() {

		if (fgClockSequence == -1)
			fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE);

		fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE;

		return fgClockSequence;
	}

	private static BigInteger nextTimestamp() {

		BigInteger timestamp = clockValueNow();
		int timestampComparison;

		timestampComparison = timestamp.compareTo(fgPreviousClockValue);

		if (timestampComparison == 0) {
			if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) {
				while (timestamp.compareTo(fgPreviousClockValue) == 0)
					timestamp = clockValueNow();
				timestamp = nextTimestamp();
			} else
				fgClockAdjustment++;
		} else {
			fgClockAdjustment = 0;

			if (timestampComparison < 0)
				nextClockSequence();
		}

		return timestamp;
	}

	/** 
	 Implements the corresponding method in the interface <code>IPrintable</code>.

	 @see IPrintable#print(java.io.DataOutputStream)
	 */
	public void print(DataOutputStream outStream) {
		try {
			outStream.writeBytes(toString());
		} catch (IOException e) {
			Assert.isTrue(false, Policy.bind("utils.print")); //$NON-NLS-1$
		}
	}

	private void setClockSequence(int clockSeq) {
		int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK;
		int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK;

		fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh);
		fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK);
	}

	protected void setNode(byte[] bytes) {

		for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++)
			fBits[index + NODE_ADDRESS_START] = bytes[index];
	}

	private void setTimestamp(BigInteger timestamp) {
		BigInteger value = timestamp;
		BigInteger bigByte = BigInteger.valueOf(256L);
		BigInteger[] results;
		int version;
		int timeHigh;

		for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) {
			results = value.divideAndRemainder(bigByte);
			value = results[0];
			fBits[index] = (byte) results[1].intValue();
		}
		version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK;
		timeHigh = value.intValue() & LOW_NIBBLE_MASK;
		fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version);
	}

	protected synchronized void setTimeValues() {
		this.setTimestamp(timestamp());
		this.setClockSequence(fgClockSequence);
	}

	protected int setVariant(int variantIdentifier) {
		int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK;
		int variant = variantIdentifier & LOW_NIBBLE_MASK;

		fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh);
		return (variant);
	}

	protected void setVersion(int versionIdentifier) {
		int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK;
		int version = versionIdentifier & LOW_NIBBLE_MASK;

		fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE));
	}

	private static BigInteger timestamp() {
		BigInteger timestamp;

		if (fgPreviousClockValue == null) {
			fgClockAdjustment = 0;
			nextClockSequence();
			timestamp = clockValueNow();
		} else
			timestamp = nextTimestamp();

		fgPreviousClockValue = timestamp;
		return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment));
	}

	/** 
	 This representation is compatible with the (byte[]) constructor.

	 @see #UniversalUniqueIdentifier(byte[])
	 */
	public byte[] toBytes() {
		byte[] result = new byte[fBits.length];

		System.arraycopy(fBits, 0, result, 0, fBits.length);
		return result;
	}

	public String toString() {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < fBits.length; i++)
			appendByteString(buffer, fBits[i]);
		return buffer.toString();
	}

	public String toStringAsBytes() {
		String result = "{"; //$NON-NLS-1$

		for (int i = 0; i < fBits.length; i++) {
			result += fBits[i];
			if (i < fBits.length + 1)
				result += ","; //$NON-NLS-1$
		}
		return result + "}"; //$NON-NLS-1$
	}
}