//-----------------------------------------------------------------------------
// Project     : SDK Core
//
// Category    : SDK Core Interfaces
// Filename    : pluginterfaces/base/fvariant.h
// 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.
//-----------------------------------------------------------------------------

#pragma once

#include "pluginterfaces/base/fstrdefs.h"
#include "pluginterfaces/base/funknown.h"

//------------------------------------------------------------------------
namespace Steinberg {

class FUnknown;

//------------------------------------------------------------------------
//  FVariant struct declaration
//------------------------------------------------------------------------
/** A Value of variable type.
 \ingroup pluginBase
 */
class FVariant
{
//------------------------------------------------------------------------
public:
	enum
	{
		kEmpty = 0,
		kInteger = 1 << 0,
		kFloat = 1 << 1,
		kString8 = 1 << 2,
		kObject = 1 << 3,
		kOwner = 1 << 4,
		kString16 = 1 << 5
	};

//------------------------------------------------------------------------
	// ctors
	inline FVariant () { memset (this, 0, sizeof (FVariant)); }
	inline FVariant (const FVariant& variant);

	inline FVariant (bool b) : type (kInteger), intValue (b) {}
	inline FVariant (uint32 v) : type (kInteger), intValue (v) {}
	inline FVariant (int64 v) : type (kInteger), intValue (v) {}
	inline FVariant (double v) : type (kFloat), floatValue (v) {}
	inline FVariant (const char8* str) : type (kString8), string8 (str) {}
	inline FVariant (const char16* str) : type (kString16), string16 (str) {}
	inline FVariant (FUnknown* obj, bool owner = false) : type (kObject), object (obj)
	{
		setOwner (owner);
	}
	inline ~FVariant () { empty (); }

//------------------------------------------------------------------------
	inline FVariant& operator= (const FVariant& variant);

	inline void set (bool b)
	{
		setInt (b);
	}

	inline void set (uint32 v)
	{
		setInt (v);
	}

	inline void set (int64 v)
	{
		setInt (v);
	}

	inline void set (double v)
	{
		setFloat (v);
	}

	inline void set (const char8* c)
	{
		setString8 (c);
	}

	inline void set (const char16* c)
	{
		setString16 (c);
	}

	inline void setInt (int64 v)
	{
		empty ();
		type = kInteger;
		intValue = v;
	}

	inline void setFloat (double v)
	{
		empty ();
		type = kFloat;
		floatValue = v;
	}
	inline void setString8 (const char8* v)
	{
		empty ();
		type = kString8;
		string8 = v;
	}
	inline void setString16 (const char16* v)
	{
		empty ();
		type = kString16;
		string16 = v;
	}

	inline void setObject (FUnknown* obj)
	{
		empty ();
		type = kObject;
		object = obj;
	}

	template <typename T>
	inline T get () const;

	inline int64 getInt () const { return (type & kInteger) ? intValue : 0; }
	inline double getFloat () const { return (type & kFloat) ? floatValue : 0.; }
	inline double getNumber () const
	{
		return (type & kInteger) ? static_cast<double> (intValue) : (type & kFloat) ? floatValue :
																					  0.;
	}
	inline const char8* getString8 () const { return (type & kString8) ? string8 : nullptr; }
	inline const char16* getString16 () const { return (type & kString16) ? string16 : nullptr; }

	inline FUnknown* getObject () const { return (type & kObject) ? object : nullptr; }

	inline uint16 getType () const { return static_cast<uint16> (type & ~(kOwner)); }
	inline bool isEmpty () const { return getType () == kEmpty; }
	inline bool isOwner () const { return (type & kOwner) != 0; }
	inline bool isString () const { return (type & (kString8 | kString16)) != 0; }
	inline void setOwner (bool state)
	{
		if (state)
			type |= kOwner;
		else
			type &= ~kOwner;
	}

	void empty ();
//------------------------------------------------------------------------
	uint16 type;
	union
	{
		int64 intValue;
		double floatValue;
		const char8* string8;
		const char16* string16;
		FUnknown* object;
	};
};

//------------------------------------------------------------------------
inline bool operator== (const FVariant& v1, const FVariant& v2)
{
#if SMTG_PLATFORM_64
	return v1.type == v2.type && v1.intValue == v2.intValue;
#else
	if (v1.type != v2.type)
		return false;
	if (v1.type & (FVariant::kString8 | FVariant::kString16 | FVariant::kObject))
		return v1.string8 == v2.string8; // pointer type comparisons
	return v1.intValue == v2.intValue; // intValue & double comparison

#endif
}

template <>
inline bool FVariant::get<bool> () const
{
	return getInt () != 0;
}

template <>
inline uint32 FVariant::get<uint32> () const
{
	return static_cast<uint32> (getInt ());
}

template <>
inline int32 FVariant::get<int32> () const
{
	return static_cast<int32> (getInt ());
}

template <>
inline int64 FVariant::get<int64> () const
{
	return static_cast<int64> (getInt ());
}

template <>
inline float FVariant::get<float> () const
{
	return static_cast<float> (getFloat ());
}

template <>
inline double FVariant::get<double> () const
{
	return getFloat ();
}

template <>
inline const char8* FVariant::get<const char8*> () const
{
	return getString8 ();
}

template <>
inline const char16* FVariant::get<const char16*> () const
{
	return getString16 ();
}

template <>
inline FUnknown* FVariant::get<FUnknown*> () const
{
	return getObject ();
}

//------------------------------------------------------------------------
inline bool operator!= (const FVariant& v1, const FVariant& v2) { return !(v1 == v2); }

//------------------------------------------------------------------------
inline FVariant::FVariant (const FVariant& variant) : type (kEmpty) { *this = variant; }

//------------------------------------------------------------------------
inline void FVariant::empty ()
{
	if (type & kOwner)
	{
		if ((type & kString8) && string8)
			delete[] string8;
		else if ((type & kString16) && string16)
			delete[] string16;

		else if ((type & kObject) && object)
			object->release ();
	}
	memset (this, 0, sizeof (FVariant));
}

//------------------------------------------------------------------------
inline FVariant& FVariant::operator= (const FVariant& variant)
{
	empty ();

	type = variant.type;

	if ((type & kString8) && variant.string8)
	{
		string8 = new char8[strlen (variant.string8) + 1];
		strcpy (const_cast<char8*> (string8), variant.string8);
		type |= kOwner;
	}
	else if ((type & kString16) && variant.string16)
	{
		auto len = static_cast<size_t> (strlen16 (variant.string16));
		string16 = new char16[len + 1];
		char16* tmp = const_cast<char16*> (string16);
		memcpy (tmp, variant.string16, len * sizeof (char16));
		tmp[len] = 0;
		type |= kOwner;
	}
	else if ((type & kObject) && variant.object)
	{
		object = variant.object;
		object->addRef ();
		type |= kOwner;
	}
	else
		intValue = variant.intValue; // copy memory

	return *this;
}

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