// --- Include guard
#ifndef EA_GAMETALK_GAMETALKINSTANCE_H
#define EA_GAMETALK_GAMETALKINSTANCE_H

#include <GameTalk/GameTalkBase.h>
#include <GameTalk/GameTalkMessage.h>
#include <GameTalk/IGameTalkProtocol.h>

namespace EA
{
	namespace GameTalk
	{
		class GameTalkInstance : public IConnection
		{
		public:
			explicit GameTalkInstance(IGameTalkProtocol* protocol, uint32_t maxHandlersCount, const char * targetName, bool freeGameTalkProtocol = false, bool reconnect=true, uint32_t receiveStrBufferSize = GAMETALK_RECEIVE_STRING_BUFFER_SIZE);

			virtual ~GameTalkInstance();

			/// Create a GameTalkMessage that associates this GameTalkInstance with it (GameTalkInstance can be used as a GameTalkMessage factory)
			virtual IMessage* CreateMessage();

			/// This uses "placement new" - therefore, user must ensure that they explicitly invoke the returned IMessage's destructor
			/// in addition to releasing the tempBuffer memory (if it is was created using a dynamic allocation)
			virtual IMessage* CreateMessage(uint8_t *tempBuffer, size_t tempBufferSize);

			/// Is the game connected to the pc server
			bool IsConnected() const;

			/// Non-static implementation for sending a GameTalkMessage (the static GameTalkManager::SendMessage invokes this)
			bool SendMessage(const char* target, GameTalkMessage* message);

			virtual bool Send(const char* target, const char *channel, const IMessage * message);
			virtual bool SendSynchronous(const char * target, const char *channel, const IMessage * message, IMessage *reply, ITimer* timer);

			/// Poll the network for messages
			int Poll();

			/// User can specify if the Poll should block until at least one GameTalkMessage has been pulled from the socket (and thus the user's callback has been invoked)
			int Poll(bool blocking);

			/// Register a message handler.
			bool RegisterMessageHandler(GameTalkHandlerFnc* handlerFunc, const char* filter);
			/// Register a message handler - with user data.
			bool RegisterMessageHandler(UserGameTalkHandlerFnc* handlerFunc, const char* filter, void * userData);
			/// Unregister a message handler.
			void UnregisterMessageHandler(GameTalkHandlerFnc* handlerFunc);
			/// Unregister a user data message handler - since users may use the same callback for different channel registration - allow them to unregister by also specifying the channel
			void UnregisterMessageHandler(UserGameTalkHandlerFnc* handlerFunc, const char * channelId = NULL);

			bool RegisterSynchroMessageHandler(SyncGameTalkHandlerFnc* syncGameTalkHandler, const char* filter, void * userData);
			void UnregisterSynchroMessageHandler(SyncGameTalkHandlerFnc* syncGameTalkHandler, const char * channelId);
			bool InvokeMatchingSynchroMsgHandler(GameTalkMessage* gtm, SyncReplyGameTalkMessage* syncReplyMessage, const char* userChannel);

			/// Check if a message ID matches with a message filter
			static bool IsMessageMatching(const char* messageId, const char* messageFilter);

			/// Register a message handler 
			///   channelFilter - describes a pattern for matching received message channels against (todo: document filter pattern)
			///   userData      - void * that can be used to store user context
			virtual bool            RegisterMessageHandler(MessageHandler handlerFunc, const char* channelFilter, void * userData);

			/// Unregister a message handler
			virtual void            UnregisterMessageHandler(MessageHandler handlerFunc, const char * channelFilter);

			/// Get target name
			const char * TargetName ();

			/// This sends a synchronous message - i.e. this method will block until message is received
			/// This can only be called by one thread at a time (like all parts of GameTalkInstance)
			GameTalkMessage * SendSynchronousMessage(const char * target, GameTalkMessage * gameTalkMessage, ITimer* timer);

			/// This tells the GameTalkServer that you're finished with the connection and that it is free to close its socket connection
			bool SendShutdown();

			/// This allows the user to update their target name both locally and in the GameTalkServer
			/// This functionality relies on the user's GameTalkServer being 2.8.0 or higher
			bool UpdateTargetName(const char * targetName);

			/// Using this allows us to poll the GameTalkServer and thus get a valid return value from
			/// the IsConnected property
			bool SendIsAlivePing();

			USE_GAMETALK_ALLOC("GameTalk::GameTalkInstance")

				friend class GameTalkManager;
		protected:
			void AssignTargetName(const char * targetName);

			bool SendInitializePacket();

			/// Receive a message from the network
			void ReceiveMessage(GameTalkMessage* message);
			void ReceiverCallback(void* buffer, unsigned int sizeInBytes);

			// We use this to send the GameTalkServer a backward compatible message (on connection)
			// If the GameTalkServer is an older version that doesn't understand the message - it should just ignore it
			bool SendKeywordMessage(const char * const keywordKeyStr, const char * keywordValueStr);

			// This is used to register or unregister server side channels for a particular client
			bool SendServerChannel(const char * filter, bool registerChannel);

			bool SendRegisteredChannelsToServer();

			void ConfigHandler(GameTalkMessage* message);

			void ParseCreateInvokeCallbackAndDelete(char* buffer, char* endOfBuffer);

			// this function is called to delete a message that was created with CreateMessage()
			void    DeleteMessage(GameTalkMessage *message);

			IGameTalkProtocol* mGameTalkProtocol;

			GameTalkHandler** mRegisteredMessageHandlers;
			uint32_t mRegisteredMessageHandlersCount;
			const uint32_t mMaxHandlersCount;

			char * mTargetName;

			static const char * mVersion;

			bool mFreeGameTalkProtocol;

			// Dynamically allocated buffer of KeyContents that is used to create GameTalkMessages
			KeyContentBuffer * mKeyContentBuffer;

			uint32_t mSyncMsgUidCounter;

			bool mReconnect;

			bool mReceivedMsg;

			uint32_t mReceiveStrBufferSize;

			SynchronousTransaction* mSynchroMsgTable [USER_GAMETALK_MAX_PENDING_SYNCHRO_TRANSACTIONS];
			
			bool mSendInitInfoToServer; // Flag that we still need to send our initialization information (target-name, platform, and version) to the GameTalkServer
		private:
			GameTalkInstance();
			GameTalkInstance(const GameTalkInstance& GameTalkInstance);
			GameTalkInstance & operator=(const GameTalkInstance & GameTalkInstance);

			// Initialize the KeyContent buffer
			void InitializeKCBuffer();
			// Release the dynamic memory in the KeyContent buffer
			void ReleaseKCBuffer();

			/// The callback used when a synchronous message reply is received
			static void SynchronousMessageResponseHandler (EA::GameTalk::GameTalkMessage * message, void *& userData);

			/// The callback when a synchronous message is received
			static void HandleSynchronousMessages(GameTalkMessage * gameTalkMessage, void *& userData);

			uint32_t GenerateSynchronousMessageChannel(char * synchronousMsgChannelBuffer, const char * userChannelId);

			friend class GameTalkMessage;
		};

		class GameTalkManager
		{
		public:
			/// Create the singleton instance with a network protocol
			/// Optionals:
			/// 1. max number of callbacks you will register (defaults to 10)
			/// 2. non-default target name (if left blank, then the name will be based on your platform - exp game.xenon)
			static void CreateInstance(IGameTalkProtocol* protocol, uint32_t maxHandlersCount = 10, const char * targetName = NULL, bool reconnect=true, uint32_t receiveStrBufferSize = GAMETALK_RECEIVE_STRING_BUFFER_SIZE);
			/// Get the singleton instance.
			static GameTalkManager* GetInstance();
			/// Release the singleton instance.
			static void ReleaseInstance(bool freeGameTalkProtocol=false);

			/// Is the game connected to the pc server
			bool IsConnected() const;

			/// Send a message - it'd be great if we could make the following non-static without breaking lots of user code
			static bool SendMessage(const char* target, GameTalkMessage* message);

			/// Poll the network for messages - it'd be great if we could make the following non-static without breaking lots of user code
			static int Poll();

			/// GameTalkManager can adopt an instance of GameTalkInstance
			static void AdoptInstance(GameTalkInstance * gameTalkInstance);

			static GameTalkInstance * GetAdoptedInstance();

			// Non static members that would be inherited from GameTalkInstance (if we extended GameTalkInstance)

			/// Register a message handler.
			bool RegisterMessageHandler(GameTalkHandlerFnc* handlerFunc, const char* filter);
			/// Register a message handler.
			bool RegisterMessageHandler(UserGameTalkHandlerFnc* handlerFunc, const char* filter, void * userData);
			/// Unregister a message handler.
			void UnregisterMessageHandler(GameTalkHandlerFnc* handlerFunc);
			/// Unregister a message handler.
			void UnregisterMessageHandler(UserGameTalkHandlerFnc* handlerFunc);

			/// Check if a message ID matches with a message filter
			static bool IsMessageMatching(const char* messageId, const char* messageFilter);

			/// Get target name
			const char * TargetName ();

			USE_GAMETALK_ALLOC("GameTalk::GameTalkManager")
		protected:
			virtual ~GameTalkManager();

		private:
			GameTalkManager();
			GameTalkManager(const GameTalkManager& gameTalkManager);
			GameTalkManager & operator=(const GameTalkManager & gameTalkManager);

			explicit GameTalkManager(IGameTalkProtocol* protocol, uint32_t maxHandlersCount, const char * targetName, bool reconnect=true, uint32_t receiveStrBufferSize = GAMETALK_RECEIVE_STRING_BUFFER_SIZE);

			static GameTalkManager* sInstance;

			bool mFreeGameTalkProtocol;

			// This is a hack for users that don't want to implement proper TLS support
			static GameTalkInstance * sGameTalkInstance;

			static void AssignGameTalkInstanceToStaticMember(GameTalkInstance * gameTalkInstance)
			{
				// If we are resetting sGameTalkInstance to NULL then it is okay for the sGameTalkInstance to be non-null
				// Basically - we're trying to prevent the situation of this function getting a non-null GameTalkInstance and assigning that to a non-null sGameTalkInstance
				USER_GAMETALK_ASSERT((sGameTalkInstance == NULL) || (gameTalkInstance == NULL), "You haven't correctly implemented the Thread Local Storage macros!  You cannot use the GameTalkManager with multiple GameTalkInstances until that is rectified.");
				sGameTalkInstance = gameTalkInstance;
			}
		};
	}
}


#endif // EA_GAMETALK_GAMETALKINSTANCE_H



