//-----------------------------------------------------------------------------
// Project     : SDK Core
//
// Category    : SDK Core Interfaces
// Filename    : pluginterfaces/base/funknown.cpp
// Created by  : Steinberg, 01/2004
// Description : Basic Interface
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses. 
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------

#include "funknown.h"

#include "fstrdefs.h"

#include <stdio.h>

#if SMTG_OS_WINDOWS
#include <objbase.h>

#if defined(__MINGW32__)
/* UUID */
#include <string>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#endif

#endif

#if SMTG_OS_MACOS
#include <CoreFoundation/CoreFoundation.h>
#include <libkern/OSAtomic.h>

#if defined(__GNUC__) && (__GNUC__ >= 4) && !__LP64__
// on 32 bit Mac OS X we can safely ignore the format warnings as sizeof(int) == sizeof(long)
#pragma GCC diagnostic ignored "-Wformat"
#endif

#endif

#if SMTG_OS_LINUX
#if !defined (SMTG_USE_ATOMIC)
#if defined (_LIBCPP_VERSION)
#define SMTG_USE_ATOMIC 1
#else
#include <ext/atomicity.h>
#endif
#endif
/* UUID */
#include <string>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#endif

#if defined (SMTG_USE_ATOMIC) && SMTG_USE_ATOMIC
#include <atomic>
#endif

namespace Steinberg {

//------------------------------------------------------------------------
#if COM_COMPATIBLE
#if SMTG_OS_WINDOWS
#define GuidStruct GUID
#else
struct GuidStruct
{
	uint32 Data1;
	uint16 Data2;
	uint16 Data3;
	uint8 Data4[8];
};
#endif
#endif

static void toString8 (char8* string, const char* data, int32 i1, int32 i2);
static void fromString8 (const char8* string, char* data, int32 i1, int32 i2);
static uint32 makeLong (uint8 b1, uint8 b2, uint8 b3, uint8 b4);

//------------------------------------------------------------------------
//  FUnknownPrivate
//------------------------------------------------------------------------
namespace FUnknownPrivate {
//------------------------------------------------------------------------
int32 PLUGIN_API atomicAdd (int32& var, int32 d)
{
#if SMTG_USE_ATOMIC
	return atomic_fetch_add (reinterpret_cast<std::atomic_int_least32_t*> (&var), d) +d;
#elif SMTG_OS_WINDOWS
	return InterlockedExchangeAdd ((volatile long int*)&var, d) + d;
#elif SMTG_OS_MACOS
	return OSAtomicAdd32Barrier (d, (int32_t*)&var);
#elif SMTG_OS_LINUX
	__gnu_cxx::__atomic_add (&var, d);
	return var;
#else
#warning implement me!
	var += d;
	return var;
#endif
}
} // FUnknownPrivate

//------------------------------------------------------------------------
//	FUID implementation
//------------------------------------------------------------------------

FUID::FUID ()
{
	memset (data, 0, sizeof (TUID));
}

//------------------------------------------------------------------------
FUID::FUID (uint32 l1, uint32 l2, uint32 l3, uint32 l4)
{
	from4Int (l1, l2, l3, l4);
}

//------------------------------------------------------------------------
FUID::FUID (const FUID& f)
{
	memcpy (data, f.data, sizeof (TUID));
}

//------------------------------------------------------------------------
#if SMTG_CPP11_STDLIBSUPPORT
FUID::FUID (FUID&& other)
{
	memcpy (data, other.data, sizeof (TUID));
}

FUID& FUID::operator= (FUID&& other)
{
	memcpy (data, other.data, sizeof (TUID));
	return *this;
}
#endif

//------------------------------------------------------------------------
bool FUID::generate ()
{
#if SMTG_OS_WINDOWS
#if defined(_M_ARM64) || defined(_M_ARM)
	//#warning implement me!
	return false;
#elif defined(__MINGW32__)
	boost::uuids::uuid u = boost::uuids::random_generator()();
	memcpy(data, (const void*)&u, 16);
	return true;
#else
	GUID guid;
	HRESULT hr = CoCreateGuid (&guid);
	switch (hr)
	{
		case RPC_S_OK: memcpy (data, (char*)&guid, sizeof (TUID)); return true;

		case RPC_S_UUID_LOCAL_ONLY:
		default: return false;
	}
#endif

#elif SMTG_OS_MACOS
	CFUUIDRef uuid = CFUUIDCreate (kCFAllocatorDefault);
	if (uuid)
	{
		CFUUIDBytes bytes = CFUUIDGetUUIDBytes (uuid);
		memcpy (data, (char*)&bytes, sizeof (TUID));
		CFRelease (uuid);
		return true;
	}
	return false;

#elif SMTG_OS_LINUX
	boost::uuids::uuid u = boost::uuids::random_generator()();
	memcpy(data, (const void*)&u, 16);
	return true;
#else
#warning implement me!
	return false;
#endif
}

//------------------------------------------------------------------------
bool FUID::isValid () const
{
	TUID nulluid = {0};

	return memcmp (data, nulluid, sizeof (TUID)) != 0;
}

//------------------------------------------------------------------------
FUID& FUID::operator= (const FUID& f)
{
	memcpy (data, f.data, sizeof (TUID));
	return *this;
}

//------------------------------------------------------------------------
void FUID::from4Int (uint32 l1, uint32 l2, uint32 l3, uint32 l4)
{
#if COM_COMPATIBLE
	data [0]  = (char)((l1 & 0x000000FF)      );
	data [1]  = (char)((l1 & 0x0000FF00) >>  8);
	data [2]  = (char)((l1 & 0x00FF0000) >> 16);
	data [3]  = (char)((l1 & 0xFF000000) >> 24);
	data [4]  = (char)((l2 & 0x00FF0000) >> 16);
	data [5]  = (char)((l2 & 0xFF000000) >> 24);
	data [6]  = (char)((l2 & 0x000000FF)      );
	data [7]  = (char)((l2 & 0x0000FF00) >>  8);
	data [8]  = (char)((l3 & 0xFF000000) >> 24);
	data [9]  = (char)((l3 & 0x00FF0000) >> 16);
	data [10] = (char)((l3 & 0x0000FF00) >>  8);
	data [11] = (char)((l3 & 0x000000FF)      );
	data [12] = (char)((l4 & 0xFF000000) >> 24);
	data [13] = (char)((l4 & 0x00FF0000) >> 16);
	data [14] = (char)((l4 & 0x0000FF00) >>  8);
	data [15] = (char)((l4 & 0x000000FF)      );
#else
	data [0]  = (char)((l1 & 0xFF000000) >> 24);
	data [1]  = (char)((l1 & 0x00FF0000) >> 16);
	data [2]  = (char)((l1 & 0x0000FF00) >>  8);
	data [3]  = (char)((l1 & 0x000000FF)      );
	data [4]  = (char)((l2 & 0xFF000000) >> 24);
	data [5]  = (char)((l2 & 0x00FF0000) >> 16);
	data [6]  = (char)((l2 & 0x0000FF00) >>  8);
	data [7]  = (char)((l2 & 0x000000FF)      );
	data [8]  = (char)((l3 & 0xFF000000) >> 24);
	data [9]  = (char)((l3 & 0x00FF0000) >> 16);
	data [10] = (char)((l3 & 0x0000FF00) >>  8);
	data [11] = (char)((l3 & 0x000000FF)      );
	data [12] = (char)((l4 & 0xFF000000) >> 24);
	data [13] = (char)((l4 & 0x00FF0000) >> 16);
	data [14] = (char)((l4 & 0x0000FF00) >>  8);
	data [15] = (char)((l4 & 0x000000FF)      );
#endif
}

//------------------------------------------------------------------------
void FUID::to4Int (uint32& d1, uint32& d2, uint32& d3, uint32& d4) const
{
	d1 = getLong1 ();
	d2 = getLong2 ();
	d3 = getLong3 ();
	d4 = getLong4 ();
}

//------------------------------------------------------------------------
uint32 FUID::getLong1 () const
{
#if COM_COMPATIBLE
	return makeLong (data[3], data[2], data[1], data[0]);
#else
	return makeLong (data[0], data[1], data[2], data[3]);
#endif
}

//------------------------------------------------------------------------
uint32 FUID::getLong2 () const
{
#if COM_COMPATIBLE
	return makeLong (data[5], data[4], data[7], data[6]);
#else
	return makeLong (data[4], data[5], data[6], data[7]);
#endif
}

//------------------------------------------------------------------------
uint32 FUID::getLong3 () const
{
#if COM_COMPATIBLE
	return makeLong (data[8], data[9], data[10], data[11]);
#else
	return makeLong (data[8], data[9], data[10], data[11]);
#endif
}

//------------------------------------------------------------------------
uint32 FUID::getLong4 () const
{
#if COM_COMPATIBLE
	return makeLong (data[12], data[13], data[14], data[15]);
#else
	return makeLong (data[12], data[13], data[14], data[15]);
#endif
}

//------------------------------------------------------------------------
void FUID::toString (char8* string) const
{
	if (!string)
		return;

#if COM_COMPATIBLE
	GuidStruct* g = (GuidStruct*)data;

	char8 s[17];
	Steinberg::toString8 (s, data, 8, 16);

	sprintf (string, "%08X%04X%04X%s", g->Data1, g->Data2, g->Data3, s);
#else
	Steinberg::toString8 (string, data, 0, 16);
#endif
}

//------------------------------------------------------------------------
bool FUID::fromString (const char8* string)
{
	if (!string || !*string)
		return false;
	if (strlen (string) != 32)
		return false;

#if COM_COMPATIBLE
	GuidStruct g;
	char s[33];

	strcpy (s, string);
	s[8] = 0;
	sscanf (s, "%x", &g.Data1);
	strcpy (s, string + 8);
	s[4] = 0;
	sscanf (s, "%hx", &g.Data2);
	strcpy (s, string + 12);
	s[4] = 0;
	sscanf (s, "%hx", &g.Data3);

	memcpy (data, &g, 8);
	Steinberg::fromString8 (string + 16, data, 8, 16);
#else
	Steinberg::fromString8 (string, data, 0, 16);
#endif

	return true;
}

//------------------------------------------------------------------------
bool FUID::fromRegistryString (const char8* string)
{
	if (!string || !*string)
		return false;
	if (strlen (string) != 38)
		return false;

// e.g. {c200e360-38c5-11ce-ae62-08002b2b79ef}

#if COM_COMPATIBLE
	GuidStruct g;
	char8 s[10];

	strncpy (s, string + 1, 8);
	s[8] = 0;
	sscanf (s, "%x", &g.Data1);
	strncpy (s, string + 10, 4);
	s[4] = 0;
	sscanf (s, "%hx", &g.Data2);
	strncpy (s, string + 15, 4);
	s[4] = 0;
	sscanf (s, "%hx", &g.Data3);
	memcpy (data, &g, 8);

	Steinberg::fromString8 (string + 20, data, 8, 10);
	Steinberg::fromString8 (string + 25, data, 10, 16);
#else
	Steinberg::fromString8 (string + 1, data, 0, 4);
	Steinberg::fromString8 (string + 10, data, 4, 6);
	Steinberg::fromString8 (string + 15, data, 6, 8);
	Steinberg::fromString8 (string + 20, data, 8, 10);
	Steinberg::fromString8 (string + 25, data, 10, 16);
#endif

	return true;
}

//------------------------------------------------------------------------
void FUID::toRegistryString (char8* string) const
{
// e.g. {c200e360-38c5-11ce-ae62-08002b2b79ef}

#if COM_COMPATIBLE
	GuidStruct* g = (GuidStruct*)data;

	char8 s1[5];
	Steinberg::toString8 (s1, data, 8, 10);

	char8 s2[13];
	Steinberg::toString8 (s2, data, 10, 16);

	sprintf (string, "{%08X-%04X-%04X-%s-%s}", g->Data1, g->Data2, g->Data3, s1, s2);
#else
	char8 s1[9];
	Steinberg::toString8 (s1, data, 0, 4);
	char8 s2[5];
	Steinberg::toString8 (s2, data, 4, 6);
	char8 s3[5];
	Steinberg::toString8 (s3, data, 6, 8);
	char8 s4[5];
	Steinberg::toString8 (s4, data, 8, 10);
	char8 s5[13];
	Steinberg::toString8 (s5, data, 10, 16);

	sprintf (string, "{%s-%s-%s-%s-%s}", s1, s2, s3, s4, s5);
#endif
}

//------------------------------------------------------------------------
void FUID::print (char8* string, int32 style) const
{
	if (!string) // no string: debug output
	{
		char8 str[128];
		print (str, style);

#if SMTG_OS_WINDOWS
		OutputDebugStringA (str);
		OutputDebugStringA ("\n");
#else
		fprintf (stdout, "%s\n", str);
#endif
		return;
	}

	uint32 l1, l2, l3, l4;
	to4Int (l1, l2, l3, l4);

	switch (style)
	{
		case kINLINE_UID:
			sprintf (string, "INLINE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4);
			break;

		case kDECLARE_UID:
			sprintf (string, "DECLARE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4);
			break;

		case kFUID:
			sprintf (string, "FUID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4);
			break;

		case kCLASS_UID:
		default:
			sprintf (string, "DECLARE_CLASS_IID (Interface, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1,
			         l2, l3, l4);
			break;
	}
}

//------------------------------------------------------------------------
//  helpers
//------------------------------------------------------------------------
static uint32 makeLong (uint8 b1, uint8 b2, uint8 b3, uint8 b4)
{
	return (uint32 (b1) << 24) | (uint32 (b2) << 16) | (uint32 (b3) << 8) | uint32 (b4);
}

//------------------------------------------------------------------------
static void toString8 (char8* string, const char* data, int32 i1, int32 i2)
{
	*string = 0;
	for (int32 i = i1; i < i2; i++)
	{
		char8 s[3];
		sprintf (s, "%02X", (uint8)data[i]);
		strcat (string, s);
	}
}

//------------------------------------------------------------------------
static void fromString8 (const char8* string, char* data, int32 i1, int32 i2)
{
	for (int32 i = i1; i < i2; i++)
	{
		char8 s[3];
		s[0] = *string++;
		s[1] = *string++;
		s[2] = 0;

		int32 d = 0;
		sscanf (s, "%2x", &d);
		data[i] = (char)d;
	}
}

//------------------------------------------------------------------------
} // namespace Steinberg
