// UUID.h

// Defines the cUUID class representing a Universally Unique Identifier

#include "Globals.h"
#include "UUID.h"

#include "mbedtls/md5.h"


/** UUID normalised in textual form. */
struct sShortUUID
{
	char Data[32]{};
	bool IsValid = false;
};

/** Returns the given UUID in shortened form with IsValid indicating success.
Doesn't check digits are hexadecimal but does check dashes if long form. */
static sShortUUID ShortenUUID(const AString & a_StringUUID)
{
	sShortUUID UUID;
	switch (a_StringUUID.size())
	{
		case 32:
		{
			// Already a short UUID
			std::memcpy(UUID.Data, a_StringUUID.data(), 32);
			UUID.IsValid = true;
			break;
		}
		case 36:
		{
			// Long UUID, confirm dashed
			if (
				(a_StringUUID[ 8] != '-') ||
				(a_StringUUID[13] != '-') ||
				(a_StringUUID[18] != '-') ||
				(a_StringUUID[23] != '-')
			)
			{
				break;
			}

			// Copy everying but the dashes from the string
			std::memcpy(UUID.Data,      a_StringUUID.data(),       8);
			std::memcpy(UUID.Data +  8, a_StringUUID.data() +  9,  4);
			std::memcpy(UUID.Data + 12, a_StringUUID.data() + 14,  4);
			std::memcpy(UUID.Data + 16, a_StringUUID.data() + 19,  4);
			std::memcpy(UUID.Data + 20, a_StringUUID.data() + 24, 12);
			UUID.IsValid = true;
		}
		default: break;
	}
	return UUID;
}





/** Returns the integer value of the hex digit or 0xff if invalid. */
static Byte FromHexDigit(char a_Hex)
{
	if (('0' <= a_Hex) && (a_Hex <= '9'))
	{
		return static_cast<Byte>(a_Hex - '0');
	}
	if (('a' <= a_Hex) && (a_Hex <= 'f'))
	{
		return static_cast<Byte>(10 + (a_Hex - 'a'));
	}
	if (('A' <= a_Hex) && (a_Hex <= 'F'))
	{
		return static_cast<Byte>(10 + (a_Hex - 'A'));
	}
	return 0xff;
}





/** From a number in the range [0, 16), returns the corresponding hex digit in lowercase. */
static char ToHexDigit(UInt8 a_Nibble)
{
	ASSERT((a_Nibble & 0xf0) == 0);
	return static_cast<char>(
		(a_Nibble < 10) ?
		('0' + a_Nibble) :
		('a' + (a_Nibble - 10))
	);
}





////////////////////////////////////////////////////////////////////////////////
// cUUID:

bool cUUID::FromString(const AString & a_StringUUID)
{
	sShortUUID Norm = ShortenUUID(a_StringUUID);
	if (!Norm.IsValid)
	{
		return false;
	}

	std::array<Byte, 16> ParsedUUID{{0}};
	for (size_t i = 0; i != m_UUID.size(); ++i)
	{
		Byte HighNibble = FromHexDigit(Norm.Data[2 * i    ]);
		Byte LowNibble  = FromHexDigit(Norm.Data[2 * i + 1]);
		if ((HighNibble > 0x0f) || (LowNibble > 0x0f))
		{
			// Invalid hex digit
			return false;
		}

		ParsedUUID[i] = static_cast<Byte>((HighNibble << 4) | LowNibble);
	}

	// Parsed successfully
	m_UUID = ParsedUUID;
	return true;
}





AString cUUID::ToShortString() const
{
	AString ShortString(32, '\0');
	for (size_t i = 0; i != m_UUID.size(); ++i)
	{
		Byte HighNibble = (m_UUID[i] >> 4) & 0x0f;
		Byte LowNibble  = m_UUID[i] & 0x0f;

		ShortString[2 * i    ] = ToHexDigit(HighNibble);
		ShortString[2 * i + 1] = ToHexDigit(LowNibble);
	}
	return ShortString;
}





AString cUUID::ToLongString() const
{
	AString LongString = ToShortString();
	LongString.reserve(36);

	// Convert to long form by inserting the dashes
	auto First = LongString.begin();
	LongString.insert(First +  8, '-');
	LongString.insert(First + 13, '-');
	LongString.insert(First + 18, '-');
	LongString.insert(First + 23, '-');

	return LongString;
}





UInt8 cUUID::Version() const
{
	return static_cast<UInt8>((m_UUID[6] >> 4) & 0x0f);
}





UInt8 cUUID::Variant() const
{
	const Byte VariantBits = static_cast<Byte>((m_UUID[8] >> 5) & 0x07);

	/* Variant bits format:
		bits | variant |      Description
		-----|---------|----------------------
		0xx  |    0    |        Obsolete
		10x  |    1    |     Standard UUID
		110  |    2    | Microsoft Legacy GUID
		111  |    3    |        Reserved
	*/

	if ((VariantBits & 0x04) == 0)
	{
		return 0;
	}
	else if ((VariantBits & 0x02) == 0)
	{
		return 1;
	}
	else if ((VariantBits & 0x01) == 0)
	{
		return 2;
	}
	else
	{
		return 3;
	}
}





std::array<Byte, 16> cUUID::ToRaw() const
{
	std::array<Byte, 16> Raw(m_UUID);
	if (Variant() == 2)
	{
		// Convert to microsoft mixed-endian format
		// First 3 components are host-endian, last 2 are network
		auto First = reinterpret_cast<UInt32 *>(Raw.data());
		*First = ntohl(*First);

		auto Second = reinterpret_cast<UInt16 *>(&Raw[4]);
		*Second = ntohs(*Second);

		auto Third = Second + 1;
		*Third = ntohs(*Third);
	}

	return Raw;
}





void cUUID::FromRaw(const std::array<Byte, 16> & a_Raw)
{
	m_UUID = a_Raw;
	if (Variant() != 2)
	{
		// Standard big-endian formats
		return;
	}

	// Convert from microsoft mixed-endian format
	// First 3 components are host-endian, last 2 are network
	auto First = reinterpret_cast<UInt32 *>(m_UUID.data());
	*First = htonl(*First);

	auto Second = reinterpret_cast<UInt16 *>(&m_UUID[4]);
	*Second = htons(*Second);

	auto Third = Second + 1;
	*Third = htons(*Third);
}





cUUID cUUID::GenerateVersion3(const AString & a_Name)
{
	cUUID UUID;
	// Generate an md5 checksum, and use it as base for the ID:
	const Byte * ByteString = reinterpret_cast<const Byte *>(a_Name.data());
	mbedtls_md5(ByteString, a_Name.length(), UUID.m_UUID.data());

	// Insert version number
	UUID.m_UUID[6] = (UUID.m_UUID[6] & 0x0f) | 0x30;

	// Insert variant number
	UUID.m_UUID[8] = (UUID.m_UUID[8] & 0x3f) | 0x80;

	return UUID;
}




