package com.pardot.rhombus.util;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.util.UUID;

/**
 * Pardot, an ExactTarget company
 * User: Michael Frank
 * Date: 5/6/13
 *
 UUID                   = time-low "-" time-mid "-"
 						  time-high-and-version "-"
 						  clock-seq-and-reserved
 						  clock-seq-low "-" node
 time-low               = 4hexOctet
 time-mid               = 2hexOctet
 time-high-and-version  = 2hexOctet
 clock-seq-and-reserved = hexOctet
 clock-seq-low          = hexOctet
 node                   = 6hexOctet
 hexOctet               = hexDigit hexDigit
 hexDigit =
 "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" /
 "a" / "b" / "c" / "d" / "e" / "f" /
 "A" / "B" / "C" / "D" / "E" / "F"

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          time_low                             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |       time_mid                |         time_hi_and_version   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                         node (2-5)                            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 time-low "-" time-mid "-" time-high-and-version "-" clock-seq-and-reserved + clock-seq-low "-" node

 */
public class UuidUtil {

	/**
	 * Generate a type 3 namespace uuid from an integer namespace and name
     * Our primary objective is to store and retrieve our name and namespace, and we don't care about collisions,
     * so we'll just shove the name and namespace values straight in rather than hashing them
     *
     * We will allow 4 bytes for the namespace and 8 bytes for the name. To avoid conflicts with reserved
     * version/variant bits, we will put the namespace data in the time_low field, 6 least significant bytes of the name data
     * in the node field, and the remaining 2 most significant name bytes in time_mid
     *
	 * @param namespace Integer representing the namespace
	 * @param name Long representing the name
	 * @return Type 3 UUID built from the namespace and name
	 */
	public static UUID namespaceUUID(Integer namespace, Long name) {
		//Create our msb and lsb return buffers
		ByteBuffer msb = ByteBuffer.allocate(8);
        msb.order(ByteOrder.BIG_ENDIAN);
		ByteBuffer lsb = ByteBuffer.allocate(8);
        msb.order(ByteOrder.BIG_ENDIAN);

        // Insert the 4 byte namespace into the time_low field
        msb.putInt(0, namespace);

        // Slice off the most significant two bytes of name
        char nameHigh = (char)(name >>> 48);
        // Push the 2 MSB bytes of name into the time_mid field
        msb.putChar(4, nameHigh);

        //Set the four most significant bits of the time_hi_and_version field to the 4 bit version number
        //(00110000 = 48)
        msb.put(7, (byte) 48);

        // Grab the least significant six bytes of name
        char nameMid = (char)(name >>> 32);
        int nameLow = (int)(long)name;
        // Shove them into the node field
        lsb.putChar(2, nameMid);
        lsb.putInt(4, nameLow);

        //Set the two most significant bits to 01 (01000000 = 64)
        lsb.put(0, (byte)64);

		return new UUID(msb.getLong(), lsb.getLong());
	}

	/**
	 * Retrieve the integer namespace from a namespace uuid generated using this class
	 * @param uuid UUID generated using
	 * @return Namespace retrieved from the UUID
	 */
	public static Integer namespaceFromUUID(UUID uuid) {
        return (int)(uuid.getMostSignificantBits() >>> 32);
	}

	/**
	 * Retrieve the integer name from a namespace uuid generated using this class
	 * @param uuid UUID generated using
	 * @return Name retrieved from the UUID
	 */
	public static Long nameFromUUID(UUID uuid) {
        char msb = (char)((uuid.getMostSignificantBits() >>> 16) & 0xffff);
        long out = uuid.getLeastSignificantBits() & 0xffffffffffffL;
        return out | ((long)msb << 48);
	}

	static final long NUMBER_OF_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L;

	public static DateTime getDateFromUUID(UUID uuid) {
		return new DateTime(convertUUIDToJavaMillis(uuid), DateTimeZone.UTC);
	}

	public static Long convertUUIDToJavaMillis(UUID uuid){
		return (uuid.timestamp() - NUMBER_OF_100NS_INTERVALS_SINCE_UUID_EPOCH) / 10000;
	}


}