// (c) 2005 Electronic Arts Inc.
//

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

using EA.GameTalkMessages;

namespace EA.GameTalkRouter
{
	/// <summary>
	/// Listener is an abstract class that is used directly by the GameTalkRouter to manage its connections/targets
	/// </summary>
	public abstract class Listener
	{
		/// <summary>
		/// Variables that are accessed by other classes indirectly.
		/// </summary>
		protected volatile bool mProcessDataFromWire = true;
		protected Thread mClientListenerThread = null;
		protected Thread mClientProcessThread = null;

		protected string mTargetName = string.Empty;
		protected string mTargetNameLowerCase = string.Empty;
		protected string[] mTargetNameArray = new string[0];

		protected string mPlatformName = string.Empty;
		protected string mVersion = string.Empty;

		protected string mUniqueName = string.Empty;

		protected HashSet<string> mChannels = new HashSet<string>();

		/// <summary>
		/// Is our client connected
		/// </summary>
		abstract public bool Connected
		{
			get;
		}

		abstract public string RemoteEndPoint
		{
			get;
		}

		/// <summary>
		/// Method that returns the state of this listener.
		/// </summary>
		public virtual bool IsMarkedForDeletion
		{
			get { return !mProcessDataFromWire; }
			// We add the setter so that RegisteredTcpSocket can override it
			set
			{
				// The ShutdownAndMarkForDeletion method will set mProcessDataFromWire to false
			}
		}

		/// <summary>
		/// Get the target name for this connection
		/// </summary>
		public string TargetName
		{
			get { return mTargetName; }
		}

		/// <summary>
		/// Get the platform name for this connection
		/// </summary>
		public string PlatformName
		{
			get { return mPlatformName; }
		}

		/// <summary>
		/// Get the version for this connection
		/// </summary>
		public string Version
		{
			get { return mVersion; }
		}

		/// <summary>
		/// Get the target name (in lower case) for this connection
		/// </summary>
		public string TargetNameLowerCase
		{
			get { return mTargetNameLowerCase; }
		}

		/// <summary>
		/// Get the target name array for this connection - just a pre-parsed (using ".") version of the TargetName
		/// </summary>
		public string[] TargetNameArray
		{
			get { return mTargetNameArray; }
		}

		public string UniqueName
		{
			get 
			{
				return mUniqueName;
			}
		}

		/// <summary>
		/// This adds a channel to this connection's list of server-side channels
		/// </summary>
		/// <param name="channelName"></param>
		public void AddChannel(string channelName)
		{
			if (!mChannels.Contains(channelName))
				mChannels.Add(channelName);
		}

		/// <summary>
		/// This checks whether the requested channel name is registered for this connection
		/// </summary>
		/// <param name="channelName"></param>
		/// <returns></returns>
		public bool ContainsChannel(string channelName)
		{
			return mChannels.Contains(channelName);
		}

		protected void ParseTargetName()
		{
			mTargetNameLowerCase = mTargetName.ToLower();
			// Parse the name into separate strings for later partial string matching
			mTargetNameArray = mTargetNameLowerCase.Split(new char[] { '.' });
		}

		/// <summary>
		/// This method is invoked immediately after this Listener is added to the list of them in the TcpServer.  This way we can 
		/// broadcast messages (through the TcpServer) and we can certain that our messages will reach this Listener.
		/// </summary>
		/// <param name="sendTargetTheirName">In cases where the client was a "registered client" - that we connect to (as opposed to 
		/// the client connecting to the GameTalkServer) we need to tell those clients their name</param>
		public virtual void OnReceiptOfConnectorsName(bool sendTargetTheirName)
		{
			if (mTargetName == null)
				mTargetName = string.Empty;

			ParseTargetName();
			// Log this connector's name
			string message = this.RemoteEndPoint + ":" + mTargetName + ":" + mPlatformName + ":" + mVersion;
			LoggingEventArgs e = new LoggingEventArgs(LoggingEventType.LogConnectionName, message);
			ServerEventManager.LoggingSend(this, e);

			// If we don't have the client's version by the time they send their name - either:
			// 1) this client is "old school" - thus needs to be registered to all channels
			// 2) this client has had the GameTalkServer initiate the connection to it - thus we'll need to unregister it from all channels if it subsequently sends us its version
			if (string.IsNullOrEmpty(mVersion))
			{
				// Register this client to all channels - this client might be "old school" and 
				// thus will not send the channels it wants to listen on to the GameTalkServer
				// It is possible that a RegisteredTcpSocket is already registered on "all channels" - necessary precaution
				ServerEventArgs serverEvent = new ServerEventArgs(ServerEventType.RegisterClientToAllChannels, mTargetNameLowerCase);
				ServerEventManager.Send(this, serverEvent);
			}

			// Broadcast this connection's name (AKA target name) to all connected clients
			GameTalkMessage broadcastGameTalkMessage = new GameTalkMessage(GameTalkMessage.sServerMessageID);
			broadcastGameTalkMessage.AddKeyContentAsString(GameTalkMessage.ClientServerMessages.ConnectedTargetMessage.ToString(), string.Empty);
			broadcastGameTalkMessage.AddKeyContentAsString(mTargetName, TcpServer.GenerateTargetMetaData(mPlatformName, mVersion));
			MessageEventArgs messageEvent = new MessageEventArgs(ServerEventType.BroadcastMessage, this.mTargetName, "*!", broadcastGameTalkMessage);
			ServerEventManager.Send(this, messageEvent);

			if (sendTargetTheirName)
			{
				// Tell this client their name
				GameTalkMessage sendClientNameMessage = new GameTalkMessage(GameTalkMessage.sServerMessageID);
				sendClientNameMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.UpdateTargetName.ToString(), this.mTargetName);
				messageEvent = new MessageEventArgs(ServerEventType.BroadcastMessage, GameTalkMessage.sGameTalkServerID, this.mTargetName, sendClientNameMessage);
				ServerEventManager.Send(this, messageEvent);
			}

			// Tell this client of all currently connected clients
			TcpServer.SendListOfCurrentlyConnectedClients(this, mTargetName, GameTalkMessage.sServerMessageID);

			// We may have buffered messages for this newly connected client - send those buffered messages to him
			messageEvent = new MessageEventArgs(ServerEventType.SendBufferToTarget, null, this.mTargetName, null);
			ServerEventManager.Send(this, messageEvent);
		}

		protected virtual void OnSuccessfulConnection()
		{
			mUniqueName = this.RemoteEndPoint;

			LoggingEventArgs guiEvent = new LoggingEventArgs(LoggingEventType.LogConnection, mUniqueName);
			ServerEventManager.LoggingSend(this, guiEvent);
		}

		protected void OnDisconnection()
		{
			LoggingEventArgs e = new LoggingEventArgs(LoggingEventType.LogDisconnection, this.RemoteEndPoint);
			ServerEventManager.LoggingSend(this, e);

			if (mTargetName != null) // This connection hasn't been initialized properly - therefore, we haven't sent a connection message to our other clients - therefore there is no need to notify them of the disconnection
			{
				// Broadcast this connection getting disconnected to all connected clients
				GameTalkMessage broadcastGameTalkMessage = new GameTalkMessage(GameTalkMessage.sServerMessageID);
				broadcastGameTalkMessage.AddKeyContentAsString(GameTalkMessage.ClientServerMessages.DisconnectedTargetMessage.ToString(), string.Empty);
				broadcastGameTalkMessage.AddKeyContentAsString(mTargetName, TcpServer.GenerateTargetMetaData(mPlatformName, mVersion));
				MessageEventArgs messageEvent = new MessageEventArgs(ServerEventType.BroadcastMessage, this.mTargetName, "*!", broadcastGameTalkMessage);
				ServerEventManager.Send(this, messageEvent);
			}
		}

		/// <summary>
		/// Destructor.
		/// </summary>
		~Listener()
		{
			KillThisConnectionsThreads();
		}

		/// <summary>
		/// Poll the connection to make sure it is still alive
		/// 
		/// Potential optimization would be to record a date/time of the last time
		/// we received a message from this target or the last time we successfully
		/// sent a message (i.e. the last time we had successful communication with 
		/// the target).  If that was successful within the last second - then no need
		/// to ping.
		/// </summary>
		public virtual void Poll()
		{
			// Try pinging it ...
			GameTalkMessage message = new GameTalkMessage("PING");
			message.AddKeyContent("PING", "PING");
			SendMessage(null, message, false);
		}

		/// <summary>
		/// Start our various reader threads.
		/// </summary>
		public virtual void StartReaderThreads()
		{
			mClientListenerThread = new Thread(new ThreadStart(ReadThread));
			mClientListenerThread.Name = "Read Thread";
			mClientListenerThread.Start();

			mClientProcessThread = new Thread(new ThreadStart(ProcessBufferedData));
			mClientProcessThread.Name = "Process Thread";
			mClientProcessThread.Start();
		}


		/// <summary>
		/// If we've already been disconneted from a client then we've probably already logged the reason
		/// No reason to spam the output
		/// </summary>
		/// <param name="message"></param>
		protected void LogMessage(string message)
		{
			if (!mProcessDataFromWire || (!this.Connected)) // If the target is already disconnected (or is about to be disconnected) - we probably don't need to tell the user why the message didn't go through
				return;

			LoggingEventArgs m = new LoggingEventArgs(LoggingEventType.LogComment, message);
			ServerEventManager.LoggingSend(this, m);
		}

		protected abstract void SendData(byte[] buffer, int offset, int size, GameTalkMessage message);
		protected abstract int ReceiveData(byte[] buffer, int offset, int size);

		protected void SerializeAndAsyncSend(string sender, GameTalkMessage message, bool logMessage)
		{
			try
			{
				// We could add the following condition if we want to try to ensure that GameTalk clients that predate 3.0 don't get sender data tagged on
				// if (mVersion is 3.0.0 or greater)
				if (string.IsNullOrEmpty(sender) == false)
				{
					byte[] senderBytes = System.Text.ASCIIEncoding.Default.GetBytes(sender);
					byte[] senderBytesWithNullDelimiter = new byte[senderBytes.Length + 1];
					Array.Copy(senderBytes, senderBytesWithNullDelimiter, senderBytes.Length);
					senderBytesWithNullDelimiter[senderBytes.Length] = 0;

					message.AddChunkData(senderBytesWithNullDelimiter, (UInt32)GameTalkMessage.InternalType.Sender);
				}

				// With a bit of work we could pass in a few variables to GameTalkMessage.GetBuffer and reduce the amount of byte array copying currently being done
				byte[] messageBuffer = message.GetBuffer();
				int totalSize = 4 + messageBuffer.Length;
				MemoryStream messageStream = new MemoryStream(totalSize);
				BinaryWriter binaryWriter = new BinaryWriter(messageStream);
				binaryWriter.Write((int)(messageBuffer.Length));
				binaryWriter.Write(messageBuffer);

				// The following is optimized for high volume data transfers
				// For reduced size messages - use messageStream.ToArray
				// We could even add a conditional here for data sends less than 256 bytes
				byte[] byteData = messageStream.GetBuffer();
				this.SendData(byteData, 0, byteData.Length, message);

				if (logMessage)
					Logger.LogSentMessage(sender, TargetNameLowerCase, message);
			}
			catch (ThreadInterruptedException)
			{
			}
			catch (Exception e)
			{
				string targetName = "";
				string uniqueName = "";
				try
				{
					targetName = this.mTargetName;
					uniqueName = this.mUniqueName;
				}
				catch { }

				LogMessage("SendMessage exception: There was an issue sending to " + targetName + " at " + uniqueName + ".  Further details: " + e.Message);
			}
		}

		/// <summary>
		/// Send a message to the client
		/// 
		/// This could be a good candidate for a thread-safe queue - that uses Monitor Pulse/Wait
		/// Currently we don't spawn a new thread until we actually try to send over the wire
		/// </summary>
		/// <param name="message">The message to send to the client</param>
		public virtual void SendMessage(string sender, GameTalkMessage message, bool logMessage)
		{
			SerializeAndAsyncSend(sender, message, logMessage);
		}

		/// <summary>
		/// If the keyword messages isn't in our buffer yet - this will reverse the mSizeRead
		/// index and set mIncompleteMessage accordingly
		/// </summary>
		/// <param name="bufferBinaryReader"></param>
		/// <param name="idFromWire">This parameter is used exclusively for meaningful logs</param>
		/// <returns></returns>
		protected string ProcessKeywordMessage(BinaryReader bufferBinaryReader, string idFromWire, 
			int sizeRead, int bufferSize)
		{
			string keywordValueStr = null;
			// Check that we have another 4 bytes in the buffer
			if ((sizeRead + 4) > bufferSize)
			{
				LogMessage("A supposed keyword message (" + idFromWire + ") has its message size recorded incorrectly - no room allocated for value portion of message.");
				// This should be considered a fatal error and we should kill this Listener at this point
				return null;
			}
			// Get the message
			int messageLength = bufferBinaryReader.ReadInt32();
			sizeRead += 4;
			if (messageLength + sizeRead > bufferSize)
			{
				LogMessage("A supposed keyword message (" + idFromWire + ") has its message size recorded incorrectly - recorded message value length is smaller than actual message value length.");
				// This should be considered a fatal error and we should kill this Listener at this point
				return null;
			}
			byte[] messageBytes = bufferBinaryReader.ReadBytes(messageLength);
			sizeRead += messageLength;
			if (messageBytes.Length > 0)
				keywordValueStr = System.Text.Encoding.ASCII.GetString(messageBytes);

			return keywordValueStr;
		}

		protected virtual void ShutdownAndMarkForDeletion()
		{
			lock (mPacketWriteBuffer)
			{
				mProcessDataFromWire = false;
				Monitor.Pulse(mPacketWriteBuffer);
			}
		}

		private void RemoveListenerFromAllChannels()
		{
			if ((!string.IsNullOrEmpty(mVersion)) && (!string.IsNullOrEmpty(mTargetName)))
			{
				// Unregister this client from all channels - this client is new enough to know that it should send us its 
				// version - therefore it is new enough to know that it should send us the channels it wants to be registered on
				ServerEventArgs serverEvent = new ServerEventArgs(ServerEventType.UnregisterClientFromAllChannels, mTargetNameLowerCase);
				ServerEventManager.Send(this, serverEvent);
			}
		}

		protected bool ProcessAsKeyword(BinaryReader bufferBinaryReader, string idFromWire,
			int sizeRead, int bufferSize)
		{
			bool keywordProcessed = true;
			if (string.Compare(idFromWire, "initialize", true) == 0)
			{
				// In passive mode (where the target name is given to us upon creation of the Listener) we could ignore the target 
				// name from the target because we have to override it with the name that is used in the target manager (to allow 
				// multiple targets on different consoles using the same game)
				mTargetName = ProcessKeywordMessage(bufferBinaryReader, idFromWire, sizeRead, bufferSize);
				if (mTargetName != null)
					OnReceiptOfConnectorsName(false);
				else
					mTargetName = string.Empty;
			}
			else if (string.Compare(idFromWire, GameTalkMessage.sPlatformKeywordMessage, true) == 0)
			{
				string platformName = ProcessKeywordMessage(bufferBinaryReader, idFromWire, sizeRead, bufferSize);
				this.mPlatformName = (platformName == null) ? string.Empty : platformName;
			}
			else if (string.Compare(idFromWire, GameTalkMessage.sVersionKeywordMessage, true) == 0)
			{
				mVersion = ProcessKeywordMessage(bufferBinaryReader, idFromWire, sizeRead, bufferSize);
				// Not sure if there is any point in checking whether mTargetName is null - shouldn't be possible - should I log that as an error?
				if ((!string.IsNullOrEmpty(mVersion)) && (!string.IsNullOrEmpty(mTargetName)))
				{
					// If the mTargetName is not empty then it is likely this was a registered client who was connected then it
					// sent the server its version - thus we need to refresh its entry in the GameTalkMonitor
					string message = this.RemoteEndPoint + ":" + mTargetName + ":" + mPlatformName + ":" + mVersion;
					LoggingEventArgs e = new LoggingEventArgs(LoggingEventType.LogConnectionName, message);
					ServerEventManager.LoggingSend(this, e);
					// If we already have the target's name - then this _should_ be a RegisteredTcpSocket that has sent us its version
					RemoveListenerFromAllChannels();
				}
			}
			else if (string.Compare(idFromWire, "shutdown", true) == 0)
				ShutdownAndMarkForDeletion();
			else
				keywordProcessed = false;

			return keywordProcessed;
		}

		protected class RepeatedSend
		{
			private Thread mThread;
			private volatile bool mRun;

			private string mTargetName;
			private GameTalkMessage mGtm;
			private int mFrequency = 1;

			public RepeatedSend(string targetName, GameTalkMessage gtm, int frequency)
			{
				mTargetName = targetName;
				mGtm = gtm;
				mFrequency = frequency;
			}

			private void SendEngine()
			{
				while (mRun)
				{
					// We reinstantiate the MessageEventArgs each iteration since we track who've we sent this message to in the contents of this MessageEventArgs
					MessageEventArgs messageEventArgs = new MessageEventArgs(ServerEventType.BroadcastMessage, "GameTalkServer", mTargetName, mGtm);
					ServerEventManager.Send(this, messageEventArgs);

					Thread.Sleep(mFrequency);
				}
			}

			public void Start()
			{
				mThread = new Thread(new ThreadStart(SendEngine));
				mRun = true;
				mThread.Start();
			}

			public void Stop()
			{
				mRun = false;
				try
				{
					if (mThread != null)
						mThread.Join(mFrequency + 1000);
				}
				catch { }
				finally
				{
					mThread = null;
				}
			}
		}

		protected Dictionary<string, RepeatedSend> mRepeatedSendThreads = new Dictionary<string, RepeatedSend>();
		protected bool TcpServerNotRequired(MessageEventArgs messageEvent)
		{
			bool handledLocally = true;
			string fromTargetLower = messageEvent.FromTargetLowerCase;
			GameTalkMessage gtm = messageEvent.Message;
			if (gtm.NumberOfKeys < 2) // All "config" messages have at least two key/content pairs
				return false;

			if (gtm.GetKey(0) == GameTalkMessage.ClientServerMessages.StartRepeatedSends.ToString())
			{
				string frequencyOfSendsStr = gtm.GetContentAsString(0);
				int frequencyOfSends = Convert.ToInt32(frequencyOfSendsStr);
				byte[] clientMessageBytes = gtm.GetContentAsBinary(1);
				GameTalkMessage clientMessage = new GameTalkMessage(clientMessageBytes);
				string toTarget = gtm.GetKey(1);
				RepeatedSend repeatedSend = new RepeatedSend(toTarget, clientMessage, frequencyOfSends);
				string repeatedSendName = toTarget + clientMessage.ToString();
				mRepeatedSendThreads.Add(repeatedSendName, repeatedSend);
				repeatedSend.Start();
			}
			else if (gtm.GetKey(0) == GameTalkMessage.ClientServerMessages.StopRepeatedSends.ToString())
			{
				byte[] clientMessageBytes = gtm.GetContentAsBinary(1);
				GameTalkMessage clientMessage = new GameTalkMessage(clientMessageBytes);
				string toTarget = gtm.GetKey(1);
				RepeatedSend repeatedSend;
				string messageTargetKey = toTarget + clientMessage.ToString();
				if (mRepeatedSendThreads.TryGetValue(messageTargetKey, out repeatedSend))
				{
					repeatedSend.Stop();
					mRepeatedSendThreads.Remove(messageTargetKey);
				}
				else
					Logger.LogCommentMessage("Wasn't able to find a \"repeated send\" thread being sent to" + toTarget + " with message: " + clientMessage.ToString());
			}
			else if (gtm.GetKey(0) == GameTalkMessage.ClientServerMessages.SecureTrust.ToString())
			{
				byte[] passphraseKeyBytes = GameTalkMessages.Configuration.Instance.GetPassPhraseKey();
				if (passphraseKeyBytes != null) // If the user is set up for secure trust
				{
					string passwordStrFromUser = gtm.GetContentAsString(0);
					byte[] passwordBytesFromUser = EA.GameTalkMessages.SymmetricCrypto.EncryptPassword(passwordStrFromUser);
					if (passwordBytesFromUser.Length == passphraseKeyBytes.Length)
					{
						bool passwordsMatch = true;
						for (int i = 0; i < passwordBytesFromUser.Length; i++)
						{
							if (passwordBytesFromUser[i] != passphraseKeyBytes[i])
							{
								passwordsMatch = false;
								break;
							}
						}

						if (passwordsMatch)
						{
							if (gtm.GetKey(1) == GameTalkMessage.ClientServerMessages.SecureTrustStartApp.ToString())
							{
								if (gtm.NumberOfKeys >= 3)
								{
									string applicationStr = gtm.GetContentAsString(1);
									string workingDir = gtm.GetKey(2);
									string commandLineParameters = gtm.GetContentAsString(2);
									// Without impersonation using named pipes or something - I don't think this is going to be feasible (for anything but the most trivial cases) - especially on Vista
									// Methods for potential use: 
									// 1. NamedPipeServerStream.RunAsClient or 
									// 2. ServiceSecurityContext.Current.WindowsIdentity.Impersonate or
									// 3. Log a special message - that GameTalkMonitor will interpret and execute as a process
									string message = string.Format("{0}|?|{1}|?|{2}|?|{3}", DateTime.Now.ToString(), applicationStr, workingDir, commandLineParameters);
									LoggingEventArgs loggingEvent = new LoggingEventArgs(LoggingEventType.LogStartProcess, message);
									ServerEventManager.LoggingSend(null, loggingEvent);
								}
							}
						}
					}
				}
			}
			else
			{
				handledLocally = false;
			}
			return handledLocally;
		}

		protected void HandleGameTalkMessage(string target, byte[] messageBytes)
		{
			// Decode message
			GameTalkMessage message = null;
			if (target == GameTalkMessage.sGameTalkServerID)
			{
				message = new GameTalkMessage(messageBytes);
				// If we get a message on the "ping channel" - just ignore it - the client is just testing their connection
				if (message.ChannelId == GameTalkMessage.sGameTalkServerPingID)
					return;
			}
			else if (ConfigReader.Instance.LogLevel != GameTalkMessages.LoggingLevel.FULL_LOGGING)
				message = new GameTalkMessage(messageBytes, false);
			else
				message = new GameTalkMessage(messageBytes);

			// Logged the received message before we send it
			Logger.LogReceivedMessage(this.mTargetNameLowerCase, target.ToLower(), message);

			MessageEventArgs messageEvent = null;
			// We could also check whether this is "addressed" to the GameTalkServer
			if (message.ChannelId == GameTalkMessage.sClientMessageID)
			{
				try
				{
					if (message.NumberOfKeys > 0) // a message with no key/value pairs is useless for client config
					{
						// This is a client message - we don't necessarily want to broadcast it
						messageEvent = new MessageEventArgs(ServerEventType.ClientConfigRequest, this.mTargetName, target, message);
						if (TcpServerNotRequired(messageEvent)) // See if the Listener can handle this on its own
							return;
					}
				}
				catch (Exception exception)
				{
					TcpServer.DebugLog("[Listener.HandleGameTalkMessage] exception: " + exception);
				}
			}
			else
			{
				// Broadcast message to intended target(s)
				messageEvent = new MessageEventArgs(ServerEventType.BroadcastMessage, this.mTargetName, target, message);
			}

			if (messageEvent != null)
				ServerEventManager.Send(this, messageEvent);
		}

		protected List<byte[]> mPacketWriteBuffer = new List<byte[]>();

		protected List<byte[]> PullPacketsFromWriteBufferToReadBuffer()
		{
			List<byte[]> packetReadBuffers = null;
			try
			{
				lock (mPacketWriteBuffer)
				{
					// If we have no data in the mPacketWriteBuffer then wait on the Monitor
					if ((mPacketWriteBuffer.Count == 0) && (mProcessDataFromWire))
						Monitor.Wait(mPacketWriteBuffer);

					if (mProcessDataFromWire) // Check that we didn't get a Monitor.Pulse for shut-down
					{
						packetReadBuffers = mPacketWriteBuffer.GetRange(0, mPacketWriteBuffer.Count);
						mPacketWriteBuffer.Clear();
					}
				} // end of lock (mPacketWriteBuffer)
			}
			catch (ThreadInterruptedException)
			{
			}
			catch (Exception ex)
			{
				LogMessage("Exception while processing message (copying from main buffer): " + ex.Message);
				// If we're getting in here repetitively - it would be safer to Sleep/Idle.
				// So as not to tight loop and make GTS processor intensive when idle.
				Thread.Sleep(TcpServer.sExceptionHandlerIdleTime);
			}
			return packetReadBuffers;
		}

		protected void ProcessPacketsFromReadBuffer(byte[] packetReadBuffer)
		{
			MemoryStream bufferMemStream = null;
			BinaryReader bufferBinaryReader = null;
			try
			{
				bufferMemStream = new MemoryStream(packetReadBuffer);
				bufferBinaryReader = new BinaryReader(bufferMemStream);
				// Get the length of the entire message, always the first four bytes
				int totalMessageLength = bufferBinaryReader.ReadInt32();
				//Debug.Assert(messageLength != 0, "How can we have a zero length GameTalkMessage?");
				if (totalMessageLength == 0)
				{
					LogMessage("A GameTalkMessage has been received that has a zero length - not going to continue processing.");
					// we're going to continue processing the message - since we still need to read the data that we've read into the memory stream - not entirely sure what will happen if we try to send a message to a target/client whose name is the empty string
					return;
				}
				else if (totalMessageLength < 8)
				{
					LogMessage("A GameTalkMessage has been received that less than eights bytes - not going to continue processing.");
					// we're going to continue processing the message - since we still need to read the data that we've read into the memory stream - not entirely sure what will happen if we try to send a message to a target/client whose name is the empty string
					return;
				}
				// Get the target
				string target = string.Empty;
				int targetLength = bufferBinaryReader.ReadInt32();
				int sizeRead = 8;
				if (targetLength == 0)
				{
					LogMessage("You're attempting to send a message without specifying a target/client (i.e. you've used the empty string for the target/client name).");
					// we're going to continue processing the message - since we still need to read the data that we've read into the memory stream - not entirely sure what will happen if we try to send a message to a target/client whose name is the empty string
				}
				
				//Debug.Assert(targetLength + 4 <= packetReadBuffer.Length, "The length of the target is apparently longer than the entire message - that is a problem");
				if (targetLength + sizeRead > packetReadBuffer.Length)
				{
					LogMessage("A non-keyword message (intended for " + target + ") had its size recorded incorrectly - the message key length is smaller than the actual message key length.");
					// This should be considered a fatal error - may want to kill this Listener at this point
					return;
				}

				byte[] targetBytes = bufferBinaryReader.ReadBytes(targetLength);
				sizeRead += targetBytes.Length;
				target = System.Text.Encoding.ASCII.GetString(targetBytes);
				if (ProcessAsKeyword(bufferBinaryReader, target, sizeRead, totalMessageLength))
					return;

				// Check that we have another 4 bytes in the buffer
				if (sizeRead + 4 > packetReadBuffer.Length)
				{
					LogMessage("A non-keyword message (intended for " + target + ") had its size recorded incorrectly - no room allocated for value portion of message.");
					// This should be considered a fatal error - may want to kill this Listener at this point
					return;
				}
				
				// Get the message
				int messageLength = bufferBinaryReader.ReadInt32();
				sizeRead += 4;

				if (sizeRead + messageLength > packetReadBuffer.Length)
				{
					LogMessage("A non-keyword message (intended for " + target + ") had its size recorded incorrectly - recorded message value length is smaller than actual message value length.");
					// This should be considered a fatal error - may want to kill this Listener at this point
					return;
				}

				byte[] messageBytes = bufferBinaryReader.ReadBytes(messageLength);
				sizeRead += messageLength;
				if (messageBytes.Length > 0)
					HandleGameTalkMessage(target, messageBytes);
			}
			catch (ThreadInterruptedException)
			{
			}
			catch (Exception ex)
			{
				LogMessage("Exception while processing message (parsing of message): " + ex.Message);
				if (ex is SocketException) // Might be safer to just assume all exceptions are "fatal" and disconnect regardless of the type of exception
					ShutdownAndMarkForDeletion();

				// If we're getting in here repetitively - it would be safer to Sleep/Idle.
				// So as not to tight loop and make GTS processor intensive when idle.
				Thread.Sleep(TcpServer.sExceptionHandlerIdleTime);
			}
			finally
			{
				if (bufferBinaryReader != null)
				{
					bufferBinaryReader.Close();
					bufferBinaryReader = null;
				}
				if (bufferMemStream != null)
				{
					bufferMemStream.Close();
					bufferMemStream = null;
				}
			}
		}

		/// <summary>
		/// The consumer thread's method
		/// </summary>
		protected void ProcessBufferedData()
		{
			try
			{
				while (mProcessDataFromWire)
				{
					List<byte[]> packetReadBuffers = PullPacketsFromWriteBufferToReadBuffer();

					// If nothing has been read - there is nothing to process
					if (packetReadBuffers == null)
						continue;

					foreach (byte[] packetReadBuffer in packetReadBuffers)
						ProcessPacketsFromReadBuffer(packetReadBuffer);
				} //while (mProcessDataFromWire)
			}
			catch (ThreadInterruptedException)
			{
			}
		}

		/// <summary>
		/// If the user isn't careful this method can actually shrink their receiving
		/// buffer which isn't likely what the user will want in the long-term
		/// </summary>
		/// <param name="buffer"></param>
		/// <param name="currentMessageLength"></param>
		/// <param name="startMsgIndex"></param>
		/// <param name="bytesReadFromTheWire"></param>
		/// <param name="endOfCurrentMessage"></param>
		/// <returns></returns>
		protected byte[] ResizeReceivingBuffer(byte[] buffer, int currentMessageLength,
			ref int startMsgIndex, ref int bytesReadFromTheWire, ref int endOfCurrentMessage)
		{
			byte[] tempBuffer = null;
			if (startMsgIndex > 0) // Just shift the bytes to the start of the array
			{
				tempBuffer = new byte[buffer.Length];
				Array.Copy(buffer, startMsgIndex, tempBuffer, 0, buffer.Length - startMsgIndex);
				bytesReadFromTheWire -= startMsgIndex;
				endOfCurrentMessage -= startMsgIndex;
				startMsgIndex = 0;
			}
			else // There is only one message - we have to increase the size of the buffer
			{
				tempBuffer = new byte[currentMessageLength];
				Array.Copy(buffer, 0, tempBuffer, 0, buffer.Length);
			}
			return tempBuffer;
		}

		/// <summary>
		/// Producer thread for reading data from the wire.
		/// </summary>
		protected void ReadThread()
		{
			int sizeBufferMax = 2 * 1024 * 1024;
			byte[] buffer = new byte[sizeBufferMax];
			// Potential performance improvement would be to store multiple GTMs in 
			// here and just provide an index for the location of each full GTM buffer
			byte[] singleGtmBuffer = new byte[0];
			int bytesReadFromTheWire = 0;
			int startMsgIndex = 0;
			int currentMessageLength = 0;
			int endOfCurrentMessage = 0;
			try
			{
				while (mProcessDataFromWire)
				{
					// Read the incoming buffer - we can incrementally update the third parameter (instead of recomputing it every time)
					int numBytesToRead = buffer.Length - bytesReadFromTheWire;
					if (numBytesToRead == 0)
					{
						LogMessage("Somehow the dynamic buffer hasn't been resized and there is insufficient room for any new data - buffer.Length: " + buffer.Length + " bytesReadFromTheWire: " + bytesReadFromTheWire + " startMsgIndex: " + startMsgIndex + " currentMessageLength: " + currentMessageLength + " endOfCurrentMessage: " + endOfCurrentMessage);
						ShutdownAndMarkForDeletion();
						return;
					}
					int size = this.ReceiveData(buffer, bytesReadFromTheWire, numBytesToRead);
					bytesReadFromTheWire += size;
					do // Need to iterate here to digest potentially multiple messages received in a single pull from the wire
					{
						if ((currentMessageLength == 0) && (bytesReadFromTheWire >= 4))
						{ // This means we aren't currently processing a message and there is sufficient room to determine the length of the current message
							currentMessageLength = BitConverter.ToInt32(buffer, startMsgIndex);
							// Check if we have a single message bigger than the remaining space in our buffer
							endOfCurrentMessage = startMsgIndex + currentMessageLength;
							if (endOfCurrentMessage > buffer.Length)
								buffer = ResizeReceivingBuffer(buffer, currentMessageLength, ref startMsgIndex, ref bytesReadFromTheWire, ref endOfCurrentMessage);

							// There is a way to create this array once and only recreate it when "resizing needs" arise
							singleGtmBuffer = new byte[currentMessageLength];
						}

						if ((endOfCurrentMessage == 0) || (endOfCurrentMessage > bytesReadFromTheWire)) // more to come
						{
							// is it more efficient to copy parts incrementally or to just do a one time copy?
							break;
						}
						else if (endOfCurrentMessage <= bytesReadFromTheWire)
						{
							Array.Copy(buffer, startMsgIndex, singleGtmBuffer, 0, currentMessageLength);
							lock (mPacketWriteBuffer)
							{
								mPacketWriteBuffer.Add(singleGtmBuffer);
								Monitor.Pulse(mPacketWriteBuffer);
							}

							if (endOfCurrentMessage == bytesReadFromTheWire)
							{
								// The last byte pulled from the wire was the last byte from the message just added to the queue - reset indices
								bytesReadFromTheWire = 0;
								startMsgIndex = 0;
							}
							else
								startMsgIndex += currentMessageLength;

							endOfCurrentMessage = 0;
							currentMessageLength = 0;
						}
					} while (bytesReadFromTheWire > startMsgIndex + 4);
				}
			}
			catch (ThreadInterruptedException)
			{
			}
			catch (Exception exception)
			{
				// Likely all that has happened is a client has disconnected
				// Without this catch - we end up with an "unhandled exception"
				if ((exception is SocketException) && (((SocketException)exception).SocketErrorCode == SocketError.NoBufferSpaceAvailable))
					LogMessage("Your entire PC's network buffer is full - you might try killing any applications that are network enabled to free up space in your network buffer.");

				System.Diagnostics.Trace.WriteLine("target: " + this.mTargetName + " unique name: " + this.UniqueName + " exception: " + exception.GetType() + ": " + exception.Message + Environment.NewLine + exception.StackTrace);
				ShutdownAndMarkForDeletion();
			}
		}

		/// <summary>
		/// Method that stops all threads (listening, processing, etc) associated with this connection.
		/// </summary>
		public virtual void KillThisConnectionsThreads()
		{
			// Wait for one second for the the thread to stop.
			if (mClientListenerThread != null)
			{
				// The following could increase the performance of shutting down the GameTalkRouter - add in later revision
				//mClientListenerThread.Interrupt();
				// Wait up to one second for the the thread to stop.
				bool result = mClientListenerThread.Join(1000);
				if (!result)
				{
					mClientListenerThread.Abort();
				}

				mClientListenerThread = null;
			}

			if (mClientProcessThread != null)
			{
				// The following could increase the performance of shutting down the GameTalkRouter - add in later revision
				//mClientProcessThread.Interrupt();
				// Wait up to one second for the the thread to stop.
				bool result = mClientProcessThread.Join(1000);
				if (!result)
				{
					mClientProcessThread.Abort();
				}

				mClientProcessThread = null;
			}

			// We need to stop all threads that are constantly sending messages
			try
			{
				foreach (RepeatedSend repeatedSend in mRepeatedSendThreads.Values)
				{
					try
					{
						repeatedSend.Stop();
					}
					catch { }
				}
				mRepeatedSendThreads.Clear();
			}
			catch { }
		}

		/// <summary>
		/// If mProcessDataFromWire is true - it means the RegisteredTcpSocket's reading/processing threads should be actively
		/// pulling/processing data from the wire (and thus it is connected)
		/// </summary>
		public bool IsAlive
		{
			get { return mProcessDataFromWire; }
		}
	}
}
