/** @file basetypeencoder.h
*	basetype encoder/decoder templates
*	@author Chris Burns (cburns@ea.com)
*
*	EABaseTypes
*	(c) 2011 Electronic Arts Inc.
*/

#ifndef EABASETYPES_BASETYPEENCODER_H_
#define EABASETYPES_BASETYPEENCODER_H_

#include "EABaseTypes/datatypes.h"
#include "eaassert/eaassert.h"

namespace EA
{
	namespace Types
	{
		class Factory;

		/*! @class BaseTypeEncoder basetypeencoder.h	"EABaseTypes/basetypeencoder.h"
		*	@brief Encodes data from a Decoder as a BaseType
		*/
		class BaseTypeEncoder
		{
		public:

			/*!
			*	BaseTypeEncoder Constructor
			*	@param[in]	factory	A pointer to a factory for creating BaseTypes
			*/
			BaseTypeEncoder(EA::Types::Factory* factory);


			/*!
			*	BaseTypeEncoder Destructor
			*/
			~BaseTypeEncoder();
			
			EA::Types::BaseType* AcquireRoot();

			// Encoder template interface
			void Reset();

			void BeginArray();
			void EndArray();

			void BeginArrayValue(size_t index) {}
			void EndArrayValue(size_t index) {}

			void BeginObject();
			void EndObject();

			void BeginObjectValue(const char* key, size_t length);
			void EndObjectValue(const char* key, size_t length);

			void Boolean(bool boolean);
			void Number(BaseTypeFloat number);
			void String(const char* str, size_t length);
			void Function(EA::Types::FunctionIn function);
			void UserData(EA::Types::UserDataIn userData);
			void Null();

		private:

			void Add(Types::BaseType* value);

			EA::String			mCurrentKey;
			typedef EASTLICA::Vector<Types::BaseType*> BaseTypeStack; // could we use some kind of fixed vector here?

			BaseTypeStack		mStack;
			EA::Types::Factory*	mFactory;
			Types::BaseType*	mRoot;
		};

		/*! @class BaseTypeArrayEncoder basetypeencoder.h	"EABaseTypes/basetypeencoder.h"
		*	@brief Encodes data from a Decoder into an Array
		*	@note each individual value will be appended to the Array
		*/
		class BaseTypeArrayEncoder
		{
		public:
			
			/*!
			*	BaseTypeArrayEncoder Constructor
			*	@param[in]	root	A pointer to the Array to append data to
			*	@note root must be a valid pointer to an Array
			*/
			BaseTypeArrayEncoder(Types::Array* root);

			/*!
			*	BaseTypeArrayEncoder Destructor
			*/
			~BaseTypeArrayEncoder();

			/*!
			*	Reset the BaseTypeArrayEncoder for re-use
			*	@param[in]	root	A pointer to the Array to append data to
			*	@note root must be a valid pointer to an Array
			*/
			void Reset(Types::Array* root);

			// Encoder template interface
			void Reset();
			void BeginArray();
			void EndArray();

			void BeginArrayValue(size_t index);
			void EndArrayValue(size_t index);

			void BeginObject();
			void EndObject();

			void BeginObjectValue(const char* key, size_t length);
			void EndObjectValue(const char* key, size_t length);

			void Boolean(bool boolean);
			void Number(BaseTypeFloat number);
			void String(const char* str, size_t length);
			void Function(EA::Types::FunctionIn function);
			void UserData(EA::Types::UserDataIn userData);
			void Null();

		private:

			void EndValue();

			Types::Array*			mRoot;
			BaseTypeEncoder			mEncoder;
			int						mLevel;
		};

		/*!
		*	Decode a BaseType into an encoder
		*	@tparam	Encoder		The encoder type
		*	@param	encoder		A pointer to the encoder
		*	@param	baseType	A pointer to the BaseType
		*/
		template<typename Encoder>
		void Decode(Encoder* encoder, Types::BaseType* baseType)
		{
			if(baseType != NULL)
			{
				Types::DataType type = baseType->GetType();

				switch(type)
				{
					case Types::UNKNOWN_TYPE:
						EA_FAIL_MESSAGE("Cannot decode UNKNOWN_TYPE!");
						break;
					case Types::FUNCTION_TYPE:
						encoder->Function(FunctionIn(static_cast<Types::Function*>(baseType)));
						break;
					case Types::BOOLEAN_TYPE:
						encoder->Boolean(*static_cast<Types::Boolean*>(baseType));
						break;
					case Types::NUMBER_TYPE:
						encoder->Number(*static_cast<Types::Number*>(baseType));
						break;
					case Types::STRING_TYPE:
						{
							const EA::String& value = *static_cast<Types::String*>(baseType);
							encoder->String(value.c_str(), value.length());
						}
						break;
					case Types::ARRAY_TYPE:
						{
							Types::Array* value = static_cast<Types::Array*>(baseType);
							encoder->BeginArray();
							for(size_t i = 0; i < value->size(); ++i)
							{
								encoder->BeginArrayValue(i);
								Decode(encoder, (*value)[static_cast<eastl_size_t>(i)].GetUnsafePointer());
								encoder->EndArrayValue(i);
							}
							encoder->EndArray();
						}
						break;
					case Types::OBJECT_TYPE:
						{
							Types::Object* value = static_cast<Types::Object*>(baseType);
							encoder->BeginObject();

							Object::iterator it = value->begin();
							Object::iterator end = value->end();
							for(; it != end; ++it)
							{
								const EA::String& key = (*it).first;
								encoder->BeginObjectValue(key.c_str(), key.length());
								Decode(encoder, (*it).second.GetUnsafePointer());
								encoder->EndObjectValue(key.c_str(), key.length());
							}

							encoder->EndObject();
						}
						break;
					case Types::USERDATA_TYPE:
						encoder->UserData(UserDataIn(static_cast<Types::UserData*>(baseType)));
						break;
				}
			}
			else
			{
				encoder->Null();
			}
		}

		/*! @class BaseTypeDecoder basetypeencoder.h	"EABaseTypes/basetypeencoder.h"
		*	@brief Decoder for BaseTypes
		*	@tparam Encoder	The encoder type
		*/
        //disable warning 4718 for the decode function since 
        //recursively calling the Decode function makes sense
        //due to the static casting that's done (and shouldn't
        //result in a stack overflow)
#if _MSC_VER
	#pragma warning( push )
    #pragma warning( disable: 4718 )
#endif // _MSC_VER
		template<typename Encoder>
		class BaseTypeDecoder
		{
		public:

			// Decoder template interface
			static void Decode(void* e, void* p)
			{
				Encoder* encoder = static_cast<Encoder*>(e);
				EA::Types::BaseType* value = static_cast<EA::Types::BaseType*>(p);
				
				EA::Types::Decode(encoder, value);
			}
		};
#if _MSC_VER
	#pragma warning( pop )
#endif // _MSC_VER

		/*! @class BaseTypeArrayDecoder basetypeencoder.h	"EABaseTypes/basetypeencoder.h"
		*	@brief Decoder for an Array
		*	@tparam Encoder	The encoder type
		*	@note The Array container will not be decoded, only its elements
		*/
		template<typename Encoder>
		class BaseTypeArrayDecoder
		{
		public:

			// Decoder template interface
			static void Decode(void* e, void* p)
			{
				Encoder* encoder = static_cast<Encoder*>(e);
				EA::Types::Array* value = static_cast<EA::Types::Array*>(p);
				
				if(value != NULL)
				{
					Array::const_iterator it = value->begin();
					Array::const_iterator end = value->end();

					for(; it != end; it++)
					{
						EA::Types::Decode(encoder, (*it).GetUnsafePointer());
					}
				}
			}
		};
	}
}

#endif /*  EABASETYPES_BASETYPEENCODER_H_ */
