// --- Include guard
#ifndef EA_GAMETALK_GAMETALKMESSAGE_H
#define EA_GAMETALK_GAMETALKMESSAGE_H

#include <GameTalk/GameTalkBase.h>


namespace EA
{
	namespace GameTalk
	{
		class GameTalkInstance;
		class GameTalkMessage;
		class SyncReplyGameTalkMessage;
		class GameTalkAllocator;

		// message handler function prototypes
		typedef void GameTalkHandlerFnc(GameTalkMessage* message);
		typedef void UserGameTalkHandlerFnc(GameTalkMessage* message, void *& userData);
		typedef void SyncGameTalkHandlerFnc(GameTalkMessage* syncMessage, SyncReplyGameTalkMessage* syncReplyMessage, void *& userData);

		class GAMETALK_API GameTalkMessage : public IMessage
		{
		public:
			// Types in ChunkBlocks
			enum InternalType
			{
				Sender = 64,
				SynchroId,
			};

			GameTalkMessage(const char* channelId, GameTalkAllocator* gameTalkAllocator=NULL);
			GameTalkMessage(const char* channelId, char * replyMsgBuffer, size_t replyMsgBufferSize, GameTalkAllocator* gameTalkAllocator=NULL);
			virtual ~GameTalkMessage();

			static GameTalkMessage* Create(char* buffer, char* endOfBuffer,
				char * stringContentBuffer, uint32_t stringContentBufferSize,
				uint32_t numKeys, GameTalkMessage * message,
				bool allocKeyContentPairs);

			size_t CreateBuffer(const char* target, char*& buffer, const char* channelId=NULL) const;

			inline const char* GetChannelId() const;
			int GetNumberOfKeys() const;
			bool CompareKey(int index, const char* compareTo) const;

			const char* GetKey(int index) const;
			int GetFormat(int index) const;
			bool IsLittleEndian(int index) const;
			const void* GetContent(int index) const;
			size_t GetSize(int index) const;
			// It is possible to have multiple keys with the same name - thus the first key that matches will be returned
			const void* GetContentWithKey(const char* key) const;
			// It is possible to have multiple keys with the same name - thus the first key that matches will be returned
			size_t GetSize(const char* key) const;
			// It is possible to have multiple keys with the same name - thus the first key that matches will be returned
			int GetIndexOfKey(const char* key) const;

			void AddKeyContent(const char* key, const FormatTypes format, const void* content, const size_t size);
			void AddKeyContentAsString(const char* key, const char* stringContent);

			virtual void Add(const char* key, bool value);
			virtual void Add(const char* key, float value);
			virtual void Add(const char* key, double value);
			virtual void Add(const char* key, int8_t value);
			virtual void Add(const char* key, uint8_t value);
			virtual void Add(const char* key, int16_t value);
			virtual void Add(const char* key, uint16_t value);
			virtual void Add(const char* key, int32_t value);
			virtual void Add(const char* key, uint32_t value);
			virtual void Add(const char* key, int64_t value);
			virtual void Add(const char* key, uint64_t value);

			virtual void Add(const char* key, const char *string);
			// Nest a GameTalkMessage in this GameTalkMessage
			void Add(const char* key, const IMessage* content);

			// Add an array of floats to the GameTalkMessage
			void AddArray(const char* key, const float* content, const int count);

			// Add an array of doubles to the GameTalkMessage
			void AddArray(const char* key, const double* content, const int count);

			// Add an array of int8_t's to the GameTalkMessage
			void AddArray(const char* key, const int8_t* content, const int count);

			// Add an array of uint8_t's to the GameTalkMessage
			void AddArray(const char* key, const uint8_t* content, const int count);

			// Add an array of int16_t's to the GameTalkMessage
			void AddArray(const char* key, const int16_t* content, const int count);

			// Add an array of uint16_t's to the GameTalkMessage
			void AddArray(const char* key, const uint16_t* content, const int count);

			// Add an array of int32_t's to the GameTalkMessage
			void AddArray(const char* key, const int32_t* content, const int count);

			// Add an array of uint32_t's to the GameTalkMessage
			void AddArray(const char* key, const uint32_t* content, const int count);

			// Add an array of int64_t's to the GameTalkMessage
			void AddArray(const char* key, const int64_t* content, const int count);

			// Add an array of uint64_t's to the GameTalkMessage
			void AddArray(const char* key, const uint64_t* content, const int count);

			virtual bool Get(int keyIndex, bool &value) const;
			virtual bool Get(int keyIndex, float &value) const;
			virtual bool Get(int keyIndex, double &value) const;
			virtual bool Get(int keyIndex, int8_t &value) const;
			virtual bool Get(int keyIndex, uint8_t &value) const;
			virtual bool Get(int keyIndex, int16_t &value) const;
			virtual bool Get(int keyIndex, uint16_t &value) const;
			virtual bool Get(int keyIndex, int32_t &value) const;
			virtual bool Get(int keyIndex, uint32_t &value) const;
			virtual bool Get(int keyIndex, int64_t &value) const;
			virtual bool Get(int keyIndex, uint64_t &value) const;

			virtual const char *GetString(int keyIndex) const;
			virtual const char *GetString(const char *key) const;
			virtual bool Get(int keyIndex, const char *&string) const;
			virtual bool Get(int index, IMessage* message, char* userBuffer, size_t userBufferSize) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, float* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, double* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, int8_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, uint8_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, int16_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, uint16_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, int32_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, uint32_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, int64_t* outputArray, int startIndex, int count) const;

			// startIndex is the offset to read from the data in the payload of the message and count is the number of elements to read
			bool GetArray(int index, uint64_t* outputArray, int startIndex, int count) const;

			template <typename T>
			bool Get(const char* key, T& value) const
			{
				int index = GetIndexOfKey(key);
				if (index < 0)
					return false;
				return Get(index, value);
			}

			template <typename T>
			bool GetArray(const char* key, T* outputArray, int startIndex, int count) const
			{
				int index = GetIndexOfKey(key);
				if (index < 0)
					return false;
				return GetArray(index, outputArray, count, startIndex);
			}

			// If the payload of this content is an array - this will return its length
			size_t GetArrayLength(int index) const;
			// This gets the size of the type at the specified index
			int GetTypeSize(int index) const;

			virtual int         AddRef();
			virtual int         Release();
			virtual int			GetRefCount();
			virtual IConnection* GetConnection() const;
			virtual bool IsArray(int keyIndex) const;
			virtual void        Clear();
			virtual bool        HasError() const;
			virtual void        ClearError() const;

			const char* GetSender() const;
			void* GetLeftOvers(size_t& leftoverSize) const;
			void* GetChunkData(uint32_t chunkType, size_t& chunkSize) const;
			void AddChunkData(void* data, uint32_t chunkType, size_t chunkSize);

			static const char* sGameTalkServerID;
			static const char* sServerMessageID;
			static const char* sUpdateTargetName;
			static const char* sAddChannelFilter;
			static const char* sRemoveChannelFilter;
			static const char* sSynchronousMessageID;
			static const char* sSynchronousMessageIDPlusSeparator;
			static const char* sSynchroResponseMessageID;
			static const char* sSynchroResponseMessageIDPlusSeparator;
			static const char* sSynchronousGameTalkIssue;
			static const char* sGameTalkServerPingID;

			USE_GAMETALK_ALLOC("GameTalk::GameTalkMessage")

				friend class GameTalkInstance;
		protected:
			// When the user invokes GetFormat - they can actually end up getting the type-code
			bool GetKeyContentMetaData(int index, FormatTypes& format, FormatTypes& typeCode, int& typeSize) const;

			// In case the type-size is not encoded in the key content meta-data - we'll try using the type-code and return the sizeof that type
			static int GetTypeSizeFromTypeCode(int typeCode);

			// We'll assume the userProvidedBuffer is big enough to house the data we're going to dump into it (so user doesn't have to provide the length of their array)
			bool GetPtrTo64Bit(int index, uint64_t* userProvidedBuffer, size_t count, int startIndex) const;

			// We'll assume the userProvidedBuffer is big enough to house the data we're going to dump into it (so user doesn't have to provide the length of their array)
			bool GetPtrTo32Bit(int index, uint32_t* userProvidedBuffer, size_t count, int startIndex) const;

			// We'll assume the userProvidedBuffer is big enough to house the data we're going to dump into it (so user doesn't have to provide the length of their array)
			bool GetPtrTo16Bit(int index, uint16_t* userProvidedBuffer, size_t count, int startIndex) const;

			// We'll assume the userProvidedBuffer is big enough to house the data we're going to dump into it (so user doesn't have to provide the length of their array)
			bool GetPtrTo8Bit(int index, uint8_t* userProvidedBuffer, size_t count, int startIndex) const;

			// This can be used by any type to add their content to the payload of this key/content pair - do we want to make this public so users can extend our serialization system?
			void AddKeyContent(const char* key, int format, const void* content, const size_t size, bool isLittleEndian, int typeCode, size_t typeSize);

			// This can be used by any type to add their content to the payload of this key/content pair - do we want to make this public so users can extend our serialization system?
			void AddBinaryKeyContent(const char* key, int typeCode, const void* content, const size_t size, size_t typeSize);

			// this sets error state
			void                SetError() const;

		private:
			GameTalkMessage();
			GameTalkMessage(const GameTalkMessage & message);
			GameTalkMessage& operator=(const GameTalkMessage & message);

			// Only for use with incoming messages - to ensure zero dynamic memory allocations
			GameTalkMessage(const char* channelId, KeyContent ** keyContentArray,
				uint32_t numKeys, GameTalkAllocator* gameTalkAllocator=NULL);

			KeyContent** AllocateDataBuffer(size_t numKeyContents);

			// Currently no reason to make this member function public
			GameTalkMessage* Clone( char * buffer, size_t bufferSize, GameTalkMessage * messageToClone);

			void CopyTo(GameTalkMessage * destMessage);

			static uint32_t AlignData(char*& buffer);

			static bool IsDataAligned(char*& buffer);

			// If the current array of KeyContent isn't long enough to house one more KeyContent - this will increase the size of the array
			void ResizeKeyContentBuffer();

			static bool ParseGameTalkMessageHeader(char*& buffer, uint32_t& numKeys, char *& channelId);

			size_t GetChannelAndKeyContentSize(const char *channelId) const;

			void SerializeGameTalkMessageContents(char*& dest, const char* channelId=NULL) const;

			static char* ReadString(char*& nextString);
			static char* ReadString(char*& nextString, uint32_t& size);
			static char* ReadBinary(char*& nextBuffer, uint32_t& size);
			static void WriteBuffer(char*& outBuffer, const char* inBuffer, size_t inSize);

			// The following functions are used to alloc/free GameTalkMessage members
			// Could be configured to not use the mGameTalkMessageAllocator (if it isn't guaranteed to be available)
			KeyContent* InstantiateKeyContent(const char * key, FormatTypes format, 
				const void* content, const size_t size, bool littleEndian, 
				FormatTypes typeCode, int typeSize);
			void DeallocateKeyContent(KeyContent* keyContent);
			void* InternalAlloc(size_t size, const char* name) const;
			void InternalFree(void *ptr, const char* name) const;

			static const char* CopyStringIntoBuffer(char*& buffer, char* endOfBuffer, char*& stringContentBuffer, size_t& stringContentBufferIndex, uint32_t stringContentBufferSize, uint32_t& size);
		protected:
			// To the user we want this to be a const, but internally when we
			// build a message received from someone else, we need to allocate it.
			char* mChannelId;

			// Flag for know the message came from the server
			// and we need to free the data allocated.
			bool mIncomingMessage;

			// Array of KeyContent
			KeyContent** mData;
			int mDataCount;

			static int mMaxDataLength;

			// When our callback receives a synchronous message - it gets dumped here until just before our SendSynchronousMessage function returns it
			SynchronousTransaction * mSynchronousTransaction;

			// For reference counting
			int mRefCount;

			// If this GameTalkMessage was constructed using a GameTalkInstance (it will have a reference to the GameTalkInstance that constructed it)
			GameTalkInstance* mOwner;

			// this is true if the message was allocated on the heap, this is false if placement new was used.
			bool              mAllocated;

			GameTalkAllocator* mGameTalkAllocator;
			// Used to indicate whether this GameTalkMessage was the one that allocated the mGameTalkAllocator
			bool mGameTalkAllocatorAlloc;

			const char* mSender;

			// was there an error when deserializing keys?
			mutable bool        mHasError;

			void* mLeftOvers;
			size_t mLeftOversSize;
			bool mAllocChunkBlock;
		};

		const char* GameTalkMessage::GetChannelId() const
		{
			return mChannelId;
		}

		class SyncReplyGameTalkMessage : public GameTalkMessage
		{
			GameTalkInstance * mGameTalkInstance;
			bool mReplied;

		public:
			SyncReplyGameTalkMessage(GameTalkMessage* gameTalkMessage, GameTalkInstance* gameTalkInstance, const char * channelToReplyOn, const char* returnTargetName);

			bool Reply();

			bool Replied()
			{
				return mReplied;
			}

			static bool GetSynchroId(GameTalkMessage* gtm, uint32_t& synchroId);

			USE_GAMETALK_ALLOC("GameTalk::SyncReplyGameTalkMessage")
		private:
			SyncReplyGameTalkMessage(const SyncReplyGameTalkMessage&);
			SyncReplyGameTalkMessage& operator=(const SyncReplyGameTalkMessage&);
		};

		namespace SyncReply
		{
			bool ContainsError(GameTalkMessage* gtm);
		}
	}
}


#endif // EA_GAMETALK_GAMETALKMESSAGE_H


