/** @file proxyencoder.h
*	Encoder and decoder for turning Functions and UserData into primitives -
*	basically, anything that we can turn into JSON
*	@author Chris Burns (cburns@ea.com)
*
*	Core Technology UI
*	(c) 2012 Electronic Arts Inc.
*/

#ifndef EABASETYPESMESSAGE_PRIMITIVEENCODER_H_
#define EABASETYPESMESSAGE_PRIMITIVEENCODER_H_

#include "EABaseTypes/basetypeencoder.h"

namespace EA
{
namespace Types
{
	/*! @class	PrimitiveEncoder primitiveencoder.h	"EABaseTypesMessage/primitiveencoder.h"
	*	@brief	Encodes data into something JSON-friendly
	*/
	template<typename Encoder>
	class PrimitiveEncoder
	{
	public:

		/*!
		*	PrimitiveEncoder constructor
		*/
		PrimitiveEncoder(Encoder* encoder, EA::Types::Array* data, EA::Types::Array* metadata):
			mEncoder(encoder),
			mDataEncoder(data),
			mMetadataEncoder(metadata) {}

		// Encoder template interface
		void Reset() { EA_FAIL_MESSAGE("PrimitiveEncoder does not support Reset() properly"); mDataEncoder.Reset(); mMetadataEncoder.Reset(); }

		void BeginArray() { mDataEncoder.BeginArray(); mMetadataEncoder.BeginArray(); }
		void EndArray() { mDataEncoder.EndArray(); mMetadataEncoder.EndArray(); }

		void BeginArrayValue(size_t index) { mDataEncoder.BeginArrayValue(index); mMetadataEncoder.BeginArrayValue(index); }
		void EndArrayValue(size_t index) { mDataEncoder.BeginArrayValue(index); mMetadataEncoder.BeginArrayValue(index); }

		void BeginObject() { mDataEncoder.BeginObject(); mMetadataEncoder.BeginObject(); }
		void EndObject() { mDataEncoder.EndObject(); mMetadataEncoder.EndObject(); }

		void BeginObjectValue(const char* key, size_t length) { mDataEncoder.BeginObjectValue(key, length); mMetadataEncoder.BeginObjectValue(key, length); }
		void EndObjectValue(const char* key, size_t length) { mDataEncoder.EndObjectValue(key, length); mMetadataEncoder.EndObjectValue(key, length); }

		void Boolean(bool boolValue) { mDataEncoder.Boolean(boolValue); mMetadataEncoder.Boolean(boolValue); }
		void Number(EA::BaseTypeFloat number) { mDataEncoder.Number(number); mMetadataEncoder.Number(number); }
		void String(const char* str, size_t length) { mDataEncoder.String(str, length); mMetadataEncoder.String(str, length); }
		
		void Function(EA::Types::FunctionIn function)
		{
			EA::Types::BaseTypeRef data = mEncoder->EncodeFunction(function);

			EA::Types::BaseTypeDecoder<EA::Types::BaseTypeArrayEncoder>::Decode(&mDataEncoder, data.GetUnsafePointer());

			mMetadataEncoder.String("f", 1);
		}
		
		void UserData(EA::Types::UserDataIn userData)
		{
			EA::Types::BaseTypeRef data = mEncoder->EncodeUserData(userData);

			EA::Types::BaseTypeDecoder<EA::Types::BaseTypeArrayEncoder>::Decode(&mDataEncoder, data.GetUnsafePointer());

			mMetadataEncoder.String("u", 1);
		}
		
		void Null() { mDataEncoder.Null(); mMetadataEncoder.Null(); }

	private:

		Encoder*						mEncoder;
		EA::Types::BaseTypeArrayEncoder	mDataEncoder;
		EA::Types::BaseTypeArrayEncoder	mMetadataEncoder;
	};

	/*!
	*	@struct PrimitiveDecoderParams
	*	@brief A structure containing data on what to decode
	*/
	template<typename Decoder>
	struct PrimitiveDecoderParams
	{
		/*!
		*	ProxyDecoderParams Constructor
		*	@param	proxy		The message proxy we want to decode the data from
		*	@param	source		The name of the source message proxy the data originated from
		*	@param	data		An array holding the data we need to decode
		*	@param	metadata	An array holding the metadata we need to decode
		*/
		PrimitiveDecoderParams(Decoder* decoder, EA::Types::Array* data, EA::Types::Array* metadata):
			mDecoder(decoder),
			mData(data),
			mMetadata(metadata)
		{
		}

		Decoder*				mDecoder;
		EA::Types::Array*		mData;
		EA::Types::Array*		mMetadata;
	};

	/*! @class ProxyDecoder proxyencoder.h	"ux_base/proxyencoder.h"
	*	@brief template class for a ProxyDecoder
	*	@tparam	Encoder	The encoder type
	*/
	template<typename Decoder, typename Encoder>
	class PrimitiveDecoder
	{
	public:

		static void Decode(void* e, void* p)
		{
			Encoder* encoder = static_cast<Encoder*>(e);
			PrimitiveDecoderParams<Decoder>* params = static_cast<PrimitiveDecoderParams<Decoder>* >(p);
		
			EA::Types::Array::const_iterator it = params->mData->begin();
			EA::Types::Array::const_iterator mIt = params->mMetadata->begin();
			EA::Types::Array::const_iterator end = params->mData->end();

			for(; it != end; it++, mIt++)
			{
				Decode(encoder, params->mDecoder, *it, *mIt);
			}
		}

	private:

		static void Decode(Encoder* encoder, Decoder* decoder, EA::Types::BaseTypeIn data, EA::Types::BaseTypeIn metadata)
		{
			if(metadata == NULL)
			{
				EA::Types::BaseTypeDecoder<Encoder>::Decode(encoder, data.GetUnsafePointer());
			}
			else
			{
				switch(metadata->GetType())
				{
					case EA::Types::STRING_TYPE:
						{
							const EA::String& str = metadata->AsEAString();

							if(str == "f")
							{
								encoder->Function(decoder->DecodeFunction(data));
							}
							else if(str == "u")
							{
								encoder->UserData(decoder->DecodeUserData(data));
							}
							else
							{
								EA_FAIL_MESSAGE("Unrecognized metadata string!");
								encoder->Null();
							}
						}
						break;
					case EA::Types::ARRAY_TYPE:
						{
							EA::Types::ArrayRef dataArray = data->AsArray();
							EA::Types::ArrayRef metadataArray = metadata->AsArray();

							EA::Types::Array::const_iterator it = dataArray->begin();
							EA::Types::Array::const_iterator mIt = metadataArray->begin();
							EA::Types::Array::const_iterator end = dataArray->end();

							encoder->BeginArray();
							size_t index = 0;
							for(; it != end; ++it, ++mIt, index++)
							{
								encoder->BeginArrayValue(index);
								Decode(encoder, decoder, *it, *mIt);
								encoder->EndArrayValue(index);
							}
							encoder->EndArray();
						}
						break;
					case EA::Types::OBJECT_TYPE:
						{
							EA::Types::ObjectRef dataObject = data->AsObject();
							EA::Types::ObjectRef metadataObject = metadata->AsObject();

							EA::Types::Object::const_iterator it = dataObject->begin();
							EA::Types::Object::const_iterator end = dataObject->end();

							encoder->BeginObject();
							for(; it != end; ++it)
							{
								encoder->BeginObjectValue((*it).first.c_str(), (*it).first.length());
								Decode(encoder, decoder, (*it).second, (*metadataObject)[(*it).first]);
								encoder->EndObjectValue((*it).first.c_str(), (*it).first.length());
							}
							encoder->EndObject();
						}
						break;
					default:
						EA_FAIL_MESSAGE("Unrecognized metadata type!");
						break;
				}
			}
		}
	};
}
}

#endif /* EABASETYPESMESSAGE_PROXYENCODER_H_ */
