#include "FileTalk/FileTalk.h"
#include "GameTalk/IGameTalkProtocol.h"

// --- Using

// I'd prefer to keep this scope as "clean" as possible - by keeping using namespaces to a min. - thus we don't "use" GameTalk
using namespace EA::FileTalk;

// --- Variables

FileTalkManager* FileTalkManager::sInstance = NULL;

// --- Classes

namespace EA
{
	namespace FileTalk
	{
		struct FileTalkMessage
		{
			static const char * sFileTalkServerID;

			static const char * sFileTalkConfigChannel;

			// unique FileTalk message ID's
			static const char * sFileInfoRequest;
			static const char * sFileInfoResponse;
			static const char * sGetFileRequest;
			static const char * sGetFileResponse;
			static const char * sSendFileRequest;
			static const char * sSendFileResponse;
			static const char * sFileNotificationRequest;
			static const char * sFileNotificationResponse;

			static const char * sSetClientFileRoot;
			static const char * sRemoveClientFileRoot;
			static const char * sSetTentativeFileRoot;
			static const char * sAcceptTentativeFileRoot;

			static const char * sInitMessage;

			enum MessageID
			{
				FileInfoRequest,
				FileInfoResponse,
				GetFileRequest,
				GetFileResponse,
				SendFileRequest,
				SendFileResponse,
				FileNotificationRequest,
				FileNotificationResponse,
				SetClientFileRoot,
				RemoveClientFileRoot,
				InitMessage,
				SetTentativeFileRoot,
				AcceptTentativeFileRoot,
			};
		};
		const char * FileTalkMessage::sFileTalkServerID = "Service.FileTalk";

		const char * FileTalkMessage::sFileTalkConfigChannel = "FileTalk.Config";

		const char * FileTalkMessage::sFileInfoRequest = "FileInfoRequest";
		const char * FileTalkMessage::sFileInfoResponse = "FileInfoResponse";
		const char * FileTalkMessage::sGetFileRequest = "GetFileRequest";
		const char * FileTalkMessage::sGetFileResponse = "GetFileResponse";
		const char * FileTalkMessage::sSendFileRequest = "SendFileRequest";
		const char * FileTalkMessage::sSendFileResponse = "SendFileResponse";
		const char * FileTalkMessage::sFileNotificationRequest = "FileNotificationRequest";
		const char * FileTalkMessage::sFileNotificationResponse = "FileNotificationResponse";

		const char * FileTalkMessage::sSetClientFileRoot = "SetClientFileRoot";
		const char * FileTalkMessage::sRemoveClientFileRoot = "RemoveClientFileRoot";
		const char * FileTalkMessage::sSetTentativeFileRoot = "SetTentativeFileRoot";
		const char * FileTalkMessage::sAcceptTentativeFileRoot = "AcceptTentativeFileRoot";

		const char * FileTalkMessage::sInitMessage = "InitMessage";
		// May want to adapt FileInfo to be "more like" the following FileInfo at some point:
		/*struct FileInfo
		{
		char * mFileName;
		size_t mFileSize;
		bool mFileWriteable;
		// If it isn't a file, then it is a directory
		bool mIsFile;
		uint64_t mFileCreatedDateTime;
		uint64_t mFileModifiedDateTime;

		FileInfo(): mFileName(NULL), mFileSize(0),mFileWriteable(false),mIsFile(true),mFileCreatedDateTime(0), 
		mFileModifiedDateTime(0)
		{
		}
		};*/
		struct FileInfo
		{
			uint64_t mFileNameOffset;
			uint64_t mFileSize;
			uint64_t mFileFlags;
			uint64_t mFileCreatedDateTime;
			uint64_t mFileModifiedDateTime;
			uint64_t mFileAccessTime;
		};

		// --- FileTalkHandler
		class FileTalkHandler
		{
		public:
			FileTalkHandler(FileTalkHandlerFnc* fileTalkHandlerFnc):
				  mFileTalkHandlerFnc(fileTalkHandlerFnc)
			  {}
			  ~FileTalkHandler() {};

			  FileTalkHandlerFnc* GetHandlerFnc() const { return mFileTalkHandlerFnc; }

			  void Invoke(Transaction* transaction)
			  {
				  if (mFileTalkHandlerFnc)
				  {
					  (*mFileTalkHandlerFnc)(transaction);
				  }
			  }

			  USE_GAMETALK_ALLOC("FileTalk::FileTalkHandler")

		private:
			FileTalkHandler();
			FileTalkHandlerFnc* mFileTalkHandlerFnc;
		};

		class TransactionList;

		class TransactionNode
		{
		public:
			TransactionNode(Transaction * transaction, uint32_t transactionUid):
				mPreviousNode(NULL),
				mNextNode(NULL),
				mTransaction(transaction),
				mTransactionUid(transactionUid)
			{
			}

			~TransactionNode()
			{
				// Note: we do not delete the transaction that we point to - that is up to the user
			}

			TransactionNode * NextNode()
			{
				return mNextNode;
			}

			Transaction * GetTransaction()
			{
				return mTransaction;
			}

			friend class TransactionList;

			USE_GAMETALK_ALLOC("FileTalk::TransactionNode")
		private:
			TransactionNode * mPreviousNode;
			TransactionNode * mNextNode;
			Transaction * mTransaction;

			uint32_t mTransactionUid;
		};

		class TransactionList
		{
		public:
			TransactionList (uint32_t maxNumTransactions = ~0U)
				:mHeadNode(NULL),
				mEndNode(NULL),
				mTransactionUidCounter(0),
				mNumOfTransactions(0),
				mMaxNumTransactions(maxNumTransactions)
			{
				// If desired we could create a pool of TransactionNode pointers up front to avoid mem. fragmentation
			}

			~TransactionList()
			{
				TransactionNode * iteratorNode = mHeadNode;
				while (iteratorNode != NULL)
				{
					TransactionNode * nextNode = iteratorNode->NextNode();
					if (iteratorNode->mTransaction)
					{
						// We get our own local pointer to tempTransaction because the deletion of the Transaction
						// will remove the current iteratorNode from the linked list of TransactionNodes
						Transaction * tempTransaction = iteratorNode->mTransaction;
						delete tempTransaction;
						tempTransaction = NULL;
					}
					iteratorNode = nextNode;
				}
			}

			uint32_t AddTransaction (Transaction * transaction)
			{
				USER_GAMETALK_ASSERT(mNumOfTransactions < mMaxNumTransactions, "There is no more room for transactions in the transaction pool.");

				uint32_t transactionUid = mTransactionUidCounter;
				TransactionNode * transactionNode = new TransactionNode(transaction, transactionUid);
				if ((mHeadNode == NULL) && (mEndNode == NULL)) // Empty linked list
				{
					mHeadNode = transactionNode;
					mEndNode = transactionNode;
				}
				else if (mEndNode != NULL) // A non-empty - "valid" list
				{
					mEndNode->mNextNode = transactionNode;
					transactionNode->mPreviousNode = mEndNode;
					mEndNode = mEndNode->mNextNode;
				}
				
				USER_GAMETALK_ASSERT(mEndNode != NULL, "Add to TransactionList failed - mEndNode points at NULL");
				if (mEndNode == NULL)
					return (~0U);
				else
				{
					++mTransactionUidCounter;
					++mNumOfTransactions;
					return transactionUid;
				}
			}

			bool RemoveTransaction (Transaction * transaction)
			{
				TransactionNode * iteratorNode = mHeadNode;
				while (iteratorNode != NULL)
				{
					if (iteratorNode->GetTransaction() == transaction)
					{
						if (iteratorNode->mPreviousNode != NULL) // If this isn't the head of the list
						{
							iteratorNode->mPreviousNode->mNextNode = iteratorNode->mNextNode;
						}
						else
							mHeadNode = iteratorNode->mNextNode;
						
						if (iteratorNode->mNextNode != NULL) // If this isn't the end of the list
						{
							iteratorNode->mNextNode->mPreviousNode = iteratorNode->mPreviousNode;
						}
						else
							mEndNode = iteratorNode->mPreviousNode;

						delete iteratorNode;
						--mNumOfTransactions;

						return true;
					}
					iteratorNode = iteratorNode->NextNode();
				}
				return false;
			}

			TransactionNode * GetFirstTransactionNode ()
			{
				return mHeadNode;
			}

			size_t GetNumOfTransactions ()
			{
				return mNumOfTransactions;
			}

			USE_GAMETALK_ALLOC("FileTalk::TransactionList")
		private:
			TransactionNode * mHeadNode;
			TransactionNode * mEndNode;

			// This is simply a unique ID for the list of transactions that have been
			// created - at some point we may recycle numbers of transactions that have 
			// been removed from the pool - hence why we have this and mNumOfTransactions
			uint32_t mTransactionUidCounter;

			// Current number of transactions in our transaction pool
			size_t mNumOfTransactions;
			// The maximum number of transactions allowed in our transaction pool at any one time
			size_t mMaxNumTransactions;
		};
	}
}

// ------------------------------------------------------------------------------
// FileTalkManager :: FileTalkManager
// ------------------------------------------------------------------------------
FileTalkManager::FileTalkManager(EA::GameTalk::GameTalkInstance* gameTalkInstance, FileTalkHandlerFnc* handlerFunc,
								 uint32_t maxNumTransactions, uint32_t sendFragmentSize, uint32_t receiveFragmentSize,
								 EA::GameTalk::GameTalkHandlerFnc* gameTalkHandlerFunc)
:
mTransactions(NULL),
mGameTalkInstance(gameTalkInstance),
mFileTalkHandlerFunc(handlerFunc),
mSendFragmentSize(sendFragmentSize),
mReceiveFragmentSize(receiveFragmentSize),
mFileTalkConfigCallback(gameTalkHandlerFunc)
{
	USER_GAMETALK_ASSERT(gameTalkInstance != NULL, "GameTalkInstance is invalid.");
	// We need to listen on all channels so that we can distribute incoming GameTalk messages to applicable transactions
	mGameTalkInstance->RegisterMessageHandler(ReceiveMessage, "FileTalk.Transaction");

	mTransactions = new TransactionList(maxNumTransactions);

	if (!mFileTalkConfigCallback)
		mFileTalkConfigCallback = ForwardConfigMessage;

	mGameTalkInstance->RegisterMessageHandler(mFileTalkConfigCallback, FileTalkMessage::sFileTalkConfigChannel);

	EA::GameTalk::GameTalkMessage gtm(FileTalkMessage::sFileTalkConfigChannel);
	const int initMessageValue = FileTalkMessage::InitMessage;
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	// We have to send the string for this message since the FileTalkServer doesn't know our endian yet - so it requires the string
	gtm.AddKeyContent(FileTalkMessage::sInitMessage,EA::GameTalk::BinaryFormat,&initMessageValue,sizeof(int));
	const int oneToDetermineEndianness = 1;
	// The int "1" is being added to the message so the server can determine whether this is little or big endian
	gtm.AddKeyContent(mGameTalkInstance->TargetName(),EA::GameTalk::BinaryFormat,&oneToDetermineEndianness,sizeof(int));
	mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

// ------------------------------------------------------------------------------
// FileTalkManager :: ~FileTalkManager
// ------------------------------------------------------------------------------
FileTalkManager::~FileTalkManager()
{
	mGameTalkInstance->UnregisterMessageHandler(ReceiveMessage);
	mGameTalkInstance->UnregisterMessageHandler(mFileTalkConfigCallback);

	// Clear the transaction pool
	delete mTransactions;
	mTransactions = NULL;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: DeregisterTransaction
// NOTE: It is up to the user to delete the Transaction - DeregisterTransaction only 
// removes the transaction from the FileTalkManager's transaction pool and thereby 
// stops that transaction from sending or receiving data to and from the FileTalkServer
// ------------------------------------------------------------------------------
void FileTalkManager::DeregisterTransaction(Transaction * transaction)
{
	bool successfullyDeregistered = mTransactions->RemoveTransaction(transaction);
	USER_GAMETALK_ASSERT(successfullyDeregistered, "Wasn't able to remove the transaction from the transaction pool.");
	USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(successfullyDeregistered);
}

// ------------------------------------------------------------------------------
// FileTalkManager :: RegisterTransaction
// ------------------------------------------------------------------------------
bool FileTalkManager::RegisterTransaction(Transaction * transaction)
{
	bool sentTransaction = false;
	uint32_t transactionUid = mTransactions->AddTransaction(transaction);

	USER_GAMETALK_ASSERT(transactionUid != ~0U, "There was an error adding this transaction to the pool.");
	transaction->SetChannelId(transactionUid);

	USER_GAMETALK_ASSERT(mGameTalkInstance, "FileTalkManager can't register a transaction with a null GameTalkInstance");
	if (mGameTalkInstance)
	{
		USER_GAMETALK_ASSERT((mGameTalkInstance->IsConnected()), "FileTalkManager can't register a transaction with a disconnected GameTalkInstance");
		if (mGameTalkInstance->IsConnected())
		{
			sentTransaction = transaction->SendTransaction();
			USER_GAMETALK_ASSERT(sentTransaction, "FileTalkManager wasn't able to send the transaction");
		}
	}
	
	return sentTransaction;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: CreateInstance
// ------------------------------------------------------------------------------
void FileTalkManager::CreateInstance(EA::GameTalk::GameTalkInstance* gameTalkInstance, FileTalkHandlerFnc* handlerFunc, 
									 uint32_t maxNumTransactions, uint32_t sendFragmentSize, uint32_t receiveFragmentSize,
									 EA::GameTalk::GameTalkHandlerFnc* gameTalkHandlerFunc)
{
	USER_GAMETALK_ASSERT(sInstance == NULL, "Instance has already been created.");
	if (sInstance == NULL)
		sInstance = new FileTalkManager(gameTalkInstance, handlerFunc, maxNumTransactions, sendFragmentSize, receiveFragmentSize, gameTalkHandlerFunc);
}

// ------------------------------------------------------------------------------
// FileTalkManager :: GetInstance

// Asserts that an instance has been created before trying to return one.
// ------------------------------------------------------------------------------
FileTalkManager * FileTalkManager::GetInstance()
{
	// Don't enable the following - since the user may create and release the instance multiple times and this is how 
	// they can check to see if the instance is currently valid
	//USER_GAMETALK_ASSERT(sInstance != NULL, "Instance has not been created.");
	return sInstance;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: ReleaseInstance
//
// Asserts that an instance exists before attempting to release it.
// ------------------------------------------------------------------------------
void FileTalkManager::ReleaseInstance()
{
	USER_GAMETALK_ASSERT(sInstance != NULL, "Instance has not yet been created.");
	// Even if sInstance->mTransactions->GetNumOfTransactions() > 0 - we will handle the deletion/dealloc of those transactions for the user

	// Any transactions the user hasn't deleted directly will be deleted here
	delete sInstance;
	sInstance = NULL;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: ReceiveMessage
// ------------------------------------------------------------------------------
void FileTalkManager::ReceiveMessage(EA::GameTalk::GameTalkMessage * message)
{
	if (message->GetNumberOfKeys() == 0) // FileTalk has no use for messages with no key/value pairs
		return;
	USER_GAMETALK_ASSERT(sInstance != NULL, "Attempt to invoke FileTalkManager's ReceiveMessage when singleton is not instantiated");
	if (sInstance == NULL)
		return;

	// Find the transaction who this message belongs to and execute that transaction's ReceiveMessage function
	TransactionNode * transactionNode = sInstance->mTransactions->GetFirstTransactionNode();
	while (transactionNode != NULL)
	{
		if (EA::GameTalk::GameTalkInstance::IsMessageMatching(message->GetChannelId(), transactionNode->GetTransaction()->mChannelId))
		{
			transactionNode->GetTransaction()->ReceiveMessage(message);
			break;
		}
		transactionNode = transactionNode->NextNode();
	}
}

// ------------------------------------------------------------------------------
// FileTalkManager :: Update
// ------------------------------------------------------------------------------
bool FileTalkManager::Update ()
{
	bool updatesSucceeded = true;
	TransactionNode * transactionNode = mTransactions->GetFirstTransactionNode();
	while (transactionNode != NULL)
	{
		// we could optionally return a false if any Transaction::Update returns false
		if (!transactionNode->GetTransaction()->Update())
			updatesSucceeded = false;

		transactionNode = transactionNode->NextNode();
	}

	return updatesSucceeded;
}

bool FileTalkManager::SetFileRootForClient(const char * client, const char * fileRoot)
{
	EA::GameTalk::GameTalkMessage gtm(FileTalkMessage::sFileTalkConfigChannel);
	gtm.AddKeyContent(FileTalkMessage::sSetClientFileRoot,EA::GameTalk::StringFormat, fileRoot, USER_GAMETALK_STRLEN(fileRoot));
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, "", 0);	
	bool msgForClientSentToGts = mGameTalkInstance->SendMessage(client, &gtm);

	EA::GameTalk::GameTalkMessage gtmForFileTalkServer(FileTalkMessage::sFileTalkConfigChannel);
	const int setTenativeFileRootValue = FileTalkMessage::SetTentativeFileRoot;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtmForFileTalkServer.AddKeyContent(FileTalkMessage::sSetTentativeFileRoot,EA::GameTalk::BinaryFormat,&setTenativeFileRootValue,sizeof(int));
#else
	gtmForFileTalkServer.AddKeyContent("",EA::GameTalk::BinaryFormat,&setTenativeFileRootValue,sizeof(int));
#endif

	gtmForFileTalkServer.AddKeyContent(sInstance->mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, fileRoot, USER_GAMETALK_STRLEN(fileRoot));	
	gtmForFileTalkServer.AddKeyContent(client, EA::GameTalk::StringFormat, "", 0);	
	bool sentMsgToFileTalkServer = sInstance->mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtmForFileTalkServer);

	return msgForClientSentToGts && sentMsgToFileTalkServer;
}

bool FileTalkManager::RemoveFileRootForClient(const char * client)
{
	EA::GameTalk::GameTalkMessage gtm(FileTalkMessage::sFileTalkConfigChannel);
	gtm.AddKeyContent(FileTalkMessage::sRemoveClientFileRoot,EA::GameTalk::StringFormat, "", 0);
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, "", 0);	
	return mGameTalkInstance->SendMessage(client, &gtm);
}

bool FileTalkManager::AcceptTentativeFileRoot()
{
	EA::GameTalk::GameTalkMessage gtmForFileTalkServer(FileTalkMessage::sFileTalkConfigChannel);
	const int acceptTenativeFileRootValue = FileTalkMessage::AcceptTentativeFileRoot;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtmForFileTalkServer.AddKeyContent(FileTalkMessage::sAcceptTentativeFileRoot,EA::GameTalk::BinaryFormat,&acceptTenativeFileRootValue,sizeof(int));
#else
	gtmForFileTalkServer.AddKeyContent("",EA::GameTalk::BinaryFormat,&acceptTenativeFileRootValue, sizeof(int));
#endif

	gtmForFileTalkServer.AddKeyContent(sInstance->mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, "", 0);	
	return sInstance->mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtmForFileTalkServer);
}

void FileTalkManager::ForwardConfigMessage (EA::GameTalk::GameTalkMessage * message)
{
	if (message->GetNumberOfKeys() < 2)
		return;

	USER_GAMETALK_ASSERT(sInstance != NULL, "Attempt to invoke FileTalkManager's ForwardConfigMessage when singleton is not instantiated");
	if (sInstance == NULL)
		return;

	EA::GameTalk::GameTalkMessage gtm(FileTalkMessage::sFileTalkConfigChannel);
	const int setClientFileRootValue = FileTalkMessage::SetClientFileRoot;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sSetClientFileRoot,EA::GameTalk::BinaryFormat,&setClientFileRootValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&setClientFileRootValue,sizeof(int));
#endif

	const char * fileRoot = static_cast<const char*>(message->GetContent(0));
	gtm.AddKeyContent(sInstance->mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, fileRoot, USER_GAMETALK_STRLEN(fileRoot));	
	sInstance->mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

size_t FileTalkManager::GetNumOfTransactions()
{
	if (!mTransactions)
		return 0;

	return mTransactions->GetNumOfTransactions();
}

// ------------------------------------------------------------------------------
// FileTalkManager :: CreateFileInfoTransaction
// ------------------------------------------------------------------------------
FileInfoTransaction * FileTalkManager::CreateFileInfoTransaction(const char * path, void * userData)
{
	// Could store the transaction ID in the transaction pool container for quick look-up
	FileInfoTransaction * fileInfoTransaction = new FileInfoTransaction(mGameTalkInstance, path, 
		mFileTalkHandlerFunc, userData);
	// Internally tracking all transactions
	RegisterTransaction(fileInfoTransaction);

	return fileInfoTransaction;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: CreateGetFileTransaction
// ------------------------------------------------------------------------------
GetFileTransaction * FileTalkManager::CreateGetFileTransaction(const char * path, 
	void * userData, EA::FileTalk::IHeapAccessor *iAllocator, size_t fileIndex, size_t numBytes)
{
	// Could store the transaction ID in the transaction pool container for quick look-up
	GetFileTransaction * fileTransaction = new GetFileTransaction(mGameTalkInstance, path, 
		mFileTalkHandlerFunc, userData, mReceiveFragmentSize, iAllocator, fileIndex, numBytes);
	// Internally tracking all transactions
	RegisterTransaction(fileTransaction);

	return fileTransaction;
}

// ------------------------------------------------------------------------------
// FileTalkManager :: CreateGetFileTransaction
// ------------------------------------------------------------------------------
GetFileTransaction * FileTalkManager::CreateGetFileTransaction(const char * path, void * userData, EA::FileTalk::IHeapAccessor *iAllocator)
{
	return CreateGetFileTransaction(path, userData, iAllocator, 0, 0);
}

// ------------------------------------------------------------------------------
// FileTalkManager :: CreateSendFileTransaction
// ------------------------------------------------------------------------------
SendFileTransaction * FileTalkManager::CreateSendFileTransaction(void * fileData, size_t fileSize, const char * filePath, 
													  void * userData, bool overwriteFile)
{
	// Could store the transaction ID in the transaction pool container for quick look-up
	SendFileTransaction * fileTransaction = new SendFileTransaction(mGameTalkInstance, fileData, 
		fileSize, mSendFragmentSize, filePath, mFileTalkHandlerFunc, userData, overwriteFile);

	// Internally tracking all transactions
	RegisterTransaction(fileTransaction);

	return fileTransaction;
}

FileNotificationTransaction * FileTalkManager::CreateFileNotificationTransaction(void * userData, const char * filepath, const char * filter, 
																uint32_t notifyFilterFlag, uint32_t eventsFlag, bool includeSubdirectories)
{
	// Could store the transaction ID in the transaction pool container for quick look-up
	FileNotificationTransaction * fileNotificationTransaction = new FileNotificationTransaction(mGameTalkInstance,
		mFileTalkHandlerFunc, userData, filepath, filter, notifyFilterFlag, eventsFlag, includeSubdirectories);

	// Internally tracking all transactions
	RegisterTransaction(fileNotificationTransaction);

	return fileNotificationTransaction;
}

Transaction::Transaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, 
						 FileTalkHandlerFnc* handlerFunc, void * userData):
mFileTalkMessageHandler(NULL),
mUserData(userData),
mGameTalkInstance(gameTalkInstance),
mIsTransactionComplete(false),
mErrorCode(0),
mErrorMsg(NULL),
mErrorOccurred(false)
{
	mFileTalkMessageHandler = new FileTalkHandler(handlerFunc);
}

Transaction::~Transaction()
{
	delete mFileTalkMessageHandler;
	mFileTalkMessageHandler = NULL;

	USER_GAMETALK_FREE(mChannelId, "FileTalk:Transaction:mChannelId");
	mChannelId = NULL;

	FileTalkManager::GetInstance()->DeregisterTransaction(this);

	if (mErrorMsg) // Null check isn't necessary - but makes allocs match frees
		USER_GAMETALK_FREE(static_cast<void *>(const_cast<char *>(mErrorMsg)), "FileTalk:Transaction:mErrorMsg");

	mErrorMsg = NULL;

	// We didn't allocate user data - the user is responsible for deleting it
}

bool Transaction::IsTransactionComplete() const
{
	return mIsTransactionComplete;
}

void Transaction::TransactionIsComplete()
{
	// If the user is polling this object - this flag will indicate that the transaction is fully executed/populated
	mIsTransactionComplete = true;
	if (mFileTalkMessageHandler != NULL)
	{
		mFileTalkMessageHandler->Invoke(this);
	}
}

void *& Transaction::GetUserData()
{
	return mUserData;
}

void Transaction::ProcessError(EA::GameTalk::GameTalkMessage * message)
{
	if (message->GetSize(1) == 0)
		return;

	mErrorOccurred = true;
	// The format of the error code is a 32 bit int with the hashcode of the string that follows.
	const char * errorValueStr = static_cast<const char *>(message->GetContent(1));
	mErrorCode = *(static_cast<const int *>(message->GetContent(1)));
	const char * errorMsgContent = &(errorValueStr[sizeof(mErrorCode)]);
	size_t lengthOfErrorMsg = USER_GAMETALK_STRLEN(errorMsgContent);
	char * errorMsg = (char*)USER_GAMETALK_ALLOC(lengthOfErrorMsg + 1, "FileTalk::Transaction");
	USER_GAMETALK_STRNCPY(errorMsg, errorMsgContent, lengthOfErrorMsg + 1);
	mErrorMsg = errorMsg;
}

const char * Transaction::GetErrorMsg()
{
	return mErrorMsg;
}

int Transaction::GetErrorCode()
{
	return mErrorCode;
}

bool Transaction::ErrorOccurred()
{
	return mErrorOccurred;
}

void Transaction::SetChannelId(uint32_t transactionUID)
{
	// Use the UID that FileTalkManager generated for us to create our Channel ID
	const char * transactionPrefix = "FileTalk.Transaction.";
	size_t transactionPrefixLength = USER_GAMETALK_STRLEN(transactionPrefix);
	char transactionName [256];
	USER_GAMETALK_STRNCPY(transactionName, transactionPrefix, transactionPrefixLength);
	USER_GAMETALK_UNSIGNED_VALUE_TO_TEXT(&(transactionName[transactionPrefixLength]), transactionUID);
	size_t lengthOfTransactionName = USER_GAMETALK_STRLEN(transactionName);
	mChannelId = (char*)USER_GAMETALK_ALLOC(lengthOfTransactionName * sizeof(char) + 1, "FileTalk::Transaction");
	USER_GAMETALK_STRNCPY(mChannelId, transactionName, lengthOfTransactionName + 1);

	// Instead of the above - could manually add the null-delimiter
	//mChannelId[lengthOfTransactionName] = NULL;
	//USER_GAMETALK_STRNCPY(mChannelId, transactionName, lengthOfTransactionName);
}

FileInfoTransaction::~FileInfoTransaction()
{
	USER_GAMETALK_FREE(mCharData, "FileInfoTransaction:mCharData");
	mCharData = NULL;
	mFileInfoArraySize = 0;
	USER_GAMETALK_FREE(mFileInfoArray, "FileInfoTransaction:mFileInfoArray");
	mFileInfoArray = NULL;
	USER_GAMETALK_FREE(mFilePath, "FileInfoTransaction:mFilePath");
	mFilePath = NULL;
}

Transaction::ETransactionType FileInfoTransaction::TransactionType (void) const
{
	return Transaction::FileInfoTransaction;
}

size_t FileInfoTransaction::GetNumberOfFileInfos() const
{
	return mFileInfoArraySize;
}

const char * FileInfoTransaction::GetFileName(size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return &mCharData[mFileInfoArray[fileIndex].mFileNameOffset];
}

uint64_t FileInfoTransaction::GetFileSize(size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return mFileInfoArray[fileIndex].mFileSize;
}

bool FileInfoTransaction::IsDirectory (size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return ((mFileInfoArray[fileIndex].mFileFlags & FileInfoTransaction::IsDirectoryFlag) != 0);
}

bool FileInfoTransaction::ReadOnly (size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return ((mFileInfoArray[fileIndex].mFileFlags & FileInfoTransaction::ReadOnlyFlag) != 0);
}

uint64_t FileInfoTransaction::DateCreated (size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return mFileInfoArray[fileIndex].mFileCreatedDateTime;
}

uint64_t FileInfoTransaction::DateLastModified(size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return mFileInfoArray[fileIndex].mFileModifiedDateTime;
}

uint64_t FileInfoTransaction::DateLastAccessed(size_t fileIndex) const
{
	// Check to ensure that there are at least as many elements as the index the user is trying to use
	USER_GAMETALK_ASSERT(mFileInfoArraySize >= fileIndex, "Attempt to index File Info array our of bounds.");

	return mFileInfoArray[fileIndex].mFileAccessTime;
}

void FileInfoTransaction::ReceiveMessage(EA::GameTalk::GameTalkMessage * message)
{
	// All FileInfoTransaction GameTalkMessages have three parts
	USER_GAMETALK_ASSERT(message->GetNumberOfKeys() == 3, "FileInfoTransaction received message with invalid content");
	// Ensure that we are receiving FileInfo GameTalkMessage(s)
	const int * transactionResponseEnum = static_cast<const int *>(message->GetContent(0));
	USER_GAMETALK_ASSERT((*transactionResponseEnum)==FileTalkMessage::FileInfoResponse, "FileInfoTransaction has received a non-FileInfo GameTalkMessage");
	USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(transactionResponseEnum);

	ProcessError(message);
	if (mErrorOccurred)
	{
		TransactionIsComplete();
		return;
	}

	// Total size of the data that was sent in the content portion of the GameTalkMessage - do we need error checking for messages of length 0?
	const size_t totalDataLength = message->GetSize(2);
	const char * messageContent = static_cast<const char *>(message->GetContent(2));
	const uint64_t * numOfArrayElements = static_cast<const uint64_t *>(message->GetContent(2));

	// Since the number of elements in the array can never be greater than unsigned int - just cast to that smaller representation
	mFileInfoArraySize = (size_t)*numOfArrayElements;
	size_t numBytesInFileInfoArray = mFileInfoArraySize * sizeof(FileInfo);
	mFileInfoArray = (FileInfo *) USER_GAMETALK_ALLOC(numBytesInFileInfoArray, "FileTalk::FileInfoTransaction");

	// The FileInfo array is all the data between the first number (num of array elements) that is sent and the char data that follows
	USER_GAMETALK_MEMCPY(mFileInfoArray, &messageContent[sizeof(uint64_t)], numBytesInFileInfoArray);

	// After the first number and the array of FileInfo's lies the char data for the file names
	size_t startOfCharData = sizeof(uint64_t) + numBytesInFileInfoArray;

	// Total number of bytes that make up the char data
	size_t numBytesInCharData = ((size_t)totalDataLength) - startOfCharData;
	// This should include the null delimiter for the last string in the block of char data
	mCharData = (char*)USER_GAMETALK_ALLOC(numBytesInCharData+1, "FileTalk::FileInfoTransaction");
	// Don't use strncpy below since it will drop everything after the first null
	USER_GAMETALK_MEMCPY(mCharData, &messageContent[startOfCharData], numBytesInCharData+1);
	mCharData[numBytesInCharData] = '\0';

	TransactionIsComplete();
}

bool FileInfoTransaction::SendTransaction()
{
	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int fileInfoRequestValue = FileTalkMessage::FileInfoRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sFileInfoRequest,EA::GameTalk::BinaryFormat,&fileInfoRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&fileInfoRequestValue,sizeof(int));
#endif
	size_t strLengthOfDirPath = USER_GAMETALK_STRLEN(mFilePath);
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, mFilePath, strLengthOfDirPath);	
	return mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

FileInfoTransaction::FileInfoTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, 
										 const char * path, FileTalkHandlerFnc* handlerFunc, void * userData):
Transaction(gameTalkInstance, handlerFunc, userData), mFileInfoArraySize(0)
{
	// The base Transaction MUST register the user's callback before we request the file listing from FileTalkServer

	// Send the request using GameTalk
	size_t lengthOfFilePath = USER_GAMETALK_STRLEN(path);
	// + 1 - for the null delimiter
	mFilePath = (char*)USER_GAMETALK_ALLOC(lengthOfFilePath * sizeof(char) + 1, "FileTalk::FileInfoTransaction");
	USER_GAMETALK_STRNCPY(mFilePath, path, lengthOfFilePath + 1);
}

GetFileTransaction::~GetFileTransaction()
{
	mByteArrayIndex = 0;
	mByteArraySize = 0;
	if (!mUserHasTakenOwnership)
	{
		mAllocator->Free(mByteArray);
		mByteArray = NULL;
	}
	USER_GAMETALK_FREE(mFilePath, "GetFileTransaction:mFilePath");
	mFilePath = NULL;

	mFileIndex = 0;
	mNumBytesRequested = 0;
}

Transaction::ETransactionType GetFileTransaction::TransactionType (void) const
{
	return Transaction::GetFileTransaction;
}

GetFileTransaction::GetFileTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance,
								 const char * filePath, FileTalkHandlerFnc* handlerFunc, void * userData, 
								 const size_t fragmentSize, EA::FileTalk::IHeapAccessor * iAllocator,
								 const size_t fileIndex, const size_t numBytes):
Transaction(gameTalkInstance, handlerFunc, userData),
mByteArrayIndex(0),
mByteArraySize(0),
mFilePath(NULL),
mByteArray(NULL),
mFragmentSize(fragmentSize),
mAllocator(iAllocator),
mFileIndex(fileIndex),
mNumBytesRequested(numBytes),
mUserHasTakenOwnership(false)
{
	size_t lengthOfFilePath = USER_GAMETALK_STRLEN(filePath);
	// + 1 - for the null delimiter
	mFilePath = (char*)USER_GAMETALK_ALLOC(lengthOfFilePath * sizeof(char) + 1, "FileTalk::GetFileTransaction");
	USER_GAMETALK_STRNCPY(mFilePath, filePath, lengthOfFilePath + 1);
}

void GetFileTransaction::ReceiveMessage(EA::GameTalk::GameTalkMessage * message)
{
	// All GetFileTransaction GameTalkMessages have three parts
	USER_GAMETALK_ASSERT(message->GetNumberOfKeys() == 3, "GetFileTransaction has received unrecognized data");
	// Ensure that we are receiving a GetFile GameTalkMessage(s)
	const int * transactionResponseEnum = static_cast<const int *>(message->GetContent(0));
	USER_GAMETALK_ASSERT((*transactionResponseEnum)==FileTalkMessage::GetFileResponse, "GetFileTransaction has received a non-GetFile GameTalkMessage");
	USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(transactionResponseEnum);

	ProcessError(message);
	if (mErrorOccurred)
	{
		TransactionIsComplete();
		return;
	}

	if (message->CompareKey(2, "FileSize"))
	{
		const char * byteArraySizeStr = static_cast<const char*>(message->GetContent(2));

		// Passing mByteArraySize and casting its address to uint32_t* breaks strict aliasing rules, so we have to use an intermediary uint32_t variable.
		uint32_t byteArraySizeUInt = 0;
		USER_GAMETALK_TEXT_TO_VALUE(byteArraySizeStr, byteArraySizeUInt);
		mByteArraySize = static_cast<size_t>(byteArraySizeUInt);

		mByteArray = (uint8_t*)mAllocator->Alloc(mByteArraySize);
	}
	else if (message->CompareKey(2, "File"))
	{
		size_t messageSize = message->GetSize(2);
		USER_GAMETALK_MEMCPY(&mByteArray[mByteArrayIndex], message->GetContent(2), messageSize);
		mByteArrayIndex += messageSize;
	}
	else if (message->CompareKey(2, "Done"))
	{
		TransactionIsComplete();
	}
}

bool GetFileTransaction::SendTransaction()
{
	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int getFileRequestValue = FileTalkMessage::GetFileRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sGetFileRequest,EA::GameTalk::BinaryFormat,&getFileRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&getFileRequestValue,sizeof(int));
#endif
	size_t strLengthOfFilePath = USER_GAMETALK_STRLEN(mFilePath);
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, mFilePath, strLengthOfFilePath);	

	char fragmentSizeStr [10];
	USER_GAMETALK_UNSIGNED_VALUE_TO_TEXT(fragmentSizeStr, (uint32_t)mFragmentSize);
	gtm.AddKeyContent(fragmentSizeStr,EA::GameTalk::BinaryFormat,&mFragmentSize,sizeof(int));

	if (this->mNumBytesRequested > 0) // The user can specify a seek index and how far to seek
	{
		char fileIndexStr [10];
		USER_GAMETALK_UNSIGNED_VALUE_TO_TEXT(fileIndexStr, (uint32_t)mFileIndex);
		gtm.AddKeyContent(fileIndexStr,EA::GameTalk::BinaryFormat,&mFileIndex,sizeof(int));
		char numBytesRequestedStr [10];
		USER_GAMETALK_UNSIGNED_VALUE_TO_TEXT(numBytesRequestedStr, (uint32_t)mNumBytesRequested);
		gtm.AddKeyContent(numBytesRequestedStr,EA::GameTalk::BinaryFormat,&mNumBytesRequested,sizeof(int));
	}
	return mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

uint8_t * GetFileTransaction::GetFile()
{
	mUserHasTakenOwnership = true;
	return mByteArray;
}

size_t GetFileTransaction::GetFileSize() const
{
	return mByteArrayIndex;
}

const char * GetFileTransaction::GetFilePath()
{
	return (const char *)mFilePath;
}

// We intentionally do NOT initialize mByteArraySize and mFragmentSize with what the user has passed in - since we
// need to validate the information is good and we don't want a partially initialized SendFileTransaction
SendFileTransaction::SendFileTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, 
										 void * fileData, size_t fileSize, const size_t fragmentSize, const char * filePath,
										 FileTalkHandlerFnc* handlerFunc, void * userData, bool overwriteFile):
Transaction(gameTalkInstance, handlerFunc, userData),
mFilePathLength(0),
mByteArrayIndex(0),
mByteArraySize(0), // this should NOT use the user provided parameter - to avoid partially initialized object
mFragmentSize(0), // this should NOT use the user provided parameter - to avoid partially initialized object
mNumFragments(0),
mFragmentIndex(0),
mByteArray(NULL),
mFilePath(NULL),
mOverwriteFile(overwriteFile)
{
	// The base Transaction MUST register the user's callback before we attempt to send the file to the FileTalkServer
	USER_GAMETALK_ASSERT(fragmentSize > 0, "The fragment size must be greater than zero");

	mByteArraySize = fileSize;
	mFragmentSize = fragmentSize;
	mNumFragments = fileSize/mFragmentSize;

	mFilePathLength = USER_GAMETALK_STRLEN(filePath);
	// + 1 - for the null delimiter
	mFilePath = (char*)USER_GAMETALK_ALLOC(mFilePathLength + 1, "FileTalk::GetFileTransaction");
	USER_GAMETALK_STRNCPY(mFilePath, filePath, mFilePathLength + 1);

	mByteArray = (uint8_t*)USER_GAMETALK_ALLOC(mByteArraySize, "FileTalk::GetFileTransaction");
	USER_GAMETALK_MEMCPY(mByteArray, fileData, mByteArraySize);
}

SendFileTransaction::~SendFileTransaction()
{
	mFilePathLength = 0;
	mByteArrayIndex = 0;
	mByteArraySize = 0;
	USER_GAMETALK_FREE(mByteArray, "SendFileTransaction:mByteArray");
	mByteArray = NULL;
	USER_GAMETALK_FREE(mFilePath, "SendFileTransaction:mFilePath");
	mFilePath = NULL;
	mFragmentSize = 0;
	mNumFragments = 0;
	mFragmentIndex = 0;
}

void SendFileTransaction::ReceiveMessage(EA::GameTalk::GameTalkMessage * message)
{
	// All SendFileTransaction GameTalkMessages have three parts
	USER_GAMETALK_ASSERT(message->GetNumberOfKeys() == 3, "SendFileTransaction received message with invalid content");
	// Ensure that we are receiving a SendFile GameTalkMessage(s)
	const int * transactionResponseEnum = static_cast<const int *>(message->GetContent(0));
	USER_GAMETALK_ASSERT((*transactionResponseEnum)==FileTalkMessage::SendFileResponse, "SendFileTransaction has received a non-SendFile GameTalkMessage");
	USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(transactionResponseEnum);

	ProcessError(message);
	if (mErrorOccurred)
	{
		TransactionIsComplete();
		return;
	}

	if (message->CompareKey(2, "Done"))
		TransactionIsComplete();
}

bool SendFileTransaction::SendTransaction()
{
	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int sendFileRequestValue = FileTalkMessage::SendFileRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sSendFileRequest,EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#endif
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, mFilePath, mFilePathLength);
	char fileSizeStr [10];
	USER_GAMETALK_UNSIGNED_VALUE_TO_TEXT(fileSizeStr, (uint32_t)mByteArraySize);
	gtm.AddKeyContent(fileSizeStr,EA::GameTalk::BinaryFormat,&mByteArraySize,sizeof(size_t));
	if (mOverwriteFile)
		gtm.AddKeyContent("overwrite",EA::GameTalk::StringFormat,"",0);
	else
		gtm.AddKeyContent("append",EA::GameTalk::StringFormat,"",0);

	return mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

Transaction::ETransactionType SendFileTransaction::TransactionType (void) const
{
	return Transaction::SendFileTransaction;
}

const char * SendFileTransaction::GetFilePath()
{
	return (const char *)mFilePath;
}

bool SendFileTransaction::Update ()
{
	// If the byte array doesn't have any data - nothing to send - so just return
	if (mByteArray == NULL)
		return true;

	if (mFragmentIndex++ < mNumFragments) // post-increment mFragmentIndex and check that it is less than the total num of fragments
	{
		EA::GameTalk::GameTalkMessage gtm2(mChannelId);
		const int sendFileRequestValue = FileTalkMessage::SendFileRequest;
#ifdef USER_FILETALK_DEBUG
		// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
		gtm2.AddKeyContent(FileTalkMessage::sSendFileRequest,EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#else
		gtm2.AddKeyContent("",EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#endif
		gtm2.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, mFilePath, mFilePathLength);
		gtm2.AddKeyContent("",EA::GameTalk::BinaryFormat,(&mByteArray[mByteArrayIndex]),mFragmentSize);
		if (!mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm2))
			return false;
		mByteArrayIndex += mFragmentSize;
	}
	else if (mByteArrayIndex != mByteArraySize) // if we've sent all the fragments - we still might have a last bit/remainder
	{
		size_t remainderToSend = mByteArraySize - mByteArrayIndex;
		EA::GameTalk::GameTalkMessage gtm3(mChannelId);
		const int sendFileRequestValue = FileTalkMessage::SendFileRequest;
#ifdef USER_FILETALK_DEBUG
		// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
		gtm3.AddKeyContent(FileTalkMessage::sSendFileRequest,EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#else
		gtm3.AddKeyContent("",EA::GameTalk::BinaryFormat,&sendFileRequestValue,sizeof(int));
#endif
		gtm3.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, mFilePath, mFilePathLength);
		gtm3.AddKeyContent("",EA::GameTalk::BinaryFormat,(&mByteArray[mByteArrayIndex]),remainderToSend);
		if (!mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm3))
			return false;
		mByteArrayIndex += remainderToSend;
	}
	return true;
}

char * FileNotificationTransaction::GetFilePath() const
{
	return mFilePath;
}

char * FileNotificationTransaction::GetName() const
{
	return mName;
}

char * FileNotificationTransaction::GetOldFilePath() const
{
	return mOldFilePath;
}

char * FileNotificationTransaction::GetOldName() const
{
	return mOldName;
}

FileNotificationTransaction::EventHandler FileNotificationTransaction::GetEventHandler() const
{
	return mEventHandler;
}

void FileNotificationTransaction::DeregisterListener() const
{
	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int fileNotificationRequestValue = FileTalkMessage::FileNotificationRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sFileNotificationRequest,EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#endif
	// Not sure whether we want to store all FileTalk requests strings as static const members of FileTalkMessage
	const char * deregisterMessage = "Deregister";
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, deregisterMessage, USER_GAMETALK_STRLEN(deregisterMessage));

	mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

void FileNotificationTransaction::ClearCurrentMembers()
{
	USER_GAMETALK_FREE(mFilePath, "FileNotificationTransaction:mFilePath");
	mFilePath = NULL;
	USER_GAMETALK_FREE(mName, "FileNotificationTransaction:mName");
	mName = NULL;
	USER_GAMETALK_FREE(mOldFilePath, "FileNotificationTransaction:mOldFilePath");
	mOldFilePath = NULL;
	USER_GAMETALK_FREE(mOldName, "FileNotificationTransaction:mOldName");
	mOldName = NULL;
	mEventHandler = FileNotificationTransaction::INVALID;
	if (mErrorMsg) // Null check isn't necessary - but makes allocs match frees
		USER_GAMETALK_FREE(static_cast<void *>(const_cast<char *>(mErrorMsg)), "FileNotificationTransaction:mErrorMsg");

	mErrorMsg = NULL;
	mErrorCode = 0;
	mErrorOccurred = false;

	// The following members are only necessary because the Send of the transaction doesn't flow from within its construction
	USER_GAMETALK_FREE(mFilter, "FileNotificationTransaction:mFilter");
	mFilter = NULL;
	mNotifyFilterFlag = 0;
	mEventsFlag = 0;
	mIncludeSubdirectories = false;
}

FileNotificationTransaction::~FileNotificationTransaction()
{
	// Deregister this listener
	DeregisterListener();

	ClearCurrentMembers();
}

Transaction::ETransactionType FileNotificationTransaction::TransactionType (void) const
{
	return Transaction::FileNotificationTransaction;
}

void FileNotificationTransaction::ReceiveMessage(EA::GameTalk::GameTalkMessage * message)
{
	// All FileNotificationTransaction GameTalkMessages have at least three parts
	USER_GAMETALK_ASSERT(message->GetNumberOfKeys() > 3, "FileNotificationTransaction received message with invalid content");
	// Ensure that we are receiving a FileNotification GameTalkMessage(s)
	const int * transactionResponseEnum = static_cast<const int *>(message->GetContent(0));
	USER_GAMETALK_ASSERT((*transactionResponseEnum)==FileTalkMessage::FileNotificationResponse, "FileNotificationTransaction has received a non-FileNotification GameTalkMessage");
	USER_GAMETALK_USE_VAR_TO_AVOID_WARNING(transactionResponseEnum);

	// Since we are about to assign new values to these members - free up the memory in use by the previous values
	ClearCurrentMembers();

	ProcessError(message);
	if (mErrorOccurred)
	{
		TransactionIsComplete();
		return;
	}

	size_t filePathLength = USER_GAMETALK_STRLEN((char *)message->GetKey(2));
	mFilePath = (char*)USER_GAMETALK_ALLOC(filePathLength + 1, "FileTalk::FileNotificationTransaction");
	USER_GAMETALK_STRNCPY(mFilePath, (char *)message->GetKey(2), filePathLength + 1);

	size_t fileNameLength = USER_GAMETALK_STRLEN(message->GetKey(3));
	mName = (char*)USER_GAMETALK_ALLOC(fileNameLength + 1, "FileTalk::FileNotificationTransaction");
	USER_GAMETALK_STRNCPY(mName, message->GetKey(3), fileNameLength + 1);

	mEventHandler = (EventHandler)atoi((char *)message->GetContent(3));

	if (message->GetNumberOfKeys() == 5)
	{
		size_t oldFilePathLength = USER_GAMETALK_STRLEN((char *)message->GetContent(4));
		mOldFilePath = (char*)USER_GAMETALK_ALLOC(oldFilePathLength + 1, "FileTalk::FileNotificationTransaction");
		USER_GAMETALK_STRNCPY(mOldFilePath, (char *)message->GetContent(4), oldFilePathLength + 1);

		size_t oldFileNameLength = USER_GAMETALK_STRLEN(message->GetKey(4));
		mOldName = (char*)USER_GAMETALK_ALLOC(oldFileNameLength + 1, "FileTalk::FileNotificationTransaction");
		USER_GAMETALK_STRNCPY(mOldName, message->GetKey(4), oldFileNameLength + 1);		
	}

	TransactionIsComplete();
}

bool FileNotificationTransaction::SendTransaction()
{
	uint32_t includeSubdirectoriesInt = 0;
	if (mIncludeSubdirectories)
		includeSubdirectoriesInt = 1;
	// Currently we know that numbers are each 4 bytes - so we don't need to use sizeof - but someone may change the 
	// parameter type without updating the numbers in the following:
	const size_t byteArrayLength = sizeof(includeSubdirectoriesInt) + sizeof(mNotifyFilterFlag) + sizeof(mEventsFlag);
	char byteArray [byteArrayLength];
	uint32_t indexInByteArray = 0;
	// Add the boolean (we make the boolean a number to avoid alignment stuff) and the two bit field numbers:
	USER_GAMETALK_MEMCPY(&byteArray[indexInByteArray], &includeSubdirectoriesInt, sizeof(includeSubdirectoriesInt));
	indexInByteArray += sizeof(includeSubdirectoriesInt);
	USER_GAMETALK_MEMCPY(&byteArray[indexInByteArray], &mNotifyFilterFlag, sizeof(mNotifyFilterFlag));
	indexInByteArray += sizeof(mNotifyFilterFlag);
	USER_GAMETALK_MEMCPY(&byteArray[indexInByteArray], &mEventsFlag, sizeof(mEventsFlag));
	//indexInByteArray += sizeof(eventsFlag);

	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int fileNotificationRequestValue = FileTalkMessage::FileNotificationRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sFileNotificationRequest,EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#endif
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::BinaryFormat, byteArray, byteArrayLength);
	gtm.AddKeyContent(mFilePath, EA::GameTalk::StringFormat, mFilter, USER_GAMETALK_STRLEN(mFilter));

	return mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
}

FileNotificationTransaction::FileNotificationTransaction(EA::GameTalk::GameTalkInstance * gameTalkInstance, 
							FileTalkHandlerFnc* handlerFunc, void * userData, const char * filepath, 
							const char * filter, uint32_t notifyFilterFlag, uint32_t eventsFlag, bool includeSubdirectories):
Transaction(gameTalkInstance, handlerFunc, userData),
mFilePath(NULL),
mName(NULL),
mOldFilePath(NULL),
mOldName(NULL),
mEventHandler(FileNotificationTransaction::INVALID),
mNotifyFilterFlag(notifyFilterFlag),
mEventsFlag(eventsFlag),
mIncludeSubdirectories(includeSubdirectories)
{
	size_t filePathLength = USER_GAMETALK_STRLEN(filepath);
	mFilePath = (char*)USER_GAMETALK_ALLOC(filePathLength + 1, "FileTalk::FileNotificationTransaction");
	USER_GAMETALK_STRNCPY(mFilePath, filepath, filePathLength + 1);

	size_t filterLength = USER_GAMETALK_STRLEN(filter);
	mFilter = (char*)USER_GAMETALK_ALLOC(filterLength + 1, "FileTalk::FileNotificationTransaction");
	USER_GAMETALK_STRNCPY(mFilter, filter, filterLength + 1);
}

void FileNotificationTransaction::Reenable()
{
	EA::GameTalk::GameTalkMessage gtm(mChannelId);
	const int fileNotificationRequestValue = FileTalkMessage::FileNotificationRequest;
#ifdef USER_FILETALK_DEBUG
	// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
	gtm.AddKeyContent(FileTalkMessage::sFileNotificationRequest,EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#else
	gtm.AddKeyContent("",EA::GameTalk::BinaryFormat,&fileNotificationRequestValue,sizeof(int));
#endif
	// Not sure whether we want to store all FileTalk requests strings as static const members of FileTalkMessage
	const char * reenableStr = "Reenable";
	gtm.AddKeyContent(mGameTalkInstance->TargetName(), EA::GameTalk::StringFormat, reenableStr, USER_GAMETALK_STRLEN(reenableStr));
	mGameTalkInstance->SendMessage(FileTalkMessage::sFileTalkServerID, &gtm);
	mIsTransactionComplete = false;
}



