using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Diagnostics;
using System.Threading;

using EA.GameTalkMessages;
using EA.FileTalkMessages;
using EA.GameTalk;

namespace EA.FileTalk
{
	public class ClientContext
	{
		/// <summary>
		/// This delegate/callback is for the FileNotificationResponse - so that the appropriate
		/// file-root is appended onto filepaths used in FileTalkServer
		/// </summary>
		/// <param name="filePath"></param>
		/// <returns></returns>
		public delegate string AppendFileRootDelegate(string filePath);
		
		/// <summary>
		/// The unique string ID of the client to which this context is specific
		/// </summary>
		string mTarget = null;

		/// <summary>
		/// mFileBuffers contains objects of type FileBuffer
		/// FileBuffer objects are the server's way of building up a file from 
		/// separate messages from a particular client/target
		/// Each FileBuffer is key'ed to a key of the form:
		/// <FilePath to Store File In>
		/// </summary>
		private Hashtable mFileBuffers = new Hashtable();

		/// <summary>
		/// There is sufficient functionality specific to FileNotificationResponses
		/// to warrant its own class - the other responses may follow suit if their
		/// complexity increases
		/// </summary>
		private FileNotificationResponse mFileNotificationResponse = null;
		
		/// <summary>
		/// Marked as volatile because the FileServer thread could alter it - while the 
		/// ClientContext thread is reading it
		/// </summary>
		private volatile bool mRun = false;

		/// <summary>
		/// The ClientContext thread is constantly running looking in the Message queue for 
		/// new messages to process
		/// </summary>
		private Queue<GameTalkMessage> mGameTalkMessagesToProcess = new Queue<GameTalkMessage>();

		/// <summary>
		/// Probably doesn't need to be marked as volatile anymore since FileTalkServer thread
		/// doesn't alter this
		/// </summary>
		private volatile string mFileRoot = null;

		/// <summary>
		/// This can be set by a client other than the client that uses this ClientContext
		/// </summary>
		private volatile string mTentativeFileRoot = null;

		/// <summary>
		/// Indication of the endian of the client that is connecting
		/// </summary>
		private bool mBigEndian = true;

		/// <summary>
		/// 
		/// </summary>
		private GetClientContextDelegate mGetClientContextDelegate = null;

		/// <summary>
		/// Initialize the ClientContext to the target that this ClientContext is specific to
		/// </summary>
		/// <param name="target"></param>
		public ClientContext(string target, GetClientContextDelegate getClientContextDelegate)
		{
			mTarget = target;
			AppendFileRootDelegate appendFileRootDelegate = new AppendFileRootDelegate(this.AppendRootToFilePath);
			mFileNotificationResponse = new FileNotificationResponse(target, appendFileRootDelegate);
			mGetClientContextDelegate = getClientContextDelegate;
		}

		/// <summary>
		/// Upon receipt of a transaction message from a FileTalk client - this can be used
		/// to determine which request it was by mapping the int to the FileTalkMessage enum
		/// </summary>
		/// <param name="numAsByteArray"></param>
		/// <returns></returns>
		private int GetEnumIndex(byte[] numAsByteArray)
		{
			MemoryStream memStream = new MemoryStream(numAsByteArray);
			PlatformBinaryReader platformBinaryReader = new PlatformBinaryReader(mBigEndian, memStream);
			int result = platformBinaryReader.ReadInt32();
			platformBinaryReader.Close();
			return result;
		}

		public static byte[] GetEnumIndexBytes (int enumIndex, bool bigEndian)
		{
			MemoryStream mStream = new MemoryStream();
			PlatformBinaryWriter platformBinaryWriter = new PlatformBinaryWriter(bigEndian, mStream);
			platformBinaryWriter.Write(enumIndex);
			long pos = mStream.Position;
			mStream.Seek(0, System.IO.SeekOrigin.Begin);
			byte[] byteArray = new byte[pos];
			mStream.Read(byteArray, 0, (int)pos);
			return byteArray;
		}

		/// <summary>
		/// This method takes an array of FileSystemInfo's and makes them into a byte 
		/// array depending on the specified endian
		/// </summary>
		/// <param name="fileSystemInfos"></param>
		/// <returns></returns>
		private byte [] GetBinaryArrayOfFileSystemInfo(FileSystemInfo[] fileSystemInfos)
		{
			MemoryStream mStream = new MemoryStream();
			PlatformBinaryWriter platformBinaryWriter = new PlatformBinaryWriter(mBigEndian, mStream);

			// Only casting this to ulong because all other data is 64 bits - thus I avoid buffering of "natural size"
			platformBinaryWriter.Write((ulong)fileSystemInfos.Length);
			ulong fileNameOffset = 0;
			for (int i = 0; i < fileSystemInfos.Length; i++)
			{
				// First part of the struct is the file name offset
				platformBinaryWriter.Write(fileNameOffset);
				ulong fileFlagsBitField = 0;
				// If the result of the AND is 0, then it is a file - otherwise it is a directory
				bool isFile = (fileSystemInfos[i].Attributes & FileAttributes.Directory) == 0;
				if (isFile)
				{
					FileInfo fileInfo = (FileInfo)fileSystemInfos[i];
					platformBinaryWriter.Write((ulong)fileInfo.Length);
				}
				else
				{
					fileFlagsBitField |= (ulong)FileTalkMessage.FileFlags.IsDirectory;
					// Since directories don't have a size (in the tranditional sense)
					// we'll use zero - so we still get this member
					// Later, we might want this to return the number of files/dirs
					// that it contains
					platformBinaryWriter.Write((ulong)0);
				}
				if ((fileSystemInfos[i].Attributes & FileAttributes.ReadOnly) != 0)
					fileFlagsBitField |= (ulong)FileTalkMessage.FileFlags.ReadOnly;
				/*
				if (blah2)
					fileFlagsBitField |= (ulong)FileFlagsPositions.Blah;
				if (blah3)
					fileFlagsBitField |= (ulong)FileFlagsPositions.Blah2;
				 */

				// Second part of the struct is the file flags
				platformBinaryWriter.Write(fileFlagsBitField);

				platformBinaryWriter.Write((ulong)fileSystemInfos[i].CreationTime.Ticks);
				platformBinaryWriter.Write((ulong)fileSystemInfos[i].LastWriteTime.Ticks);
				platformBinaryWriter.Write((ulong)fileSystemInfos[i].LastAccessTime.Ticks);

				// This is a post increment - we don't need the results of the calculation
				// until the next iteration (if there is one)
				// We add one for the null delimiter
				fileNameOffset += (ulong)fileSystemInfos[i].FullName.Length + 1;
			}

			for (int i = 0; i < fileSystemInfos.Length; i++)
			{
				// We write each of the file name strings individually so we get the null 
				// delimiter added - I could do it manually in a StringBuilder in the
				// above loop - but meh.. 
				platformBinaryWriter.Write(fileSystemInfos[i].FullName);
			}

			long pos = mStream.Position;
			mStream.Seek(0, System.IO.SeekOrigin.Begin);
			byte[] byteArray = new byte[pos];
			mStream.Read(byteArray, 0, (int)pos);
			// Is this next commented out line needed if we are just throwing the stream away?
			//mStream.Seek(pos, System.IO.SeekOrigin.Begin);

			return byteArray;
		}

		private byte[] GetErrorBytes(string methodName, Exception exception, string taggedOnMessage)
		{
			int exceptionHashCode = exception.Message.GetHashCode();
			Trace.WriteLine(methodName + ": Exception Hash-Code: " + exceptionHashCode + " Exception: " + exception.Message + " Tagged On Message: " + taggedOnMessage + Environment.NewLine + exception.StackTrace);
			MemoryStream mStream = new MemoryStream();
			PlatformBinaryWriter platformBinaryWriter = new PlatformBinaryWriter(mBigEndian, mStream);
			platformBinaryWriter.Write(exceptionHashCode);
			platformBinaryWriter.Write(exception.Message + taggedOnMessage);
			long pos = mStream.Position;
			mStream.Seek(0, System.IO.SeekOrigin.Begin);
			byte[] errorMsg = new byte[pos];
			mStream.Read(errorMsg, 0, (int)pos);
			return errorMsg;
		}

		/// <summary>
		/// Respond to request for file information on a file or a directory of files
		/// </summary>
		/// <param name="message"></param>
		private void RespondToFileInfoRequest(GameTalkMessage message)
		{
			// All file listing request messages should have exactly two key/content pairs
			if (message.NumberOfKeys != 2)
				return;

			byte[] binaryArrayKeyContent = new byte[0];
			string target = message.GetKey(1);
			byte [] errorMsg = new byte[0];
			try
			{
				string filePathStr = message.GetContentAsString(1);
				filePathStr = AppendRootToFilePath(filePathStr);
				// If this path doesn't exist - the following FileInfo constructor will throw an exception
				FileInfo fileInfo = new FileInfo(filePathStr);
				FileSystemInfo[] fileSystemInfos = null;
				if ((fileInfo.Attributes & FileAttributes.Directory) == 0)
				{
					// The user is requesting File Information on a single file
					fileSystemInfos = new FileSystemInfo[1];
					fileSystemInfos[0] = fileInfo;
				}
				else
				{
					// The user is requesting file information on a directory of files
					DirectoryInfo directoryInfo = new DirectoryInfo(filePathStr);
					fileSystemInfos = directoryInfo.GetFileSystemInfos();
				}

				if (fileSystemInfos.Length != 0)
				{
					binaryArrayKeyContent = GetBinaryArrayOfFileSystemInfo(fileSystemInfos);
				}
			}
			catch (Exception exception)
			{
				errorMsg = GetErrorBytes("RespondToFileInfoRequest", exception, null);
			}
			GameTalkMessage responseMessage = new GameTalkMessage(message.ChannelId);
#if(DEBUG)
			// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
			responseMessage.AddKeyContentAsBinary(FileTalkMessage.MessageID.FileInfoResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileInfoResponse,mBigEndian));
#else
			responseMessage.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileInfoResponse, mBigEndian));
#endif
			responseMessage.AddKeyContentAsBinary("", errorMsg);
			responseMessage.AddKeyContentAsBinary("FileInfos", binaryArrayKeyContent);
			GameTalkManager.Instance.Send(target, responseMessage);
		}

		/// <summary>
		/// Get the bytes that make up a file in the file-system
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private byte[] ReadFileContent(string fileName, long offset, uint size)
		{
			byte[] content = null;
			FileStream stream = null;
			BinaryReader reader = null;
			try
			{
				if (!File.Exists(fileName))
					return content;

				string openFileStreamErrMsg = string.Empty;
				// We'll repeatedly try to get a valid stream 10 times
				for (int i = 0; (i < 100) && (stream == null); i++)
				{
					try
					{
						stream = File.Open(fileName, FileMode.Open, FileAccess.Read);
					}
					catch (System.IO.IOException exception)
					{
						// This likely means that another process has a lock on this file
						Trace.WriteLine("ReadFileContent: IOException: " + exception.Message + Environment.NewLine + exception.StackTrace);
						if (openFileStreamErrMsg == string.Empty)
							openFileStreamErrMsg = "ReadFileContent: IOException: " + exception.Message + Environment.NewLine + exception.StackTrace;
						System.Threading.Thread.Sleep(10);
					}
				}

				if (stream == null)
				{
					if (openFileStreamErrMsg != string.Empty)
						throw new Exception(openFileStreamErrMsg);
					else
						throw new Exception("In ClientContext.ReadFileContent the FileStream was not successfully opened - but nor was there an error apparently.");
				}

				if (size != 0)
				{
					stream.Seek(offset, SeekOrigin.Begin);
					reader = new BinaryReader(stream);
					content = reader.ReadBytes((int)size);
				}
				else
				{
					reader = new BinaryReader(stream);
					content = reader.ReadBytes((int)stream.Length);
				}
			}
			catch
			{
				throw;
			}
			finally
			{
				if (reader != null)
				{
					reader.Close();
				}
				if (stream != null)
				{
					stream.Close();
				}
			}

			return content;
		}

		private void SendFinalGetFileMessageResponse(byte[] errorMsg, string target, string channelId)
		{
			GameTalkMessage doneMessageResponse = new GameTalkMessage(channelId);
#if(DEBUG)
			// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
			doneMessageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.GetFileResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#else
			doneMessageResponse.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#endif
			doneMessageResponse.AddKeyContentAsBinary("", errorMsg);
			doneMessageResponse.AddKeyContentAsString("Done", "Done");
			GameTalkManager.Instance.Send(target, doneMessageResponse);
		}

		/// <summary>
		/// Respond to the request for a particular file in the file-system
		/// Assuming the file exists and the server has access to it - the file
		/// will be returned to the client that made the request
		/// </summary>
		/// <param name="message"></param>
		private void RespondToGetFileRequest(GameTalkMessage message)
		{
			// All file request messages should have exactly three or five key/content pairs
			if (message.NumberOfKeys < 3)
				return;

			string target = message.GetKey(1);
			string filePath = message.GetContentAsString(1);
			filePath = AppendRootToFilePath(filePath);

			byte[] errorMsg = new byte[0];
			if (!File.Exists(filePath))
			{
				// If file doesn't exist - send error response
				Exception exception = new Exception("File doesn't exist");
				errorMsg = GetErrorBytes("RespondToFileInfoRequest", exception, ": " + filePath);
				SendFinalGetFileMessageResponse(errorMsg, target, message.ChannelId);

				return;
			}

			try
			{
				uint index = 0;
				// This number is also available in the content as a binary
				uint fragmentSize = Convert.ToUInt32((string)message.GetKey(2));
				byte[] data = null;
				if (message.NumberOfKeys == 5)
				{
					uint indexToStart = Convert.ToUInt32((string)message.GetKey(3));
					uint dataToSend = Convert.ToUInt32((string)message.GetKey(4));
					data = ReadFileContent(filePath, indexToStart, dataToSend);
				}
				else
					data = ReadFileContent(filePath, 0, 0);

				uint numOfFragments = (uint)data.Length / fragmentSize;
				GameTalkMessage messageResponseSize = new GameTalkMessage(message.ChannelId);
#if(DEBUG)
				// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
				messageResponseSize.AddKeyContentAsBinary(FileTalkMessage.MessageID.GetFileResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#else
				messageResponseSize.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#endif
				messageResponseSize.AddKeyContentAsBinary("", errorMsg);
				messageResponseSize.AddKeyContentAsString("FileSize", data.Length.ToString());
				GameTalkManager.Instance.Send(target, messageResponseSize);

				for (int i = 0; i < numOfFragments; i++)
				{
					byte[] content = new byte[fragmentSize];
					Array.Copy(data, index, content, 0, fragmentSize);
					GameTalkMessage messageResponse = new GameTalkMessage(message.ChannelId);
#if(DEBUG)
					// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
					messageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.GetFileResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#else
					messageResponse.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#endif
					messageResponse.AddKeyContentAsBinary("", errorMsg);
					messageResponse.AddKeyContentAsBinary("File", content);
					index += fragmentSize;
					GameTalkManager.Instance.Send(target, messageResponse);
				}
				// Send any leftover - i.e. not perfectly divisible by the num of fragments
				if (index < data.Length)
				{
					fragmentSize = (uint)data.Length - index;
					byte[] content = new byte[fragmentSize];
					Array.Copy(data, index, content, 0, fragmentSize);
					GameTalkMessage messageResponse = new GameTalkMessage(message.ChannelId);
#if(DEBUG)
					// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
					messageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.GetFileResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#else
					messageResponse.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.GetFileResponse, mBigEndian));
#endif
					messageResponse.AddKeyContentAsBinary("", errorMsg);
					messageResponse.AddKeyContentAsBinary("File", content);
					GameTalkManager.Instance.Send(target, messageResponse);
				}
			}
			catch (Exception exception)
			{
				Trace.WriteLine("RespondToFileInfoRequest: Exception: " + exception.Message + Environment.NewLine + exception.StackTrace);
				errorMsg = GetErrorBytes("RespondToFileInfoRequest", exception, null);
			}
			SendFinalGetFileMessageResponse(errorMsg, target, message.ChannelId);
		}

		private void SendFinalSendFileMessageResponse(byte[] errorMsg, string target, string channelId)
		{
			GameTalkMessage doneMessageResponse = new GameTalkMessage(channelId);
#if(DEBUG)
			// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
			doneMessageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.SendFileResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.SendFileResponse, mBigEndian));
#else
			doneMessageResponse.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.SendFileResponse, mBigEndian));
#endif
			doneMessageResponse.AddKeyContentAsBinary("", errorMsg);
			doneMessageResponse.AddKeyContentAsString("Done", "Done");
			GameTalkManager.Instance.Send(target, doneMessageResponse);
		}

        /// <summary>
        /// We now generate a unique ID based on the file-path of a file AND 
        /// the transaction ID - in case the user starts multiple SendFileTransactions
        /// followed by a single FileTalkManager::Update
        /// </summary>
        /// <param name="transactionId"></param>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private string GenerateFileBufferId(string transactionId, string filePath)
        {
            return transactionId + Environment.NewLine + filePath;
        }

		/// <summary>
		/// Respond to request to send a file to the FileTalkServer
		/// The first message is used to create a member with sufficient space
		/// to store the bytes of the file
		/// The client will also specify where in the FileTalkServer's directory system
		/// it should put the file
		/// </summary>
		/// <param name="message"></param>
		private void RespondToSendFileRequest(GameTalkMessage message)
		{
			// All send file messages should have at least three key/content pairs
			if (message.NumberOfKeys < 3)
				return;

			string target = message.GetKey(1);
			string filePath = message.GetContentAsString(1);
			byte[] errorMsg = new byte[0];
			filePath = AppendRootToFilePath(filePath);

			// What happens if the user sends bad data:
			// bad file path, file size smaller than file they send, etc
			if (filePath == string.Empty)
			{
				Exception exception = new Exception("Empty file path");
				errorMsg = GetErrorBytes("RespondToSendFileRequest", exception, null);
				SendFinalSendFileMessageResponse(errorMsg, target, message.ChannelId);
				return;
			}

			try
			{
                if (!mFileBuffers.Contains(GenerateFileBufferId(message.ChannelId, filePath)))
                {
					// This is also being passed as a binary in the value/content portion of 2
					uint fileSize = Convert.ToUInt32((string)message.GetKey(2));
					Trace.WriteLine("Send File Transaction has commenced.  File to be sent has " + fileSize + " bytes.");
					bool overwrite = true;
					if (message.NumberOfKeys > 3)
					{
						if (message.GetKey(3) == "append")
							overwrite = false;
					}
					FileBuffer fileBuffer = new FileBuffer(fileSize, filePath, overwrite, true);
					mFileBuffers.Add(GenerateFileBufferId(message.ChannelId, filePath), fileBuffer);
				}
				else
				{
                    FileBuffer fileBuffer = (FileBuffer)mFileBuffers[GenerateFileBufferId(message.ChannelId, filePath)];
                    fileBuffer.AddBytes(message.GetContentAsBinary(2));
					if (fileBuffer.IsFull)
					{
						mFileBuffers.Remove(GenerateFileBufferId(message.ChannelId, filePath));
						fileBuffer.WriteFile();
						// If we want the FileTalkServer to immediately release the memory of a file 
						// (that was sent to it) then we can use the following:
						/*System.GC.WaitForPendingFinalizers();
						System.GC.Collect();
						*/
						SendFinalSendFileMessageResponse(errorMsg, target, message.ChannelId);
					}
				}
			}
			catch (Exception exception)
			{
				errorMsg = GetErrorBytes("RespondToSendFileRequest", exception, null);
				SendFinalSendFileMessageResponse(errorMsg, target, message.ChannelId);
			}
		}

        /// <summary>
        /// Note: We have to compare the enum string for the Init message since that is the message
        /// that will set the big or little endian so we can properly determine the enum value
        /// </summary>
        /// <param name="gameTalkMessage"></param>
		private void HandleConfigMessage(GameTalkMessage gameTalkMessage)
		{
            int enumIndex = GetEnumIndex((byte[])gameTalkMessage.GetContent(0));
            if (enumIndex == (int)FileTalkMessage.MessageID.SetClientFileRoot)
			{
				// Add client file root
				SetFilePathRoot(gameTalkMessage.GetContentAsString(1));
			}
            else if (enumIndex == (int)FileTalkMessage.MessageID.RemoveClientFileRoot)
            {
				// Remove client file root
				SetFilePathRoot(null);
			}
			else if (enumIndex == (int)FileTalkMessage.MessageID.SetTentativeFileRoot)
			{
				if (gameTalkMessage.NumberOfKeys != 3)
					return;

				string clientFileRootToChange = gameTalkMessage.GetKey(2);
				if (clientFileRootToChange == this.mTarget)
				{
					SetFilePathRoot(gameTalkMessage.GetContentAsString(1));
					return;
				}

				if (mGetClientContextDelegate != null)
				{
					ClientContext clientContext = mGetClientContextDelegate(clientFileRootToChange);
					if (clientContext != null) // I don't actually think it is possible for this to be null
						clientContext.TentativeFileRoot = gameTalkMessage.GetContentAsString(1);
				}
			}
			else if (enumIndex == (int)FileTalkMessage.MessageID.AcceptTentativeFileRoot)
			{
				if (gameTalkMessage.NumberOfKeys != 2)
					return;

				// Not sure if it is necessary to check if TentativeFileRoot is null
				if (TentativeFileRoot != null)
					SetFilePathRoot(TentativeFileRoot);
			}
			else if (gameTalkMessage.GetKey(0) == FileTalkMessage.MessageID.InitMessage.ToString())
			{
				// This part of the message is used to tell us whether to use big or little endian
				byte[] oneToDetermineEndianness = (byte[])gameTalkMessage.GetContent(1);
				if (oneToDetermineEndianness[oneToDetermineEndianness.Length - 1] != 1)
					mBigEndian = false;

				mFileNotificationResponse.BigEndian = mBigEndian;
			}
			else
			{
				// Don't understand the gameTalkMessage - ignore it
				Trace.WriteLine("ClientContext.HandleConfigMessage - gameTalkMessage not understood: " + gameTalkMessage.ToString() + Environment.NewLine);
			}
		}

		/// <summary>
		/// Each time ClientContext thread grabs a GameTalkMessage from the queue - it hands it to this method
		/// </summary>
		/// <param name="gameTalkMessage"></param>
		private void ProcessMessage(GameTalkMessage gameTalkMessage)
		{
			int enumIndex = GetEnumIndex((byte[])gameTalkMessage.GetContent(0));
			if (enumIndex == (int)FileTalkMessage.MessageID.FileInfoRequest)
			{
				RespondToFileInfoRequest(gameTalkMessage);
			}
			else if (enumIndex == (int)FileTalkMessage.MessageID.GetFileRequest)
			{
				RespondToGetFileRequest(gameTalkMessage);
			}
			else if (enumIndex == (int)FileTalkMessage.MessageID.SendFileRequest)
			{
				RespondToSendFileRequest(gameTalkMessage);
			}
			else if (enumIndex == (int)FileTalkMessage.MessageID.FileNotificationRequest)
			{
				try
				{
					mFileNotificationResponse.RespondToFileNotification(gameTalkMessage);
				}
				catch (Exception exception)
				{
					byte[] errorMsg = GetErrorBytes("ProcessMessage", exception, null);
					GameTalkMessage messageResponse = new GameTalkMessage(gameTalkMessage.ChannelId);
#if(DEBUG)
					// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
					messageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.FileNotificationResponse.ToString(), GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileNotificationResponse, mBigEndian));
#else
					messageResponse.AddKeyContentAsBinary("", GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileNotificationResponse, mBigEndian));
#endif
					messageResponse.AddKeyContentAsBinary("", errorMsg);
					// We tag on two more Key/Value pairs because FileNotificationTransactions expect at least 3 parts to a GameTalkMessage
					messageResponse.AddKeyContentAsString("", "");
					messageResponse.AddKeyContentAsString("", "");
					GameTalkManager.Instance.Send(mTarget, messageResponse);
				}
			}
			else if (gameTalkMessage.ChannelId == FileTalkMessage.sFileTalkConfigChannel)
			{
				HandleConfigMessage(gameTalkMessage);
			}
			else
			{
				// Don't understand the gameTalkMessage - ignore it
				Trace.WriteLine("ClientContext.ProcessMessage - ClientContext: "+mTarget+" received gameTalkMessage it didn't understand: " + gameTalkMessage.ToString() + Environment.NewLine);
			}
		}

		public void Start()
		{
			mRun = true;
			while (mRun)
			{
				try
				{
					GameTalkMessage gameTalkMessage = null;
					lock (mGameTalkMessagesToProcess)
					{
						if ((mGameTalkMessagesToProcess.Count == 0) && (mRun))
							Monitor.Wait(mGameTalkMessagesToProcess);

						gameTalkMessage = mGameTalkMessagesToProcess.Dequeue();
					}
					if (gameTalkMessage == null) // if we don't have a non-null GameTalkMessage - FileTalkServer may be stopped or simply something bad happened - slow your roll
						System.Threading.Thread.Sleep(5);
					else
						ProcessMessage(gameTalkMessage);
				}
				catch (Exception exception)
				{
					// We can get in here in whenever a ClientContext gets shutdown/stopped
					// Since this is more rare than any other reason we'd get a Monitor.Pulse - this can be handled as an exception
					// This exception could be avoided by adding another check for whether the mGameTalkMessagesToProcess.Count is greater than zero
					// This is here to avoid "tight-looping" and thereby making FileTalkServer processor intensive when it isn't doing anything useful
					System.Threading.Thread.Sleep(5);
					Trace.WriteLine("ClientContext.Start(): Exception: " + exception.Message + Environment.NewLine + exception.StackTrace);
				}
			}
		}

		public void Stop()
		{
			mRun = false;
			lock (mGameTalkMessagesToProcess)
			{
				Monitor.Pulse(mGameTalkMessagesToProcess);
			}
		}

		public void AddMessageToQueue(GameTalkMessage message)
		{
			lock (mGameTalkMessagesToProcess)
			{
				mGameTalkMessagesToProcess.Enqueue(message);
				Monitor.Pulse(mGameTalkMessagesToProcess);
			}
		}

		public void SetFilePathRoot(string filePathRoot)
		{
			mFileRoot = filePathRoot;
		}

		public string TentativeFileRoot
		{
			get { return mTentativeFileRoot; }
			set { mTentativeFileRoot = value; }
		}

		private string AppendRootToFilePath(string filePath)
		{
			if (mFileRoot != null)
			{
				filePath = Path.Combine(mFileRoot, filePath);
			}
			return filePath;
		}

		public void DeregisterAndClear()
		{
            // Clear the file buffers - in case there have been incomplete file sends 
            mFileBuffers.Clear();

            // Clear any pending GameTalkMessages
            lock (mGameTalkMessagesToProcess)
            {
                mGameTalkMessagesToProcess.Clear();
            }

            // Deregister and clear file notifiers
			mFileNotificationResponse.DeregisterListeners(null);
			mFileNotificationResponse.ClearNotificationQueue(null);

            // This is just precautionary and may be unnecessary - but since this
            // client thread is being shut-down it shouldn't adversely effect 
            // FileTalkServer performance
            System.GC.WaitForPendingFinalizers();
            System.GC.Collect();
		}
	}

	/// <summary>
	/// FileBuffer objects are the server's way of building up a file from 
	/// separate messages from a particular client/target
	/// 
	/// Note: It would be dangerous/unwise to have a FileStream member that you open
	/// when the first FileBuffer is first created (in the constructor) and then which 
	/// is closed only once all bytes are received.  Since multiple transactions could 
	/// end up writing to the same file in an interleaving manner.
	/// </summary>
	internal class FileBuffer
	{
		// Members needed by non-streaming mode
		private byte[] mByteArray;

		// Members needed by both modes
		private bool mOverwrite = true;
		private string mFilePath;
		private bool mStreamingMode = true;
		// Made this an int since <Array>.Length returns int
		private int mByteArrayIndex = 0;
		private uint mByteArraySize = 0;

		private FileStream mFileStream = null;
		private System.Timers.Timer mCloseFileTimer = null;

		public FileBuffer(uint byteArraySize, string filePath, bool overwrite, bool streamingMode)
		{
			mByteArraySize = byteArraySize;
			mStreamingMode = streamingMode;
			mFilePath = filePath;
			mOverwrite = overwrite;
			if (mOverwrite)
				mFileStream = new FileStream(mFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, (int)mByteArraySize);
			else
				mFileStream = new FileStream(mFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite, (int)mByteArraySize);

			if (!mStreamingMode)
				mByteArray = new byte[byteArraySize];

			mCloseFileTimer = new System.Timers.Timer(1000);
			mCloseFileTimer.Elapsed += new System.Timers.ElapsedEventHandler(CloseFileStream);
		}

		void CloseFileStream(object sender, System.Timers.ElapsedEventArgs e)
		{
			CloseFileStream();
		}

		private void CloseFileStream()
		{
			try
			{
				// The following isn't really thread-safe (in regards to multiple timer threads getting in here) - but we lock on mFileStream - so shouldn't be a big deal (since we ignore any exceptions)
				mCloseFileTimer.Stop();
				if (mFileStream != null)
				{
					lock (mFileStream)
					{
						mFileStream.Close();
						mFileStream = null;
					}
				}
			}
			catch { }
		}

		private void CreateDirectoryIfNotExistent()
		{
			int lastDirSeparatorIndex = mFilePath.LastIndexOf(Path.DirectorySeparatorChar);
			if (lastDirSeparatorIndex != -1)
			{
				// We have a path with at least one directory separator in it
				string directory = mFilePath.Substring(0, lastDirSeparatorIndex);
				DirectoryInfo dirInfo = new DirectoryInfo(directory);
				if (!dirInfo.Exists)
					dirInfo.Create();
			}
		}

		/// <summary>
		/// This was previously opening and closing the FileStream between writes
		/// This was found to be inefficient - more evident on larger files (120 megs or more)
		/// </summary>
		/// <param name="byteArray"></param>
		public void AddBytes(byte[] byteArray)
		{
			mCloseFileTimer.Stop();
			if (!mStreamingMode)
			{
				Array.Copy(byteArray, 0, mByteArray, mByteArrayIndex, byteArray.Length);
				mByteArrayIndex += byteArray.Length;
			}
			else
			{
				CreateDirectoryIfNotExistent();
				if (mFileStream == null)
				{
					if ((mOverwrite) && (mByteArrayIndex == 0))
						mFileStream = new FileStream(mFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, byteArray.Length);
					else if (mFileStream == null)
						mFileStream = new FileStream(mFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite, byteArray.Length);
				}
				lock (mFileStream)
				{
					mFileStream.Write(byteArray, 0, byteArray.Length);
				}
				mByteArrayIndex += byteArray.Length;
				if (IsFull)
					CloseFileStream();
			}
			mCloseFileTimer.Start();
		}

		public bool IsFull
		{
			get
			{
				return mByteArrayIndex == mByteArraySize;
			}
		}

		public void WriteFile()
		{
			if (mStreamingMode)
				return;

			CreateDirectoryIfNotExistent();

			FileStream fileStream;
			if (mOverwrite)
				fileStream = new FileStream(mFilePath, FileMode.Create);
			else
				fileStream = new FileStream(mFilePath, FileMode.Append);

			fileStream.Write(mByteArray, 0, mByteArray.Length);
			fileStream.Flush();
			fileStream.Close();
		}
	}
}
