//-----------------------------------------------------------------------------------------------
// NamedProperties.hpp
//
#pragma once
#ifndef include_NamedProperties
#define include_NamedProperties

//-----------------------------------------------------------------------------------------------
// Goals:
//	- Construct, set/populate, pass (by reference), and get - without heap allocs in main cases
//	- Heap allocation required if large properties (and/or a large quantity of them) are stored
//


//-----------------------------------------------------------------------------------------------
// ToDo List:
//	- Accept only small-enough (<= 16B) properties as "local small properties"
//	- Accept only known types (primitives, as well as Rgba, Vector3, etc.) as "local small"
//	- Use specialized overrides and/or type traits to determine if the type being set qualifies
//	- Support for more than 10 local small known types
//	- Speed-comparison test for 1, 5, 10, 15, 20, 50, 100, 200 properties for map< hash, property > vs. vector/array[ HashAndProperty ]
//  - Customized support for string properties?
//	- Customized support for pointer properties?
//	- General support for arbitrarily-sized and/or unknown-type properties?

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/EngineBase.hpp"
#include "Engine/Core/Rgba.hpp"
#include "Engine/Core/Vector2.hpp"
#include "Engine/Core/Vector3.hpp"
#include "Engine/Core/Vector4.hpp"
#include "Engine/Core/IntVector2.hpp"
#include "Engine/Core/IntVector3.hpp"
//#include "Engine/Core/IntVector4.hpp"
#include "Engine/Core/StringTable.hpp"


/////////////////////////////////////////////////////////////////////////////////////////////////
enum PropertyGetResult
{
	GET_RESULT_SUCCESS,
	GET_RESULT_FAILURE_PROPERTY_NOT_FOUND,
	GET_RESULT_FAILURE_PROPERTY_WRONG_TYPE,
	NUM_GET_RESULTS
};

/*
/////////////////////////////////////////////////////////////////////////////////////////////////
enum SmallKnownPropertyType
{
	SMALLKNOWNPROPERTYTYPE_DOUBLE,
	SMALLKNOWNPROPERTYTYPE_INT,
	SMALLKNOWNPROPERTYTYPE_RGBA,
	SMALLKNOWNPROPERTYTYPE_VECTOR2,
	SMALLKNOWNPROPERTYTYPE_VECTOR3,
	SMALLKNOWNPROPERTYTYPE_VECTOR4,
	SMALLKNOWNPROPERTYTYPE_INTVECTOR2,
	SMALLKNOWNPROPERTYTYPE_INTVECTOR3,
//	SMALLKNOWNPROPERTYTYPE_INTVECTOR4,
	SMALLKNOWNPROPERTYTYPE_POINTER,
	NUM_SMALLKNOWNPROPERTYTYPES
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class TypedPropertyBase
{
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class SmallKnownTypedProperty
{
	friend class NamedProperties;
	friend class HashNamedSmallKnownTypedProperty;

public:
	SmallKnownTypedProperty()
		: m_rttiHash( 0 )
		, m_rttiTypeName( nullptr )
	{}

	template< typename T_PropertyType >
	SmallKnownTypedProperty( const T_PropertyType& smallKnownTypedData );

	template< typename T_PropertyType >
	void Set( const T_PropertyType& propertyValue )
	{
		UNUSED( propertyValue ); // Apparently MSVC doesn't recognize "typeid()" as "usage"
		const type_info& destinationTypeInfo = typeid( propertyValue );
		m_rttiHash = destinationTypeInfo.hash_code();
		m_rttiTypeName = destinationTypeInfo.name();
		memcpy( m_dataAsRawBytes, &propertyValue, sizeof( propertyValue ) );
	}

	template< typename T_PropertyType >
	PropertyGetResult GetValueOut( T_PropertyType& out_propertyValue )
	{
		const type_info& destinationTypeInfo = typeid( out_propertyValue );
		if( destinationTypeInfo.hash_code() != m_rttiHash )
		{
			return GET_RESULT_FAILURE_PROPERTY_WRONG_TYPE;
		}
		else
		{
			memcpy( &out_propertyValue, m_dataAsRawBytes, sizeof( out_propertyValue ) );
			return GET_RESULT_SUCCESS;
		}
	}

private:
	size_t			m_rttiHash;
	const char*		m_rttiTypeName;
	unsigned char	m_dataAsRawBytes[ sizeof( Vector4 ) ];

private:
	void operator = ( const SmallKnownTypedProperty& copy ) // should this be Intentionally not implemented?
	{
		m_rttiHash = copy.m_rttiHash;
		m_rttiTypeName = copy.m_rttiTypeName;
		memcpy( m_dataAsRawBytes, copy.m_dataAsRawBytes, sizeof( m_dataAsRawBytes ) );
	}
};


/////////////////////////////////////////////////////////////////////////////////////////////////
template< typename T_PropertyType >
inline SmallKnownTypedProperty::SmallKnownTypedProperty( const T_PropertyType& smallKnownTypedData )
	: m_rttiHash( 0 )
	, m_rttiTypeName( nullptr )
{
	const type_info& typeInfo = typeid( smallKnownTypedData );
	m_rttiHash = typeInfo.hash_code();
	m_rttiTypeName = typeInfo.name();
	memcpy( m_dataAsRawBytes, &smallKnownTypedData, sizeof( smallKnownTypedData ) );
}


/////////////////////////////////////////////////////////////////////////////////////////////////
class HashNamedSmallKnownTypedProperty
{
	friend class NamedProperties;

private: public:
	HashValue							m_keyNameHash;
	SmallKnownTypedProperty				m_property;

private:
	HashNamedSmallKnownTypedProperty()
		: m_keyNameHash( 0 )
	{}

	template< typename T_PropertyType >
	void Set( HashValue keyNameHash, const T_PropertyType& propertyValue )
	{
		m_keyNameHash = keyNameHash;
		m_property.Set( propertyValue );
	}

	template< typename T_PropertyType >
	PropertyGetResult GetValueOut( T_PropertyType& out_propertyValue )
	{
		return m_property.GetValueOut( out_propertyValue );
	}
};



/////////////////////////////////////////////////////////////////////////////////////////////////
template< typename T_PropertyType >
class CustomTypedProperty : public TypedPropertyBase
{
};


const int MAX_LOCAL_SMALL_PROPERTIES = 10;


/////////////////////////////////////////////////////////////////////////////////////////////////
class NamedProperties
{
public:
	NamedProperties()
		: m_numSmallKnownTypedProperties( 0 )
	{}

	template< typename T_PropertyType >
	void Set( HashValue keyNameHash, const T_PropertyType& propertyValue );

	template< typename T_PropertyType >
	void Set( const char* keyName, const T_PropertyType& propertyValue );

	template< typename T_PropertyType >
	PropertyGetResult Get( HashValue keyNameHash, T_PropertyType& out_propertyValue );

	template< typename T_PropertyType >
	PropertyGetResult Get( const char* keyName, T_PropertyType& out_propertyValue );

private: public:
	short												m_numSmallKnownTypedProperties;
	HashNamedSmallKnownTypedProperty					m_localSmallKnownTypedProperties[ MAX_LOCAL_SMALL_PROPERTIES ];
	std::vector< HashNamedSmallKnownTypedProperty >		m_additionalSmallKnownTypedProperties;
};


//-----------------------------------------------------------------------------------------------
template< typename T_PropertyType >
inline void NamedProperties::Set( HashValue keyNameHash, const T_PropertyType& propertyValue )
{
	for( int localSlot = 0; localSlot < m_numSmallKnownTypedProperties; ++ localSlot )
	{
		HashNamedSmallKnownTypedProperty& localTypedProperty = m_localSmallKnownTypedProperties[ localSlot ];
		if( localTypedProperty.m_keyNameHash == keyNameHash )
		{
			localTypedProperty.m_property.Set( propertyValue );
			return;
		}
	}

	int nextSlot = m_numSmallKnownTypedProperties;
	++ m_numSmallKnownTypedProperties;
	HashNamedSmallKnownTypedProperty& localTypedProperty = m_localSmallKnownTypedProperties[ nextSlot ];
	localTypedProperty.Set( keyNameHash, propertyValue );
}


//-----------------------------------------------------------------------------------------------
template< typename T_PropertyType >
inline void NamedProperties::Set( const char* keyName, const T_PropertyType& propertyValue )
{
	HashValue keyNameHash = Hash( keyName );
	Set( keyNameHash, propertyValue );
}


//-----------------------------------------------------------------------------------------------
template< typename T_PropertyType >
inline PropertyGetResult NamedProperties::Get( HashValue keyNameHash, T_PropertyType& out_propertyValue )
{
	for( int localSlot = 0; localSlot < m_numSmallKnownTypedProperties; ++ localSlot )
	{
		HashNamedSmallKnownTypedProperty& localTypedProperty = m_localSmallKnownTypedProperties[ localSlot ];
		if( localTypedProperty.m_keyNameHash == keyNameHash )
		{
			PropertyGetResult result = localTypedProperty.GetValueOut( out_propertyValue );
			return result;
		}
	}

	// TODO: Check non-local slots if any exist and we still haven't found the property

	return GET_RESULT_FAILURE_PROPERTY_NOT_FOUND;
}


//-----------------------------------------------------------------------------------------------
template< typename T_PropertyType >
inline PropertyGetResult NamedProperties::Get( const char* keyName, T_PropertyType& out_propertyValue )
{
	HashValue keyNameHash = Hash( keyName );
	return Get( keyNameHash, out_propertyValue );
}
*/



/////////////////////////////////////////////////////////////////////////////////////////////////
class TypedPropertyBase
{
public:
	TypedPropertyBase( bool isPointerType )
//		: m_isPointerType( isPointerType )
	{
		UNUSED( isPointerType );
	}

//	template< typename T_DataType >
//	T_DataType* GetAsCompatiblePointer( const T_DataType* pointerTypeRequested );

private:
public:
//	bool	m_isPointerType;
};



/////////////////////////////////////////////////////////////////////////////////////////////////
template< typename T_DataType >
class TypedProperty : public TypedPropertyBase
{
	friend class NamedProperties;

private:
	TypedProperty( const T_DataType& propertyData, bool isPointerType );
	void operator = ( const TypedProperty< T_DataType >& copy );

private:
	T_DataType		m_propertyData;
};



/////////////////////////////////////////////////////////////////////////////////////////////////
class NamedPropertyEntry
{
	friend class NamedProperties;

private:
	CaseSensitiveStringHashValue	m_propertyNameHash;
	TypedPropertyBase*				m_property;
	size_t							m_rttiHash;
};



/////////////////////////////////////////////////////////////////////////////////////////////////
class NamedProperties
{
public:
	static const NamedProperties s_empty;

public:
	void Set( CaseSensitiveStringHashValue propertyNameHash, const char* propertyData );
	template< typename T_DataType >
	void Set( const char* propertyName, const T_DataType& propertyData );
	template< typename T_DataType >
	void Set( const std::string& propertyName, const T_DataType& propertyData );
// 	template< typename T_DataType >
// 	void Set( CaseSensitiveStringHashValue propertyNameHash, const T_DataType* propertyDataPointer );
	template< typename T_DataType >
	void Set( CaseSensitiveStringHashValue propertyNameHash, const T_DataType& propertyData );

	PropertyGetResult Get( CaseSensitiveStringHashValue propertyNameHash, std::string& out_propertyData ) const;
	PropertyGetResult Get( const char* propertyName, std::string& out_propertyData ) const;
	PropertyGetResult Get( const std::string& propertyName, std::string& out_propertyData ) const;
	template< typename T_DataType >
	PropertyGetResult Get( const char* propertyName, T_DataType& out_propertyData ) const;
	template< typename T_DataType >
	PropertyGetResult Get( const std::string& propertyName, T_DataType& out_propertyData ) const;
// 	template< typename T_DataType >
// 	PropertyGetResult Get( CaseSensitiveStringHashValue propertyNameHash, T_DataType* out_propertyDataPointer ) const;
	template< typename T_DataType >
	PropertyGetResult Get( CaseSensitiveStringHashValue propertyNameHash, T_DataType& out_propertyData ) const;

private:
	std::vector< NamedPropertyEntry >	m_properties;
};


/*
/////////////////////////////////////////////////////////////////////////////////////////////////
// TypedPropertyBase methods
//
//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
T_DataType* TypedPropertyBase::GetAsCompatiblePointer( const T_DataType* pointerTypeRequested )
{
	if( !m_isPointerType )
		return nullptr;

	here; // How in the hell do we check if the pointer types themselves are compatible?  We can't use virtual template functions, for example...
}
*/


/////////////////////////////////////////////////////////////////////////////////////////////////
// TypedProperty methods
//
//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
inline TypedProperty< T_DataType >::TypedProperty( const T_DataType& propertyData, bool isPointerType )
	: TypedPropertyBase( isPointerType )
	, m_propertyData( propertyData )
{
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// NamedProperties methods
//
//-----------------------------------------------------------------------------------------------
// Wrapper for handling c-style string literals, e.g. "Squirrel Eiserloh", which come in as
//	const char[18] rather than const char* (but we want to store them that way).
//
// This is generally safe-ish as this construct (const char[]) is typically used for persistent,
//	rather than transient, data.  If complete lifetime safety needs to be guaranteed, call Set()
//	with a std::string for the property data instead.
//
inline void NamedProperties::Set( CaseSensitiveStringHashValue propertyNameHash, const char* propertyData )
{
	Set< const char* >( propertyNameHash, propertyData );
}


//-----------------------------------------------------------------------------------------------
// Handles cases in which the property name is provided as a c-style string literal or variable.
//
template< typename T_DataType >
inline void NamedProperties::Set( const char* propertyName, const T_DataType& propertyData )
{
	HashValue propertyNameHash = Hash( propertyName );
	Set( propertyNameHash, propertyData );
}


//-----------------------------------------------------------------------------------------------
// Handles cases in which the property name is provided as a std::string.
//
template< typename T_DataType >
inline void NamedProperties::Set( const std::string& propertyName, const T_DataType& propertyData )
{
	HashValue propertyNameHash = Hash( propertyName );
	Set( propertyNameHash, propertyData );
}


/*
//-----------------------------------------------------------------------------------------------
// Special case for pointers (supports automatic dynamic_cast when Getting as compatible type)
// 
template< typename T_DataType >
inline void NamedProperties::Set( CaseSensitiveStringHashValue propertyNameHash, const T_DataType* propertyDataPointer )
{
	const type_info& typeInfo = typeid( const T_DataType* );
	size_t rttiHash = typeInfo.hash_code();
	std::vector< NamedPropertyEntry >::iterator propertyIter;
	for( propertyIter = m_properties.begin(); propertyIter != m_properties.end(); ++ propertyIter )
	{
		NamedPropertyEntry& entry = *propertyIter;
		if( entry.m_propertyNameHash == propertyNameHash )
		{
			if( entry.m_rttiHash == rttiHash )
			{
				TypedProperty< T_DataType* >* typedProperty = reinterpret_cast< TypedProperty< T_DataType* >* >( entry.m_property );
				typedProperty->m_propertyData = propertyDataPointer;
				return;
			}
			else
			{
				delete entry.m_property;
				TypedProperty< T_DataType* >* newProperty = new TypedProperty< T_DataType* >( propertyDataPointer, true );
				newProperty->m_propertyData = propertyDataPointer;
				entry.m_property = newProperty;
				entry.m_rttiHash = rttiHash;
				return;
			}
		}
	}

	TypedProperty< T_DataType* >* newProperty = new TypedProperty< T_DataType* >( propertyDataPointer, true );
	NamedPropertyEntry newEntry;
	newEntry.m_rttiHash = rttiHash;
	newEntry.m_propertyNameHash = propertyNameHash;
	newEntry.m_property = newProperty;
	m_properties.push_back( newEntry );
}
*/


//-----------------------------------------------------------------------------------------------
// General case (setting arbitrary-typed data with hashed property key name)
// 
template< typename T_DataType >
inline void NamedProperties::Set( CaseSensitiveStringHashValue propertyNameHash, const T_DataType& propertyData )
{
	const type_info& typeInfo = typeid( T_DataType );
	size_t rttiHash = typeInfo.hash_code();
	std::vector< NamedPropertyEntry >::iterator propertyIter;
	for( propertyIter = m_properties.begin(); propertyIter != m_properties.end(); ++ propertyIter )
	{
		NamedPropertyEntry& entry = *propertyIter;
		if( entry.m_propertyNameHash == propertyNameHash )
		{
			if( entry.m_rttiHash == rttiHash )
			{
				TypedProperty< T_DataType >* typedProperty = reinterpret_cast< TypedProperty< T_DataType >* >( entry.m_property );
				typedProperty->m_propertyData = propertyData;
				return;
			}
			else
			{
				delete entry.m_property;
				TypedProperty< T_DataType >* newProperty = new TypedProperty< T_DataType >( propertyData, false );
				newProperty->m_propertyData = propertyData;
				entry.m_property = newProperty;
				entry.m_rttiHash = rttiHash;
				return;
			}
		}
	}

	TypedProperty< T_DataType >* newProperty = new TypedProperty< T_DataType >( propertyData, false );
	NamedPropertyEntry newEntry;
	newEntry.m_rttiHash = rttiHash;
	newEntry.m_propertyNameHash = propertyNameHash;
	newEntry.m_property = newProperty;
	m_properties.push_back( newEntry );
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
inline PropertyGetResult NamedProperties::Get( const char* propertyName, std::string& out_propertyData ) const
{
	HashValue propertyNameHash = Hash( propertyName );
	return Get( propertyNameHash, out_propertyData );
}


//-----------------------------------------------------------------------------------------------
inline PropertyGetResult NamedProperties::Get( const std::string& propertyName, std::string& out_propertyData ) const
{
	HashValue propertyNameHash = Hash( propertyName );
	return Get( propertyNameHash, out_propertyData );
}


//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
PropertyGetResult NamedProperties::Get( const char* propertyName, T_DataType& out_propertyData ) const
{
	HashValue propertyNameHash = Hash( propertyName );
	return Get( propertyNameHash, out_propertyData );
}


//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
PropertyGetResult NamedProperties::Get( const std::string& propertyName, T_DataType& out_propertyData ) const
{
	HashValue propertyNameHash = Hash( propertyName );
	return Get( propertyNameHash, out_propertyData );
}


/*
//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
PropertyGetResult Get( CaseSensitiveStringHashValue propertyNameHash, T_DataType* out_propertyDataPointer )
{
	const type_info& typeInfo = typeid( T_DataType* );
	size_t rttiHash = typeInfo.hash_code();

	std::vector< NamedPropertyEntry >::iterator propertyIter;
	for( propertyIter = m_properties.begin(); propertyIter != m_properties.end(); ++ propertyIter )
	{
		NamedPropertyEntry& entry = *propertyIter;
		if( entry.m_propertyNameHash == propertyNameHash )
		{
			if( entry.m_rttiHash == rttiHash )
			{
				TypedProperty< T_DataType* >* typedProperty = reinterpret_cast< TypedProperty< T_DataType >* >( entry.m_property );
				out_propertyDataPointer = typedProperty->m_propertyData;
				return GET_RESULT_SUCCESS;
			}
			else
			{
				bool wasAbleToConvertPointer = entry.m_property->GetAsCompatiblePointer( out_propertyDataPointer );
				if( wasAbleToConvertPointer )
					return GET_RESULT_SUCCESS;
				else
					return GET_RESULT_FAILURE_PROPERTY_WRONG_TYPE;
			}
		}
	}

	return GET_RESULT_FAILURE_PROPERTY_NOT_FOUND;
}
*/


//-----------------------------------------------------------------------------------------------
template< typename T_DataType >
PropertyGetResult NamedProperties::Get( CaseSensitiveStringHashValue propertyNameHash, T_DataType& out_propertyData ) const
{
	const type_info& typeInfo = typeid( T_DataType );
	size_t rttiHash = typeInfo.hash_code();

	std::vector< NamedPropertyEntry >::const_iterator propertyIter;
	for( propertyIter = m_properties.begin(); propertyIter != m_properties.end(); ++ propertyIter )
	{
		const NamedPropertyEntry& entry = *propertyIter;
		if( entry.m_propertyNameHash == propertyNameHash )
		{
			if( entry.m_rttiHash == rttiHash )
			{
				TypedProperty< T_DataType >* typedProperty = reinterpret_cast< TypedProperty< T_DataType >* >( entry.m_property );
				out_propertyData = typedProperty->m_propertyData;
				return GET_RESULT_SUCCESS;
			}
			else
			{
				return GET_RESULT_FAILURE_PROPERTY_WRONG_TYPE;
			}
		}
	}

	return GET_RESULT_FAILURE_PROPERTY_NOT_FOUND;
}



#endif // include_NamedProperties
