// NOTE: If FileTalk is used - then users MUST invoke FileTalk::ReleaseInstance before they deallocate their GameTalkInstance
// otherwise FileTalk will fail badly.  Since FileTalk's singular dependency is on GameTalk it is essential that GameTalk
// be properly initialized and functioning

#ifndef EA_FILETALK_FILETALKMANAGER_H
#define EA_FILETALK_FILETALKMANAGER_H

#include <GameTalk/GameTalkInstance.h>

// I'd rather not pollute this scope with GameTalk's namespace - so I won't use the following:
//using namespace EA::GameTalk;

namespace EA
{
	namespace FileTalk
	{
		struct FileInfo;
		class FileTalkHandler;
		class Transaction;
		class FileInfoTransaction;
		class GetFileTransaction;
		class SendFileTransaction;
		class FileNotificationTransaction;
		class IHeapAccessor;
		class TransactionList;

		// message handler function prototype
		typedef void FileTalkHandlerFnc(Transaction* message);

		// This is the entry point for ALL users of the FileTalk API.  They will use this class to initialize all things
		// FileTalk and subsequently to send FileTalk requests to the FileTalkServer
		class FileTalkManager
		{
		public:
			/// Create the singleton instance with a network protocol.
			static void CreateInstance(EA::GameTalk::GameTalkInstance* gameTalkInstance, FileTalkHandlerFnc* handlerFunc,
				uint32_t maxNumTransactions = 10, uint32_t sendFragmentSize = 1024, uint32_t receiveFragmentSize = 1024,
				EA::GameTalk::GameTalkHandlerFnc* gameTalkHandlerFunc = NULL);
			/// Get the singleton instance.
			static FileTalkManager* GetInstance();
			/// Release the singleton instance.
			static void ReleaseInstance();

			/// Send a message.
			static void ReceiveMessage(EA::GameTalk::GameTalkMessage * message);

			// Used by user to "drive" how many cycles FileTalk gets to do what it does behind the scenes
			bool Update ();

			// Only transactions should ever invoke this function - this function will 
			// automatically be called when you delete your transaction
			void DeregisterTransaction(Transaction * transaction);

			// Used to set the file root for all future file paths submitted to FileTalkServer
			bool SetFileRootForClient(const char * client, const char * fileRoot);

			// Used to remove any file root that has been set for the client name passed into this function
			bool RemoveFileRootForClient(const char * client);

			// This tells the FileTalkServer that this FileTalkManager is okay with whatever fileroot has been set for it (possibly by a different client)
			bool AcceptTentativeFileRoot();

			// This is the default configuration message forwarding mechanism for FileTalk.  If the user doesn't provide 
			// their own - then this will simply "consent" to any configuration changes and forward them to the FileTalkServer
			static void ForwardConfigMessage (EA::GameTalk::GameTalkMessage * message);

			// Returns the number of transactions in the transaction pool
			size_t GetNumOfTransactions();

			// A convenience getter to allow users to get the particular GameTalkInstance that was
			// used to initialize the FileTalkManager
			EA::GameTalk::GameTalkInstance* GetGameTalkInstance()
			{
				return mGameTalkInstance;
			}
			
			// The following are a listing of the various transactions you can request from FileTalkManager:

			// Get a FileInfoTransaction which sends a request for the file information on the files contained in a
			// particular directory or for a specific file
			FileInfoTransaction * CreateFileInfoTransaction(const char * path, void * userData);

			// IHeapAccessor parameter is provided so that different files can be loaded into different memory pools
			GetFileTransaction * CreateGetFileTransaction(const char * path, void * userData, EA::FileTalk::IHeapAccessor * iAllocator, size_t fileIndex, size_t numBytes);

			// IHeapAccessor parameter is provided so that different files can be loaded into different memory pools
			GetFileTransaction * CreateGetFileTransaction(const char * path, void * userData, EA::FileTalk::IHeapAccessor * iAllocator);

			// If the user doesn't provide the number of fragments for the file - we'll assume they don't want it
			// fragmented - thus numFragments is one
			SendFileTransaction * CreateSendFileTransaction(void * fileData, size_t fileSize, const char * filepath, void * userData, bool overwriteFile = true);

			// Users can create file notification's - so they can listen for file events on the FileTalkServer's system
			FileNotificationTransaction * CreateFileNotificationTransaction(void * userData, const char * filepath, const char * filter,
				uint32_t notifyFilterFlag, uint32_t eventsFlag, bool includeSubdirectories = false);

			USE_GAMETALK_ALLOC("FileTalk::FileTalkManager")
		protected:
			// Not sure that we expect anyone to inherit from FileTalkManager, but just in case mark this as virtual
			virtual ~FileTalkManager();

		private:
			FileTalkManager();
			explicit FileTalkManager(EA::GameTalk::GameTalkInstance* gameTalkInstance, FileTalkHandlerFnc* handlerFunc,
				uint32_t maxNumTransactions, uint32_t sendFragmentSize, uint32_t receiveFragmentSize, 
				EA::GameTalk::GameTalkHandlerFnc* gameTalkHandlerFunc);
			FileTalkManager(const FileTalkManager& fileTalkManager);
			FileTalkManager & operator=(const FileTalkManager & fileTalkManager);

			// This is used by FileTalkManager to register transactions it has created
			bool RegisterTransaction(Transaction * transaction);

			// Singleton
			static FileTalkManager* sInstance;

			// Currently just a sorted list of Transaction pointers
			// May also want to put these into a quick indexing container like a hashmap
			TransactionList * mTransactions;

			// FileTalkManager needs GameTalkInstance to register a callback and hand off its GameTalkInstance pointer
			// to transactions it creates
			EA::GameTalk::GameTalkInstance* mGameTalkInstance;

			FileTalkHandlerFnc* mFileTalkHandlerFunc;

			size_t mSendFragmentSize;
			size_t mReceiveFragmentSize;

			EA::GameTalk::GameTalkHandlerFnc* mFileTalkConfigCallback;
		};

		// Abstract class that is extended by all FileTalk transactions
		class Transaction
		{
		public:
			enum ETransactionType
			{
				FileInfoTransaction = 0,
				GetFileTransaction = 1,
				SendFileTransaction = 2,
				FileNotificationTransaction = 3,
			};

			// User's of the FileTalk API can poll this method to determine whether this transaction has completed - or
			// simply rely on the FileTalk callback to be immediately notified upon completion
			bool IsTransactionComplete() const;

			// Since most users of FileTalk will have RTTI disabled - use this to determine Transaction type you have
			virtual ETransactionType TransactionType (void) const = 0;

			// Get user data that user created this transaction with
			// It is a pointer reference so that they can assign it to null and know it is no longer valid
			void *& GetUserData();

			// All transactions can potentially return/have an error - this will return the error message if there was one
			const char * GetErrorMsg();

			// All transactions can potentially return/have an error - this will return the hashcode of the message if there was one
			int GetErrorCode();

			// Indicates whether an error occurred in the processing of this transaction
			bool ErrorOccurred();

			// For debugging
			char * GetTransactionID ()
			{
				return mChannelId;
			}

			friend class FileTalkManager;
			friend class TransactionList;

			USE_GAMETALK_ALLOC("FileTalk::Transaction")
		protected:
			Transaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, FileTalkHandlerFnc* handlerFunc,
				void * userData);

			virtual ~Transaction();

			// Every transaction will register a ReceiveMessage callback with GameTalkInstance
			virtual void ReceiveMessage(EA::GameTalk::GameTalkMessage * message) = 0;

			// All subclasses can use this method to set their status to "complete".  This will update the internal flag
			// so that polling users will see the transaction is complete and will also invoke their callback (assuming
			// they provided one).
			void TransactionIsComplete();

			// Subclasses can implement this if they choose - useful if transaction object will need more than one "cycle"
			// to do their work
			virtual bool Update()
			{
				return true;
			};

			// If the FileTalkServer encounters an error while processing this transaction - it will be processed through this
			void ProcessError(EA::GameTalk::GameTalkMessage * message);

			// Send the transaction's data (or at least the first bit of it) to the GameTalkServer
			virtual bool SendTransaction() = 0;

			// Channel ID that uniquely identifies this transaction on this target
			char * mChannelId;

			// Each transaction has a pointer to the 
			FileTalkHandler* mFileTalkMessageHandler;
			void * mUserData;

			// Individual transactions need GameTalkInstance to send GameTalkMessages
			EA::GameTalk::GameTalkInstance* mGameTalkInstance;

			bool mIsTransactionComplete;
			int mErrorCode;
			const char * mErrorMsg;
			bool mErrorOccurred;

		private:
			Transaction(const Transaction & transaction);
			Transaction& operator=(const Transaction & transaction);
			
			// A transaction is instantiated, registered in the transaction pool and then it has its channel id set
			void SetChannelId(uint32_t transactionUID);
		};

		class FileInfoTransaction : public EA::FileTalk::Transaction
		{
		public:
			virtual ~FileInfoTransaction();

			// Since most users of FileTalk will have RTTI disabled - use this to determine Transaction type you have
			virtual Transaction::ETransactionType TransactionType (void) const;

			size_t GetNumberOfFileInfos() const;

			// The fileIndex parameter is used to get the file information on a specific element in the returned file listing

			// Get the filename at specified index
			const char * GetFileName(size_t fileIndex) const;

			// Get file size at specified index
			uint64_t GetFileSize(size_t fileIndex) const;

			// Get whether the file info at the specified index is a file or directory (false means it is a file)
			bool IsDirectory (size_t fileIndex) const;

			// Get whether this file/dir is read-only
			bool ReadOnly (size_t fileIndex) const;

			// Get date the file/dir at specified index was created
			uint64_t DateCreated (size_t fileIndex) const;

			// Get date the file/dir at specified index was last modified
			uint64_t DateLastModified (size_t fileIndex) const;

			// Get date the file/dir at specified index was last accessed
			uint64_t DateLastAccessed (size_t fileIndex) const;

			friend class FileTalkManager;

			USE_GAMETALK_ALLOC("FileTalk::FileInfoTransaction")
		protected:
			// This can be protected since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			virtual void ReceiveMessage(EA::GameTalk::GameTalkMessage * message);

			// Send the request for the FileInfo to the FileTalkServer
			virtual bool SendTransaction();

		private:
			FileInfoTransaction(const FileInfoTransaction & fileInfoTransaction);
			FileInfoTransaction& operator=(const FileInfoTransaction & fileInfoTransaction);

			// Used to determine bit position of meta-data in bit field FileInfo::mFileFlags
			enum FileFlags
			{
				IsDirectoryFlag = 1,
				ReadOnlyFlag = 2,
				BlahFlag = 4,
				Blah2Flag = 8,
			};

			// This can be private since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			FileInfoTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance,
				const char * path, FileTalkHandlerFnc* handlerFunc, void * userData);

			size_t mFileInfoArraySize;
			FileInfo * mFileInfoArray;
			char * mCharData;
			char * mFilePath;
		};

		// Used to get a file downloaded from the FileTalkServer
		class GetFileTransaction : public EA::FileTalk::Transaction
		{
		public:
			virtual ~GetFileTransaction();

			// Since most users of FileTalk will have RTTI disabled - use this to determine Transaction type you have
			virtual Transaction::ETransactionType TransactionType (void) const;

			// Returns the bytes that make up a file (assuming the file is downloaded).
			// NOTE: If a user uses this function the transaction object in use will no longer be responsible for freeing
			// the memory in the byte array returned - thus ownership of that byte array is transferred.
			uint8_t * GetFile();
			// Returns the number of bytes contained in the file's byte array
			size_t GetFileSize() const;
			// Get the filepath (on the FileTalkServer's file-system) of the file sought in this GetFileTransaction
			const char * GetFilePath();

			friend class FileTalkManager;

			USE_GAMETALK_ALLOC("FileTalk::GetFileTransaction")
		protected:
			// This can be protected since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			virtual void ReceiveMessage(EA::GameTalk::GameTalkMessage * message);

			// Send the request for the file to the FileTalkServer
			virtual bool SendTransaction();

		private:
			GetFileTransaction(const GetFileTransaction & transaction);
			GetFileTransaction& operator=(const GetFileTransaction & transaction);

			// This can be private since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			GetFileTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance,
				const char * path, FileTalkHandlerFnc* handlerFunc, void * userData, const size_t fragmentSize,
				EA::FileTalk::IHeapAccessor * iAllocator, size_t fileIndex, size_t numBytes);

			size_t mByteArrayIndex;
			size_t mByteArraySize;
			char * mFilePath;
			uint8_t * mByteArray;
			const size_t mFragmentSize;
			EA::FileTalk::IHeapAccessor * mAllocator;
			size_t mFileIndex;
			size_t mNumBytesRequested;
			bool mUserHasTakenOwnership;
		};

		class SendFileTransaction : public EA::FileTalk::Transaction
		{
		public:
			virtual ~SendFileTransaction();

			// Since most users of FileTalk will have RTTI disabled - use this to determine Transaction type you have
			virtual Transaction::ETransactionType TransactionType (void) const;

			// Get the filepath of where this file will or was sent in this SendFileTransaction
			const char * GetFilePath();

			friend class FileTalkManager;

			USE_GAMETALK_ALLOC("FileTalk::SendFileTransaction")
		protected:
			// This can be protected since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			virtual void ReceiveMessage(EA::GameTalk::GameTalkMessage * message);

			// Used by the FileTalkManager to allow this transaction a cycle to fire off the next fragment bytes that make
			// up the file
			virtual bool Update();

			// Send the file meta-data (for the file this transaction will send) to the FileTalkServer
			virtual bool SendTransaction();

		private:
			SendFileTransaction(const SendFileTransaction & transaction);
			SendFileTransaction& operator=(const SendFileTransaction & transaction);

			// This can be private since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			SendFileTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, void * fileData,
				size_t fileSize, const size_t fragmentSize, const char * filePath, FileTalkHandlerFnc* handlerFunc,
				void * userData, bool overwriteFile);

			size_t mFilePathLength;
			size_t mByteArrayIndex;
			size_t mByteArraySize;
			size_t mFragmentSize;
			size_t mNumFragments;
			size_t mFragmentIndex;
			uint8_t * mByteArray;
			char * mFilePath;
			bool mOverwriteFile;
		};

		class FileNotificationTransaction : public EA::FileTalk::Transaction
		{
		public:
			// Using the same values as Microsoft (bit position of meta-data in bit field)
			enum NotifyFilterFlags
			{
				FileName = 1,
				DirectoryName = 2,
				Attributes = 4,
				Size = 8,
				LastWrite = 16,
				LastAccess = 32,
				CreationTime =64,
				Security = 256,
			};

			// List of possible event handlers that can be registered with the FileTalkServer
			// The INVALID entry exists for initializing the EventHandler member
			// EventHandler is singular because FileNotificationTransaction contains an
			// EventHandler member not an EventHandlers member
			enum EventHandler
			{
				INVALID = 0,
				Changed = 1,
				Created = 2,
				Deleted = 4,
				Renamed = 8
			};

			// This returns the full path of the file/directory that was affected
			char * GetFilePath() const;
			// This returns the "short name" of the file/directory that was affected
			char * GetName() const;
			// In the case of a rename - this returns the previous full path of the file/directory that was affected
			char * GetOldFilePath() const;
			// In the case of a rename - this returns the previous "short name" of the file/directory that was affected
			char * GetOldName() const;
			// This returns which event-handler was responsible for populating this transaction
			EventHandler GetEventHandler() const;

			// User must explicitly call this to continue to receive new events in a particular instance of FileNotificationTransaction
			void Reenable();

			virtual ~FileNotificationTransaction();

			// Since most users of FileTalk will have RTTI disabled - use this to determine Transaction type you have
			virtual Transaction::ETransactionType TransactionType (void) const;

			friend class FileTalkManager;

			USE_GAMETALK_ALLOC("FileTalk::FileNotificationTransaction")
		protected:
			// This can be protected since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			virtual void ReceiveMessage(EA::GameTalk::GameTalkMessage * message);

			// Send a request for a file notification for a particular file or set of files to the FileTalkServer
			virtual bool SendTransaction();

		private:
			FileNotificationTransaction(const FileNotificationTransaction & transaction);
			FileNotificationTransaction& operator=(const FileNotificationTransaction & transaction);

			// This can be private since FileTalkManager is the only one that uses this and FileTalkManager is our friend
			FileNotificationTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance,
				FileTalkHandlerFnc* handlerFunc, void * userData, const char * filepath, const char * filter,
				uint32_t notifyFilterFlag, uint32_t eventsFlag, bool includeSubdirectories);

			// If a user deletes this transaction - the FileNotificationTransaction will invoke this method from its
			// destructor to tell the FileTalkServer to stop listening to whatever this transaction had told it to listen for
			void DeregisterListener() const;

			// Frees up the memory used by all members of this class
			void ClearCurrentMembers();

			char * mFilePath;
			char * mName;
			char * mOldFilePath;
			char * mOldName;
			EventHandler mEventHandler;

			char * mFilter;
			uint32_t mNotifyFilterFlag;
			uint32_t mEventsFlag;
			bool mIncludeSubdirectories;
		};

		// This interface only exposes what FileTalk knows how to provide - size and the pointer on the alloc and free respectively
		class IHeapAccessor
		{
		public:

			/// Virtual destructor is required so if the allocator is deleted
			/// as the base type, the derived class' destructor is called.
			virtual ~IHeapAccessor() {}

			virtual void *Alloc(size_t size) = 0;

			virtual void Free(void *block) = 0;
		};
	}
}
#endif

