/** @file string.cpp
 *	Definition of string template for EABaseTypes
 *	@headerfile string.h "EABaseTypes/string.h"
 *	@author Jeff Skelton (jskelton@ea.com)
 *
 *	EABaseTypes
 *	(c) 2011 Electronic Arts Inc.
 */

#include "EABaseTypes/string.h"
#include "EABaseTypes/factory.h"
#include "EABaseTypes/stringtable.h"
#include "EAAssert/eaassert.h"

namespace EA
{
#if defined(EABASETYPES_NO_STRING_INTERN)
	/* explicit */ String::String(Types::Factory* factory)
		: RawString(factory->GetAllocator())
	{
	}
	
	String::String(Types::Factory* factory, const char* str)
		: RawString(factory->GetAllocator(), str)
	{
	}

	String::String(const String& str)
		: RawString(str)
	{
	}

#else

	/* explicit */ String::String(Types::Factory* factory)
		: mFactory(factory)
		, mStr(factory->GetStringTable()->GetEmptyString())
	{
	}

	String::String(Types::Factory* factory, const char* str)
		: mFactory(factory)
		, mStr(factory->GetStringTable()->GetEmptyString())
	{
		EA_ASSERT_MSG(str != NULL, ("Can't initialize EA::String to NULL"));
		assign(str);
	}

	String::String(const String& str)
		: mFactory(str.mFactory)
		, mStr(str.mFactory->GetStringTable()->GetEmptyString())
	{
		assign(str.c_str());
	}

	bool String::empty() const
	{
		return mStr->empty();
	}

	void String::Clear()
	{
		// if we are not the empty string then look our string value up in the string table, 
		// decrement our reference count and remove the string from the table if it is no longer needed
		mFactory->GetStringTable()->Release(mStr);
		
		mStr = NULL;
	}

	void String::assign( const char* val )
	{
		/*
		1. clear any existing data
		2. If the value is the empty string we are done
		2. If the new value already exists in the string table, grab it and increment the reference count
		3. If the new value does not exist in the string table, add it and initialize the reference count 
		*/
		Clear();

		mStr = mFactory->GetStringTable()->Intern(val);		
	}
	
	void String::assign( const char* val, size_type length )
	{
		/*
		1. clear any existing data
		2. If the value is the empty string we are done
		2. If the new value already exists in the string table, grab it and increment the reference count
		3. If the new value does not exist in the string table, add it and initialize the reference count 
		*/
		Clear();

		mStr = mFactory->GetStringTable()->Intern(val, length);	
	}

	String& String::operator=( const char* rhs )
	{
		assign(rhs);
		return *this;
	}

	String& String::operator=( const String& rhs )
	{
		if(&rhs != this)
		{
			assign(rhs.c_str());
		}
		return *this;
	}

	// PC-Lint hates varargs
	//lint -efunc(1916, EA::String::sprintf)
	//lint -efunc(438, EA::String::sprintf)
	String& String::sprintf( const RawString::value_type* pFormat, ... )
	{
		/*
		in order to provide sprintf functionality with interned strings we need to create a temporary string, 
		format it and then do the assignment. The only other option is to not provide this API and force the user to do 
		the formatting prior to constructing the EA::String instance, either way a temp string is being allocated
		so we might as well provide the API.
		*/
		va_list args;
		va_start(args, pFormat);

		RawString str(mFactory->GetAllocator());
		str.sprintf_va_list(pFormat, args);
		assign(str.c_str());

		va_end(args);
		return *this;
	}

	size_t String::hash::operator()( const String& str ) const
	{
		eastl::hash<const char8_t*> h;
		size_t h1 = h(str.c_str());
		return (h1);
	}

#if defined(EA_RUNTIME_TESTING)
	int String::GetRefCount() const
	{
		EA_ASSERT(mStr != NULL);

		return mFactory->GetStringTable()->GetRefCount(mStr);
	}
#endif

#endif //!defined(EABASETYPES_NO_STRING_INTERN)
}
