/*
 * mptUUID.cpp
 * -----------
 * Purpose: UUID utility functions.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "mptUUID.h"

#include "mptRandom.h"
#include "mptStringFormat.h"
#include "Endianness.h"

#include <cstdlib>

#if MPT_OS_WINDOWS
#include <windows.h>
#include <rpc.h>
#if defined(MODPLUG_TRACKER) || !defined(NO_DMO) || MPT_OS_WINDOWS_WINRT
#include <objbase.h>
#endif // MODPLUG_TRACKER || !NO_DMO || MPT_OS_WINDOWS_WINRT
#endif // MPT_OS_WINDOWS


OPENMPT_NAMESPACE_BEGIN


#if MPT_OS_WINDOWS


namespace Util
{


#if defined(MODPLUG_TRACKER) || !defined(NO_DMO)


mpt::winstring CLSIDToString(CLSID clsid)
{
	std::wstring str;
	LPOLESTR tmp = nullptr;
	switch(::StringFromCLSID(clsid, &tmp))
	{
	case S_OK:
		break;
	case E_OUTOFMEMORY:
		if(tmp)
		{
			::CoTaskMemFree(tmp);
			tmp = nullptr;
		}
		MPT_EXCEPTION_THROW_OUT_OF_MEMORY();
		break;
	default:
		if(tmp)
		{
			::CoTaskMemFree(tmp);
			tmp = nullptr;
		}
		throw std::logic_error("StringFromCLSID() failed.");
		break;
	}
	if(!tmp)
	{
		throw std::logic_error("StringFromCLSID() failed.");
	}
	try
	{
		str = tmp;
	} MPT_EXCEPTION_CATCH_OUT_OF_MEMORY(e)
	{
		::CoTaskMemFree(tmp);
		tmp = nullptr;
		MPT_EXCEPTION_RETHROW_OUT_OF_MEMORY(e);
	}
	::CoTaskMemFree(tmp);
	tmp = nullptr;
	return mpt::ToWin(str);
}


CLSID StringToCLSID(const mpt::winstring &str_)
{
	const std::wstring str = mpt::ToWide(str_);
	CLSID clsid = CLSID();
	std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
	switch(::CLSIDFromString(tmp.data(), &clsid))
	{
	case NOERROR:
		// nothing
		break;
	case E_INVALIDARG:
		clsid = CLSID();
		break;
	case CO_E_CLASSSTRING:
		clsid = CLSID();
		break;
	case REGDB_E_CLASSNOTREG:
		clsid = CLSID();
		break;
	case REGDB_E_READREGDB:
		clsid = CLSID();
		throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
		break;
	default:
		clsid = CLSID();
		throw std::logic_error("CLSIDFromString() failed.");
		break;
	}
	return clsid;
}


bool VerifyStringToCLSID(const mpt::winstring &str_, CLSID &clsid)
{
	const std::wstring str = mpt::ToWide(str_);
	bool result = false;
	clsid = CLSID();
	std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
	switch(::CLSIDFromString(tmp.data(), &clsid))
	{
	case NOERROR:
		result = true;
		break;
	case E_INVALIDARG:
		result = false;
		break;
	case CO_E_CLASSSTRING:
		result = false;
		break;
	case REGDB_E_CLASSNOTREG:
		result = false;
		break;
	case REGDB_E_READREGDB:
		throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
		break;
	default:
		throw std::logic_error("CLSIDFromString() failed.");
		break;
	}
	return result;
}


bool IsCLSID(const mpt::winstring &str_)
{
	const std::wstring str = mpt::ToWide(str_);
	bool result = false;
	CLSID clsid = CLSID();
	std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
	switch(::CLSIDFromString(tmp.data(), &clsid))
	{
	case NOERROR:
		result = true;
		break;
	case E_INVALIDARG:
		result = false;
		break;
	case CO_E_CLASSSTRING:
		result = false;
		break;
	case REGDB_E_CLASSNOTREG:
		result = false;
		break;
	case REGDB_E_READREGDB:
		result = false;
		throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
		break;
	default:
		result = false;
		throw std::logic_error("CLSIDFromString() failed.");
		break;
	}
	return result;
}


mpt::winstring IIDToString(IID iid)
{
	std::wstring str;
	LPOLESTR tmp = nullptr;
	switch(::StringFromIID(iid, &tmp))
	{
	case S_OK:
		break;
	case E_OUTOFMEMORY:
		if(tmp)
		{
			::CoTaskMemFree(tmp);
			tmp = nullptr;
		}
		MPT_EXCEPTION_THROW_OUT_OF_MEMORY();
		break;
	default:
		if(tmp)
		{
			::CoTaskMemFree(tmp);
			tmp = nullptr;
		}
		throw std::logic_error("StringFromIID() failed.");
		break;
	}
	if(!tmp)
	{
		throw std::logic_error("StringFromIID() failed.");
	}
	try
	{
		str = tmp;
	} MPT_EXCEPTION_CATCH_OUT_OF_MEMORY(e)
	{
		::CoTaskMemFree(tmp);
		tmp = nullptr;
		MPT_EXCEPTION_RETHROW_OUT_OF_MEMORY(e);
	}
	return mpt::ToWin(str);
}


IID StringToIID(const mpt::winstring &str_)
{
	const std::wstring str = mpt::ToWide(str_);
	IID iid = IID();
	std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
	switch(::IIDFromString(tmp.data(), &iid))
	{
	case S_OK:
		// nothing
		break;
	case E_OUTOFMEMORY:
		iid = IID();
		MPT_EXCEPTION_THROW_OUT_OF_MEMORY();
		break;
	case E_INVALIDARG:
		iid = IID();
		break;
	default:
		iid = IID();
		throw std::logic_error("IIDFromString() failed.");
		break;
	}
	return iid;
}


mpt::winstring GUIDToString(GUID guid)
{
	std::vector<OLECHAR> tmp(256);
	if(::StringFromGUID2(guid, tmp.data(), static_cast<int>(tmp.size())) <= 0)
	{
		throw std::logic_error("StringFromGUID2() failed.");
	}
	return mpt::ToWin(tmp.data());
}


GUID StringToGUID(const mpt::winstring &str)
{
	return StringToIID(str);
}


GUID CreateGUID()
{
	GUID guid = GUID();
	switch(::CoCreateGuid(&guid))
	{
	case S_OK:
		// nothing
		break;
	default:
		guid = GUID();
		throw std::runtime_error("CoCreateGuid() failed.");
	}
	return guid;
}


bool IsValid(UUID uuid)
{
	return false
		|| uuid.Data1 != 0
		|| uuid.Data2 != 0
		|| uuid.Data3 != 0
		|| uuid.Data4[0] != 0
		|| uuid.Data4[1] != 0
		|| uuid.Data4[2] != 0
		|| uuid.Data4[3] != 0
		|| uuid.Data4[4] != 0
		|| uuid.Data4[5] != 0
		|| uuid.Data4[6] != 0
		|| uuid.Data4[7] != 0
		;
}


#endif // MODPLUG_TRACKER || !NO_DMO


} // namespace Util


#endif // MPT_OS_WINDOWS


namespace mpt
{

#if MPT_OS_WINDOWS

mpt::UUID UUIDFromWin32(::UUID uuid)
{
	return mpt::UUID
		( uuid.Data1
		, uuid.Data2
		, uuid.Data3
		, (static_cast<uint64>(0)
			| (static_cast<uint64>(uuid.Data4[0]) << 56)
			| (static_cast<uint64>(uuid.Data4[1]) << 48)
			| (static_cast<uint64>(uuid.Data4[2]) << 40)
			| (static_cast<uint64>(uuid.Data4[3]) << 32)
			| (static_cast<uint64>(uuid.Data4[4]) << 24)
			| (static_cast<uint64>(uuid.Data4[5]) << 16)
			| (static_cast<uint64>(uuid.Data4[6]) <<  8)
			| (static_cast<uint64>(uuid.Data4[7]) <<  0)
			)
		);
}

::UUID UUIDToWin32(mpt::UUID uuid)
{
	::UUID result = ::UUID();
	result.Data1 = uuid.GetData1();
	result.Data2 = uuid.GetData2();
	result.Data3 = uuid.GetData3();
	result.Data4[0] = static_cast<uint8>(uuid.GetData4() >> 56);
	result.Data4[1] = static_cast<uint8>(uuid.GetData4() >> 48);
	result.Data4[2] = static_cast<uint8>(uuid.GetData4() >> 40);
	result.Data4[3] = static_cast<uint8>(uuid.GetData4() >> 32);
	result.Data4[4] = static_cast<uint8>(uuid.GetData4() >> 24);
	result.Data4[5] = static_cast<uint8>(uuid.GetData4() >> 16);
	result.Data4[6] = static_cast<uint8>(uuid.GetData4() >>  8);
	result.Data4[7] = static_cast<uint8>(uuid.GetData4() >>  0);
	return result;
}

#if defined(MODPLUG_TRACKER) || !defined(NO_DMO)

UUID::UUID(::UUID uuid)
{
	*this = UUIDFromWin32(uuid);
}

UUID::operator ::UUID () const
{
	return UUIDToWin32(*this);
}

#endif // MODPLUG_TRACKER || !NO_DMO

#endif // MPT_OS_WINDOWS

UUID UUID::Generate()
{
	#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
		#if (_WIN32_WINNT >= 0x0602)
			::GUID guid = ::GUID();
			HRESULT result = CoCreateGuid(&guid);
			if(result != S_OK)
			{
				return mpt::UUID::RFC4122Random();
			}
			return mpt::UUIDFromWin32(guid);
		#else
			return mpt::UUID::RFC4122Random();
		#endif
	#elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
		::UUID uuid = ::UUID();
		RPC_STATUS status = ::UuidCreate(&uuid);
		if(status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
		{
			return mpt::UUID::RFC4122Random();
		}
		status = RPC_S_OK;
		if(UuidIsNil(&uuid, &status) != FALSE)
		{
			return mpt::UUID::RFC4122Random();
		}
		if(status != RPC_S_OK)
		{
			return mpt::UUID::RFC4122Random();
		}
		return mpt::UUIDFromWin32(uuid);
	#else
		return RFC4122Random();
	#endif
}

UUID UUID::GenerateLocalUseOnly()
{
	#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
		#if (_WIN32_WINNT >= 0x0602)
			::GUID guid = ::GUID();
			HRESULT result = CoCreateGuid(&guid);
			if(result != S_OK)
			{
				return mpt::UUID::RFC4122Random();
			}
			return mpt::UUIDFromWin32(guid);
		#else
			return mpt::UUID::RFC4122Random();
		#endif
	#elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
		::UUID uuid = ::UUID();
		RPC_STATUS status = ::UuidCreateSequential(&uuid);
		if(status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
		{
			return Generate();
		}
		status = RPC_S_OK;
		if(UuidIsNil(&uuid, &status) != FALSE)
		{
			return mpt::UUID::RFC4122Random();
		}
		if(status != RPC_S_OK)
		{
			return mpt::UUID::RFC4122Random();
		}
		return mpt::UUIDFromWin32(uuid);
	#else
		return RFC4122Random();
	#endif
}

UUID UUID::RFC4122Random()
{
	UUID result;
	mpt::thread_safe_prng<mpt::default_prng> & prng = mpt::global_prng();
	result.Data1 = mpt::random<uint32>(prng);
	result.Data2 = mpt::random<uint16>(prng);
	result.Data3 = mpt::random<uint16>(prng);
	result.Data4 = mpt::random<uint64>(prng);
	result.MakeRFC4122(4);
	return result;
}

void UUID::MakeRFC4122(uint8 version) noexcept
{
	// variant
	uint8 Nn = static_cast<uint8>((Data4 >> 56) & 0xffu);
	Data4 &= 0x00ffffffffffffffull;
	Nn &= ~(0xc0u);
	Nn |= 0x80u;
	Data4 |= static_cast<uint64>(Nn) << 56;
	// version
	version &= 0x0fu;
	uint8 Mm = static_cast<uint8>((Data3 >> 8) & 0xffu);
	Data3 &= 0x00ffu;
	Mm &= ~(0xf0u);
	Mm |= (version << 4u);
	Data3 |= static_cast<uint16>(Mm) << 8;
}

UUID UUID::FromString(const mpt::ustring &str)
{
	std::vector<mpt::ustring> segments = mpt::String::Split<mpt::ustring>(str, U_("-"));
	if(segments.size() != 5)
	{
		return UUID();
	}
	if(segments[0].length() != 8)
	{
		return UUID();
	}
	if(segments[1].length() != 4)
	{
		return UUID();
	}
	if(segments[2].length() != 4)
	{
		return UUID();
	}
	if(segments[3].length() != 4)
	{
		return UUID();
	}
	if(segments[4].length() != 12)
	{
		return UUID();
	}
	UUID result;
	result.Data1 = mpt::String::Parse::Hex<uint32>(segments[0]);
	result.Data2 = mpt::String::Parse::Hex<uint16>(segments[1]);
	result.Data3 = mpt::String::Parse::Hex<uint16>(segments[2]);
	result.Data4 = mpt::String::Parse::Hex<uint64>(segments[3] + segments[4]);
	return result;
}

mpt::ustring UUID::ToUString() const
{
	return mpt::ustring()
		+ mpt::ufmt::hex0<8>(GetData1())
		+ U_("-")
		+ mpt::ufmt::hex0<4>(GetData2())
		+ U_("-")
		+ mpt::ufmt::hex0<4>(GetData3())
		+ U_("-")
		+ mpt::ufmt::hex0<4>(static_cast<uint16>(GetData4() >> 48))
		+ U_("-")
		+ mpt::ufmt::hex0<4>(static_cast<uint16>(GetData4() >> 32))
		+ mpt::ufmt::hex0<8>(static_cast<uint32>(GetData4() >>  0))
		;
}

UUID::UUID(UUIDbin uuid)
{
	Data1 = uuid.Data1.get();
	Data2 = uuid.Data2.get();
	Data3 = uuid.Data3.get();
	Data4 = uuid.Data4.get();
}

UUID::UUID(GUIDms guid)
{
	Data1 = guid.Data1.get();
	Data2 = guid.Data2.get();
	Data3 = guid.Data3.get();
	Data4 = guid.Data4.get();
}

UUID::operator UUIDbin() const
{
	UUIDbin result;
	result.Data1 = GetData1();
	result.Data2 = GetData2();
	result.Data3 = GetData3();
	result.Data4 = GetData4();
	return result;
}

UUID::operator GUIDms() const
{
	GUIDms result;
	result.Data1 = GetData1();
	result.Data2 = GetData2();
	result.Data3 = GetData3();
	result.Data4 = GetData4();
	return result;
}


} // namespace mpt


OPENMPT_NAMESPACE_END
