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

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Reflection;

using Microsoft.Win32;
using EA.GameTalkMessages;

namespace EA.GameTalk
{
	/// <summary>
	/// A manager for all the different type of connections
	/// </summary>
	public class GameTalkInstance : IGameTalkInstance
	{
		/// <summary>
		/// Delegate for when a message is received.
		/// </summary>
		public delegate void MessageReceivedDelegate(GameTalkMessage message);

		/// <summary>
		/// Delegate for when synchronous message is received.
		/// </summary>
		public delegate void SynchronousMessageReceivedDelegate(SyncReplyGameTalkMessage message, GameTalkMessage synchronousGtm);

		/// <summary>
		/// Different initialization options that can be passed into the constructor of a GameTalkInstance
		/// </summary>
		public enum InitializationOptions
		{
			/// <summary>
			/// Do not auto-install/start the GameTalkServer or the GameTalkMonitor
			/// </summary>
			NoAutoGameTalkServerOrMonitorInstall,
			/// <summary>
			/// Auto-install/start the GameTalkServer - but don't install/start the GameTalkMonitor
			/// </summary>
			NoAutoGameTalkMonitorInstall,
		}

		public class GameTalkConfig
		{
			// "Tool.GameTalk" might be a better default value for Target Name - but perhaps better for users to have their attention drawn to the fact they haven't set it
			private string mTargetName = "<no name>";
			/// <summary>
			/// The name that other clients of GameTalk will use to uniquely identify your client application (be it a game, tool or whatever)
			/// </summary>
			public string TargetName
			{
				get { return mTargetName; }
				set { mTargetName = value; }
			}

			private bool mStartServerAsApp = false;
			/// <summary>
			/// Whether we've asked the server to be started as a mundane application (instead of as a service)
			/// </summary>
			public bool StartServerAsApp
			{
				get { return mStartServerAsApp; }
				set { mStartServerAsApp = value; }
			}

			private bool mIsService = false;
			/// <summary>
			/// This lets GameTalk know if the process starting GameTalk is a service.  If it is a service starting GameTalk - then it will not try to install or start the 
			/// GameTalkMonitor - since Services shouldn't start stand-alone applications
			/// </summary>
			public bool IsService
			{
				get { return mIsService; }
				set { mIsService = value; }
			}

			private bool mMultiThreaded = true;
			/// <summary>
			/// This setting was added exclusively for a team that wanted the GameTalk tool-side to run in single-threaded mode.  They wanted this feature so that they
			/// could look at the value of .NET properties in the debugger (that would fetch data over the network) without the client reader, processing, etc threads running.
			/// In other words - this feature was added for an edge case - most users of GameTalk should NOT set this true - as it will diminish performance.
			/// </summary>
			public bool MultiThreaded
			{
				get { return mMultiThreaded; }
				set { mMultiThreaded = value; }
			}

			private string mServerName = "localhost";
			/// <summary>
			/// The hostname of the PC that has the GameTalkServer running on it - most users just want to connect to the GameTalkServer on their local machine and thus can leave
			/// this set to the default value of "localhost".
			/// </summary>
			public string ServerName
			{
				get { return mServerName; }
				set { mServerName = value; }
			}

			private int mPortNum = 9876;
			/// <summary>
			/// Network port to use to connect to the GameTalkServer (historically that has been set to 9876)
			/// </summary>
			public int PortNum
			{
				get { return mPortNum; }
				set { mPortNum = value; }
			}

			private bool mAutoInstallGameTalkServer = true;
			/// <summary>
			/// The auto-install of the GameTalkServer allows users of GameTalk to have their server auto-upgraded when a newer client is run on their machine
			/// </summary>
			public bool AutoInstallGameTalkServer
			{
				get { return mAutoInstallGameTalkServer; }
				set { mAutoInstallGameTalkServer = value; }
			}

			private bool mAutoInstallGameTalkMonitor = true;
			/// <summary>
			/// The auto-install of the GameTalkMonitor allows users of GameTalk to have their GameTalkMonitor auto-upgraded when a newer client is run on their machine
			/// </summary>
			public bool AutoInstallGameTalkMonitor
			{
				get { return mAutoInstallGameTalkMonitor; }
				set { mAutoInstallGameTalkMonitor = value; }
			}

			private string mPlatform = "PC";
			/// <summary>
			/// The platform name (PS2, Xenon, etc) - since we're using .NET - we'll assume PC
			/// </summary>
			public string Platform
			{
				get { return mPlatform; }
				set { mPlatform = value; }
			}

			private bool mInstallUsbPlugin = false;
			/// <summary>
			/// The USB Plugin allows users of GameTalk to send and receive GameTalkMessages over USB connections - but it is has been found to introduce performance issues
			/// even when users aren't sending or receiving any data over GameTalk - so disabled by default
			/// </summary>
			public bool InstallUsbPlugin
			{
				get { return mInstallUsbPlugin; }
				set { mInstallUsbPlugin = value; }
			}
		}

		/// <summary>
		/// Track the current Tcp client
		/// </summary>
		protected Connection mConnection = null;

		/// <summary>
		/// IP Address of the GameTalkServer
		/// </summary>
		private string mIPAddress = string.Empty;

		/// <summary>
		/// Users may have their IP address change while using their application
		/// This allows them the opportunity to update the IP address that the GameTalkInstance
		/// will attempt to connect to
		/// </summary>
		protected string IPAddress
		{
			get { return mIPAddress; }
			set { mIPAddress = value; }
		}

		/// <summary>
		/// Whether a custom port has been set - and thus should not be updated from the registry
		/// </summary>
		protected bool mCustomPort = false;

		/// <summary>
		/// Setter for port that also sets flag to indicate - port has been customized
		/// </summary>
		protected int Port
		{
			set
			{
				mGameTalkConfig.PortNum = value;
				mCustomPort = true;
			}
		}

		/// <summary>
		/// List of message handlers
		/// </summary>
		protected Dictionary<MessageReceivedDelegate, string> mMessageHandlers = new Dictionary<MessageReceivedDelegate, string>();

		/// <summary>
		/// Container of synchronous message handlers the user can register
		/// </summary>
		protected Dictionary<SynchronousMessageReceivedDelegate, string> mSynchroMessageHandlers = new Dictionary<SynchronousMessageReceivedDelegate, string>();

		/// <summary>
		/// Temporary store for synchronous GameTalkMessages 
		/// Messages added to this container are responses to a synchronous message that is sent over the network
		/// While one thread is blocking (waiting for a response) - the background GameTalk thread will pull the data from the socket and populate this container
		/// </summary>
		private Dictionary<uint, GameTalkMessage> mSynchronousResponseGtms = new Dictionary<uint, GameTalkMessage>();

		/// <summary>
		/// The message callback from the connection
		/// </summary>
		protected Connection.MessageReceivedDelegate mMessageCallback = null;

		private uint mSyncMsgUidCounter = 0;

		protected GameTalkConfig mGameTalkConfig = new GameTalkConfig();
		/// <summary>
		/// Whether we've asked the server to be started as a mundane application (instead of as a service)
		/// </summary>
		public bool StartServerAsApp { get { return mGameTalkConfig.StartServerAsApp; } }

		private bool ServerIsApp { 
			set 
			{
				mGameTalkConfig.StartServerAsApp = value;
				// We're in the GameTalk dll - this will ensure that the code in this Assembly will access the CurrentUser (instead of the LocalMachine) parts of the Registry
				if (mGameTalkConfig.StartServerAsApp) // When the Server is running as an application - it won't access LocalMachine - only CurrentUser (part of the Registry)
					RegistrySettings.SetRegistryKeyAreaToCurrentUser();
			} 
		}

		/// <summary>
		/// Is the connection connected to a target.
		/// </summary>
		public bool IsConnected
		{
			get
			{
				bool active = false;
				if (mConnection != null)
				{
					active = mConnection.Connected;
				}
				return active;
			}
		}

		#region Initialize Methods
		/// <summary>
		/// This probably wants to be made private - since this should be getting called directly from the constructor
		/// </summary>
		public void Initialize()
		{
			if ((mIPAddress == null) || (mIPAddress == string.Empty))
				mIPAddress = Network.ResolveIpAddress(Dns.GetHostName());

			mMessageCallback = new Connection.MessageReceivedDelegate(MessageReceived);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName">The name of the client to connect</param>
		public void Initialize(string targetName)
		{
			Initialize();
			mGameTalkConfig.TargetName = targetName;

			Debug.WriteLine("GameTalkInstance.Initialize: " + targetName);
			bool installedServer = false;
			if ((mGameTalkConfig.AutoInstallGameTalkServer) || (Network.IsLocalPcIpAddress(mIPAddress))) // We'll initialize the GameTalkServer even if the user asked us not to - if they are connecting to the local PC
			{
				Debug.WriteLine("GameTalkInstance.Initialize: Install Server");
				installedServer = Installer.Instance.InstallServer(StartServerAsApp, mGameTalkConfig.InstallUsbPlugin);
				if (!mGameTalkConfig.IsService)
				{
					Debug.WriteLine("GameTalkInstance.Initialize: an application is running GameTalk (not a service)");
					Installer.Instance.InstallViewer(installedServer, StartServerAsApp);
				}
			}

			// Connect to the server
			try
			{
				if (!Connect() && installedServer) // Sometimes the server install can take some time - so try again if the first attempt fails and we were installing the server
					Connect();
			}
			catch
			{
				try
				{
					if (installedServer) // Sometimes the server install can take some time - so try again if the first attempt fails and we were installing the server
						Connect();
				}
				catch { }
			}
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="startServerAsApp"></param>
		public void Initialize(string targetName, bool startServerAsApp)
		{
			ServerIsApp = startServerAsApp;
			Initialize(targetName);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName">The name of the client to connect</param>
		/// <param name="serverName">The server name to connect</param>
		public void Initialize(string targetName, string serverName)
		{
			mGameTalkConfig.ServerName = serverName;
			mIPAddress = Network.ResolveIpAddress(mGameTalkConfig.ServerName);
			Initialize(targetName);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName">The name of the client to connect</param>
		/// <param name="serverName">The server name to connect</param>
		/// <param name="portNum">The port that the server is listening on (to which we will connect)</param>
		public void Initialize(string targetName, string serverName, int portNum)
		{
			Port = portNum;
			Initialize(targetName, serverName);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		public void Initialize(string targetName, string serverName, string platform)
		{
			mGameTalkConfig.Platform = platform;
			Initialize(targetName, serverName);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		/// <param name="isService">This lets GameTalk know if the process starting GameTalk is
		/// a service.  If it is a service starting GameTalk - then it will not try to install
		/// or start the GameTalkMonitor - since Services shouldn't start stand-alone applications</param>
		public void Initialize(string targetName, string serverName, string platform, bool isService)
		{
			mGameTalkConfig.IsService = isService;
			Initialize(targetName, serverName, platform);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		/// <param name="isService"></param>
		/// <param name="startServerAsApp"></param>
		public void Initialize(string targetName, string serverName, string platform, bool isService, bool startServerAsApp)
		{
			ServerIsApp = startServerAsApp;
			mGameTalkConfig.IsService = isService;
			Initialize(targetName, serverName, platform);
		}

		/// <summary>
		/// Initialize the communication
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		/// <param name="isService">This lets GameTalk know if the process starting GameTalk is
		/// a service.  If it is a service starting GameTalk - then it will not try to install
		/// or start the GameTalkMonitor - since Services shouldn't start stand-alone applications</param>
		/// <param name="portNum">The port that the server is listening on (to which we will connect)</param>
		public void Initialize(string targetName, string serverName, string platform, bool isService, int portNum)
		{
			Port = portNum;
			Initialize(targetName, serverName, platform, isService);
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor.
		/// </summary>
		public GameTalkInstance()
		{
			Initialize();
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		public GameTalkInstance(string clientName)
		{
			Initialize(clientName);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="startServerAsApp"></param>
		/// <param name="multiThreaded"></param>
		public GameTalkInstance(string clientName, bool startServerAsApp, bool multiThreaded)
		{
			mGameTalkConfig.MultiThreaded = multiThreaded;
			Initialize(clientName, startServerAsApp);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="multiThreaded"></param>
		public GameTalkInstance(string clientName, bool multiThreaded)
		{
			mGameTalkConfig.MultiThreaded = multiThreaded;
			Initialize(clientName);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		public GameTalkInstance(string clientName, string serverName)
		{
			Initialize(clientName, serverName);
		}

		/// <summary>
		/// Constructor
		/// The third parameter is not a mask (int or uint) because I don't want to cause confusion with the constructor that takes a third argument indicating port number
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="initOptions"></param>
		public GameTalkInstance(string clientName, string serverName, InitializationOptions initOptions)
		{
			if (initOptions == InitializationOptions.NoAutoGameTalkServerOrMonitorInstall)
				mGameTalkConfig.AutoInstallGameTalkServer = false;

			Initialize(clientName, serverName);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="multiThreaded"></param>
		public GameTalkInstance(string clientName, string serverName, bool multiThreaded)
		{
			mGameTalkConfig.MultiThreaded = multiThreaded;
			Initialize(clientName, serverName);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="portNum"></param>
		public GameTalkInstance(string clientName, string serverName, int portNum)
		{
			Initialize(clientName, serverName, mGameTalkConfig.Platform, false, portNum);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		public GameTalkInstance(string clientName, string serverName, string platform)
		{
			Initialize(clientName, serverName, platform);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		/// <param name="isService"></param>
		public GameTalkInstance(string clientName, string serverName, string platform, bool isService)
		{
			Initialize(clientName, serverName, platform, isService);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="clientName"></param>
		/// <param name="serverName"></param>
		/// <param name="platform"></param>
		/// <param name="isService"></param>
		/// <param name="portNum"></param>
		public GameTalkInstance(string clientName, string serverName, string platform, bool isService, int portNum)
		{
			Initialize(clientName, serverName, platform, isService, portNum);
		}

		public GameTalkInstance(GameTalkConfig gameTalkConfig)
		{
			mGameTalkConfig = gameTalkConfig;
			Initialize(mGameTalkConfig.TargetName);
		}
		#endregion

		#region Send Methods
		private bool SendServerChannel(string channel)
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.AddChannelFilter.ToString(), channel);
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}
		
		/// <summary>
		/// Register a message handler for a specific channel
		/// </summary>
		/// <param name="callback">The callback handler</param>
		/// <param name="channel">The channel for filtering</param>
		public bool RegisterMessageHandler(MessageReceivedDelegate callback, string channel)
		{
			bool messageHandlerRegistered = false;
			lock (mMessageHandlers)
			{
				if (!mMessageHandlers.ContainsKey(callback))
				{
					messageHandlerRegistered = SendServerChannel(channel);

					if (messageHandlerRegistered)
						mMessageHandlers.Add(callback, channel);
				}
			}
			return messageHandlerRegistered;
		}

		/// <summary>
		/// Unregister a message handler for a specific channel
		/// </summary>
		/// <param name="callback">The callback handler</param>
		public bool UnregisterMessageHandler(MessageReceivedDelegate callback)
		{
			string channel = null;
			lock (mMessageHandlers)
			{
				if (mMessageHandlers.TryGetValue(callback, out channel))
					mMessageHandlers.Remove(callback);
			}

			if (channel != null) // If the user tries to unregister a channel that doesn't exist - channel will be null
			{
				bool isChannelStillHandled = false;
				lock (mMessageHandlers)
				{
            		foreach(string ch in mMessageHandlers.Values)
            		{
            	    	if(ch == channel)
            	    	{
            	        	isChannelStillHandled = true;
        	            	break;
    	            	}
	            	}
				}
            	// We do not want to send the RemoveChannelFilter message if the channel still has other handlers registered
            	if(!isChannelStillHandled)
            	{
            		GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
					gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.RemoveChannelFilter.ToString(), channel);
					return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);	
            	}				
			}
			return false;
		}

		/// <summary>
		/// We could optionally return whether this send was successful
		/// But we currently don't have a workflow set up to handle failure...
		/// </summary>
		/// <param name="keywordKeyStr"></param>
		/// <param name="keywordValueStr"></param>
		private void SendKeywordMessage(string keywordKeyStr, string keywordValueStr)
		{
			// This totalBufferSize is used to tell the GameTalkServer how big the 
			// message we are about to send is - the first 4 is for the first number 
			// that sums all the bytes in the message (totalBufferSize itself).
			int totalBufferSize = 4;
			totalBufferSize += 4 + keywordKeyStr.Length;
			totalBufferSize += 4 + keywordValueStr.Length;

			using (MemoryStream messageStream = new MemoryStream(totalBufferSize))
			{
				using (BinaryWriter binaryWriter = new BinaryWriter(messageStream))
				{
					try
					{
						binaryWriter.Write(totalBufferSize);

						// keyword key string
						byte[] messageBuffer = System.Text.Encoding.ASCII.GetBytes(keywordKeyStr);
						binaryWriter.Write(messageBuffer.Length);
						binaryWriter.Write(messageBuffer);

						// keyword value string
						messageBuffer = System.Text.Encoding.ASCII.GetBytes(keywordValueStr);
						binaryWriter.Write(messageBuffer.Length);
						binaryWriter.Write(messageBuffer);

						// Send the message
						byte[] toSend = messageStream.GetBuffer();
						Connection.ConnectionResult result = mConnection.Send(toSend);
					}
					catch { }
				}
			}
		}

		/// <summary>
		/// Send target name and some other keyword stuff to server
		/// </summary>
		private void SendTargetName()
		{
			if ((mGameTalkConfig.Platform != null) && (mGameTalkConfig.Platform != string.Empty))
				SendKeywordMessage(GameTalkMessage.sPlatformKeywordMessage, mGameTalkConfig.Platform);

			string version = string.Empty;
			try
			{
				version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
			}
			catch { }
			SendKeywordMessage(GameTalkMessage.sVersionKeywordMessage, version);
			// The target name must be sent last, since it triggers a context specific work flow in the GameTalkServer
			SendKeywordMessage("initialize", mGameTalkConfig.TargetName);
		}

		private void OnConnect()
		{
			if (mConnection.Connected)
			{
				SendTargetName();

				MessageReceivedDelegate synchronousCallback = new MessageReceivedDelegate(HandleSynchronousMessages);
				this.RegisterMessageHandler(synchronousCallback, GameTalkMessage.sSyncMessageChannel);
				MessageReceivedDelegate synchronousResponseCallback = new MessageReceivedDelegate(SynchronousMessageResponseHandler);
				this.RegisterMessageHandler(synchronousResponseCallback, GameTalkMessage.sSynchroResponseMessageID);
			}
		}

		/// <summary>
		/// User can use this to send their GameTalkMessage over the network to the specified target
		/// </summary>
		/// <param name="target"></param>
		/// <param name="message"></param>
		/// <returns></returns>
		public bool Send(string target, GameTalkMessage message)
		{
			// Get buffer information
			byte[] targetBuffer = System.Text.Encoding.ASCII.GetBytes(target);
			byte[] messageBuffer = message.GetBuffer();
			int totalSize = 4;
			totalSize += 4 + targetBuffer.Length;
			totalSize += 4 + messageBuffer.Length;

			MemoryStream messageStream = new MemoryStream(totalSize);
			BinaryWriter binaryWriter = new BinaryWriter(messageStream);

			// Build the buffer
			binaryWriter.Write(totalSize); // size of buffer
			binaryWriter.Write(targetBuffer.Length); // size of target
			binaryWriter.Write(targetBuffer); // target
			binaryWriter.Write(messageBuffer.Length); // size of message
			binaryWriter.Write(messageBuffer); // message
			binaryWriter.Flush();
			byte[] toSend = messageStream.GetBuffer();

			// Queue the buffer to send in case we need to reconnect to the server
			// Connect if not connected already
			Connection.ConnectionResult result = Connection.ConnectionResult.Failed;
			lock (mConnection)
			{
				if (Reconnect())
					result = mConnection.Send(toSend);
			}
			return result == Connection.ConnectionResult.Success;
		}

		#endregion

		#region Connect method
		/// <summary>
		/// Connect to the ip address on the specified port.
		/// </summary>
		/// <returns>True if connected otherwise false.</returns>
		private bool Connect()
		{
			try
			{
				if (!mCustomPort)
				{
					int port = Configuration.Instance.GetGameTalkServerPortNum();
					if (port >= 0) // negative numbers are invalid for port numbers - I think if you use zero it'll just use the first free port it can find (which probably isn't desireable to be honest) - not sure
						mGameTalkConfig.PortNum = port;
				}
			}
			catch { }

			Connection.ConnectionResult result = Connection.ConnectionResult.Failed;

			// If we lost our connection then create another one
			if (mConnection == null)
			{
				mConnection = new Connection(mMessageCallback, mGameTalkConfig.MultiThreaded);

				// Connect to target platform
				result = mConnection.Connect(mIPAddress, mGameTalkConfig.PortNum);

				// Send the name of the target
				OnConnect();
			}
			else if (!mConnection.Connected)
			{
				mConnection.Close();
				mConnection = new Connection(mMessageCallback, mGameTalkConfig.MultiThreaded);

				// Connect to target platform
				result = mConnection.Connect(mIPAddress, mGameTalkConfig.PortNum);

				if (result == Connection.ConnectionResult.Success)
				{
					// Send the name of the target
					OnConnect();
				}
			}
			else if (mConnection.Connected)
			{
				result = Connection.ConnectionResult.Success;
			}

			return result == Connection.ConnectionResult.Success;
		}

		private bool Reconnect()
		{
			if (this.IsConnected)
				return true;

			if (!Connect())
				return false;

			bool sentAllChannels = true;
			lock (mMessageHandlers)
			{
				Dictionary<MessageReceivedDelegate, string>.ValueCollection.Enumerator channels = mMessageHandlers.Values.GetEnumerator();
				while (channels.MoveNext())
				{
					if (!SendServerChannel(channels.Current))
					{
						sentAllChannels = false;
						break;
					}
				}
			}

			return sentAllChannels;
		}
		#endregion

		#region Shutdown Methods

		/// <summary>
		/// Lets the GameTalkServer know the user is shutting down their connection
		/// </summary>
		/// <returns></returns>
		public Connection.ConnectionResult SendShutDownMessage()
		{
			Connection.ConnectionResult result = Connection.ConnectionResult.Failed;
			if (mConnection == null)
				return result;

			lock (mConnection)
			{
				if (mConnection.Connected)
				{
					string message = "shutdown";
					byte[] messageBuffer = System.Text.Encoding.ASCII.GetBytes(message);
					int totalSize = 4 + 4 + messageBuffer.Length;

					MemoryStream messageStream = new MemoryStream(totalSize);
					BinaryWriter binaryWriter = new BinaryWriter(messageStream);

					binaryWriter.Write(totalSize);
					binaryWriter.Write(messageBuffer.Length);
					binaryWriter.Write(messageBuffer);
					byte[] toSend = messageStream.GetBuffer();

					result = mConnection.Send(toSend);
				}
			}
			return result;
		}

		/// <summary>
		/// Release the client.
		/// </summary>
		public bool Shutdown()
		{
			try
			{
				// We could optionally use the UnregisterMessageHandler function to unregister
				// each of our message handlers - assuming we still have a connection, this would
				// then send that unregistration to the GameTalkServer - however, it gracefully
				// takes care of this - so meh...
				lock (mMessageHandlers)
				{
					mMessageHandlers.Clear();
				}

				if (mConnection != null)
				{
					lock (mConnection)
					{
						SendShutDownMessage();
						mConnection.Close();
					}
					mConnection = null;
				}
			}
			catch (System.Threading.ThreadStateException)
			{
				// The following appears to throw its own exception
				//System.Threading.Thread.ResetAbort();
				return false;
			}
			return true;
		}

		#endregion

		#region Configuration Send Methods
		/// <summary>
		/// Tell the server to start buffering messages from the current client to the requested target
		/// </summary>
		/// <param name="target"></param>
		/// <param name="maxBufferedMessages">The maximum number of messages that will be buffered for this particular client</param>
		/// <param name="reset">Whether to clear the buffer</param>
		/// <returns></returns>
		public bool StartBuffering(string target, int maxBufferedMessages, bool reset)
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.StartBuffering.ToString(), maxBufferedMessages.ToString());
			gameTalkMessage.AddKeyContent(target, "");
			// we only need to send the reset if boolean if the user actually wants the server to reset/clear the existing buffer
			if (reset)
				gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.ClearBuffer.ToString(), reset.ToString());

			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// Tells the server to stop buffering any messages from the current client for the specified target
		/// </summary>
		/// <param name="target"></param>
		/// <param name="reset">Whether to clear the buffer</param>
		/// <returns></returns>
		public bool StopBuffering(string target, bool reset)
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			// GameTalk doesn't support sending null for the key content - so we'll just send the empty string
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.StopBuffering.ToString(), "");
			gameTalkMessage.AddKeyContent(target, "");
			// we only need to send the reset if boolean if the user actually wants the server to reset/clear the existing buffer
			if (reset)
				gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.ClearBuffer.ToString(), reset.ToString());

			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// Requests the GameTalkServer to send the client its version
		/// </summary>
		/// <returns></returns>
		public bool RequestServerVersion()
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			// GameTalk doesn't support sending null for the key content - so we'll just send the empty string
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.VersionRequest.ToString(), "");
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// Requests that the GameTalkServer send the client the list of clients that are registered (i.e. users
		/// added in the TargetManager)
		/// </summary>
		/// <returns></returns>
		public bool RequestRegisteredTargets()
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			// GameTalk doesn't support sending null for the key content - so we'll just send the empty string
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.RegisteredTargetsRequest.ToString(), "");
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// Using this allows us to poll the GameTalkServer and thus get a valid return value from
		/// the IsConnected property
		/// </summary>
		/// <returns></returns>
		public bool SendIsAlivePing()
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sGameTalkServerPingID);
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// This requests the list of all currently connected clients
		/// The response is received on the GameTalkMessage.sServerMessageID channel
		/// </summary>
		/// <returns></returns>
		public bool GetAllConnectedTargets()
		{
			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			// GameTalk doesn't support sending null for the key content - so we'll just send the empty string
			gameTalkMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.GetAllConnections.ToString(), "");
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// Allows users to tell GameTalkServer to add a new Registered Target (example - if you'd like to register game.ps3 on a particular IP address and port number).
		/// </summary>
		/// <param name="target"></param>
		/// <returns></returns>
		public bool RegisterTarget(Target target)
		{
			MemoryStream memStream = new MemoryStream();
			System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
			serializer.Serialize(memStream, target);
			byte[] serializedTarget = memStream.ToArray();

			GameTalkMessage gameTalkMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			gameTalkMessage.AddKeyContentAsBinary(GameTalkMessage.ClientServerMessages.RegisteredTargetAddRequest.ToString(), serializedTarget);
			return Send(GameTalkMessage.sGameTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// This will request the GameTalkServer to repeatedly send the given message
		/// to the list of given registered targets.
		/// </summary>
		/// <param name="message"></param>
		/// <param name="targetName"></param>
		/// <param name="frequency">In milliseconds</param>
		/// <returns></returns>
		public bool StartSendingMessageToRegisteredTargets(GameTalkMessage message, string targetName, int frequency)
		{
			byte[] messageBytes = message.GetBuffer();
			GameTalkMessage serverMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			serverMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.StartRepeatedSends.ToString(), frequency.ToString());
			serverMessage.AddKeyContentAsBinary(targetName, messageBytes);
			return Send(GameTalkMessage.sGameTalkServerID, serverMessage);
		}

		/// <summary>
		/// This will request the GameTalkServer to stop sending the given message
		/// to the list of given registered targets.
		/// </summary>
		/// <param name="message"></param>
		/// <param name="targetName"></param>
		/// <returns></returns>
		public bool StopSendingMessageToRegisteredTargets(GameTalkMessage message, string targetName)
		{
			byte[] messageBytes = message.GetBuffer();
			GameTalkMessage serverMessage = new GameTalkMessage(GameTalkMessage.sClientMessageID);
			serverMessage.AddKeyContent(GameTalkMessage.ClientServerMessages.StopRepeatedSends.ToString(), "");
			serverMessage.AddKeyContentAsBinary(targetName, messageBytes);
			return Send(GameTalkMessage.sGameTalkServerID, serverMessage);
		}

		#endregion

		#region Message Management

		private void MessageReceived(byte[] buffer)
		{
			GameTalkMessage message = new GameTalkMessage(buffer);
			if (message.ChannelId.Equals("PING")) // Ignore PING messages
				return;

			// We add all matching handlers to this callback list because we don't
			// want to have a lock on the main thread when we invoke the handlers
			// - i.e. we can end up in a deadlock if we simply lock here and then invoke 
			// each of the user's handlers/callbacks.
			List<MessageReceivedDelegate> callbacks = new List<MessageReceivedDelegate>();
			lock (mMessageHandlers)
			{
				Dictionary<MessageReceivedDelegate, string>.Enumerator messageHandlerEnum = mMessageHandlers.GetEnumerator();
				while (messageHandlerEnum.MoveNext())
				{
					KeyValuePair<MessageReceivedDelegate, string> msgHandlerKeyValuePair = messageHandlerEnum.Current;
					string channel = msgHandlerKeyValuePair.Value;
					// For a message handler to match on a channel - we need either:
					// 1) channel name is empty string - means it listens on all channels
					// 2) an exact name match
					// 3) the first part of the message's channel name matches and the next character in non-matching part to be a period
					if ((channel == "") ||
						(channel == message.ChannelId) ||
						((message.ChannelId.StartsWith(channel)) &&
							(message.ChannelId.Length > channel.Length) &&
							(message.ChannelId.Substring(channel.Length, 1) == ".")))
					{
						MessageReceivedDelegate callback = msgHandlerKeyValuePair.Key;
						callbacks.Add(callback);
					}
				}
			} // end of lock (mMessageHandlers)

			// Now that we've released the lock - invoke the handlers/callbacks
			foreach (MessageReceivedDelegate callback in callbacks)
			{
				try
				{
					// Do NOT invoke this callback on a separate thread - we have users that need
					// to preserve ordering of the receipt of their GameTalkMessages
					callback(message);
				}
				catch (Exception exception)
				{
					Trace.WriteLine("exception: " + exception.Message + Environment.NewLine + exception.StackTrace);
				}
			}
		}

		#endregion

		#region Identity getter accessors
		/// <summary>
		/// Just returns the string representing the version of GameTalk being used
		/// </summary>
		public string ClientVersion
		{
			get
			{
				return Assembly.GetExecutingAssembly().GetName().Version.ToString();
			}
		}

		/// <summary>
		/// Returns the client's target name
		/// </summary>
		public string TargetName
		{
			get
			{
				return mGameTalkConfig.TargetName;
			}
		}
		#endregion

		#region Synchronous messaging
		private bool InvokeMatchingSynchoMsgHandler(SyncReplyGameTalkMessage synchroReplyGTM, GameTalkMessage message)
		{
			bool invoked = false;
			List<SynchronousMessageReceivedDelegate> callbacks = new List<SynchronousMessageReceivedDelegate>();
			lock (mSynchroMessageHandlers)
			{
				Dictionary<SynchronousMessageReceivedDelegate, string>.Enumerator messageHandlerEnum = mSynchroMessageHandlers.GetEnumerator();
				while (messageHandlerEnum.MoveNext())
				{
					KeyValuePair<SynchronousMessageReceivedDelegate, string> msgHandlerKeyValuePair = messageHandlerEnum.Current;
					string channel = msgHandlerKeyValuePair.Value;
					// For a message handler to match on a channel - we need either:
					// 1) channel name is empty string - means it listens on all channels
					// 2) an exact name match
					// 3) the first part of the message's channel name matches 
					// and the next character in non-matching part to be a period
					if ((channel == "") ||
						(channel == message.ChannelId) ||
						((message.ChannelId.StartsWith(channel)) &&
							(message.ChannelId.Length > channel.Length) &&
							(message.ChannelId.Substring(channel.Length, 1) == ".")))
					{
						SynchronousMessageReceivedDelegate callback = msgHandlerKeyValuePair.Key;
						callbacks.Add(callback);
					}
				}
			} // end of lock (mSynchroMessageHandlers)

			// Now that we've released the lock - invoke the handlers/callbacks
			foreach (SynchronousMessageReceivedDelegate callback in callbacks)
			{
				try
				{
					// Do NOT invoke this callback on a separate thread - we have users that need
					// to ensure the serial ordering of the receipt of their GameTalkMessages
					callback(synchroReplyGTM, message);
					invoked = true;
				}
				catch (Exception exception)
				{
					Trace.WriteLine("exception: " + exception.Message + Environment.NewLine + exception.StackTrace);
				}
			}
			return invoked;
		}

		private void HandleSynchronousMessages(GameTalkMessage gameTalkMessage)
		{
			string channelMinusSynchronousBit = gameTalkMessage.ChannelId;
			string synchroStringToStrip = GameTalkMessage.sSyncMessageChannel + ".";
			if (channelMinusSynchronousBit.Length < synchroStringToStrip.Length)
			{
				Debug.Assert(false, "Received a GameTalkMessage in our synchronous GameTalkMessage handler that doesn't have the synchronous string in the channel - not sure how that is possible!");
				return;
			}
			else
				channelMinusSynchronousBit = channelMinusSynchronousBit.Substring(synchroStringToStrip.Length);

			int lastSeparatorPosition = channelMinusSynchronousBit.LastIndexOf(".");
			if (lastSeparatorPosition == -1)
			{
				Debug.Assert(false, "Received a GameTalkMessage in our synchronous GameTalkMessage handler that doesn't have enough parts (separated by \".\") - not sure how that is possible!");
				return;
			}
			string synchronousResponseChannel = GameTalkMessage.sSynchroResponseMessageID + "." + channelMinusSynchronousBit;
			SyncReplyGameTalkMessage synchroReplyGTM = new SyncReplyGameTalkMessage(gameTalkMessage,
				new SyncReplyGameTalkMessage.SendDelegate(Send), synchronousResponseChannel);
			string synchronousChannel = channelMinusSynchronousBit.Substring(0, lastSeparatorPosition);

			gameTalkMessage.ChannelId = synchronousChannel;
			bool invokedSynchroMsgHandler = InvokeMatchingSynchoMsgHandler(synchroReplyGTM, gameTalkMessage);
			if (!synchroReplyGTM.Replied)
			{
				if (invokedSynchroMsgHandler)
					synchroReplyGTM.AddKeyContent(GameTalkMessage.sSynchronousGameTalkIssue, "User did not reply in their callback");
				else
					synchroReplyGTM.AddKeyContent(GameTalkMessage.sSynchronousGameTalkIssue, "User did not register a synchronous GameTalkMessage handler");

				synchroReplyGTM.Reply();
			}
		}

		/// <summary>
		/// Used to register a user's Synchronous GameTalkMessage handler
		/// </summary>
		/// <param name="userCallback"></param>
		/// <param name="channel"></param>
		/// <returns></returns>
		public bool RegisterSynchronousMessageHandler(SynchronousMessageReceivedDelegate userCallback, string channel)
		{
			if (!mSynchroMessageHandlers.ContainsKey(userCallback))
			{
				mSynchroMessageHandlers.Add(userCallback, channel);
				return true;
			}

			return false;
		}

		/// <summary>
		/// User can unregister their synchronous GameTalkMessage handler
		/// </summary>
		/// <returns></returns>
		public void UnregisterSynchronousMessageHandler(SynchronousMessageReceivedDelegate synchroMessageHandler)
		{
			if (!mSynchroMessageHandlers.ContainsKey(synchroMessageHandler))
				mSynchroMessageHandlers.Remove(synchroMessageHandler);
		}

		/// <summary>
		/// The response to a submitted Synchronous GameTalkMessage is stored
		/// in the mSynchronousResponseGtm
		/// </summary>
		/// <param name="gameTalkMessage"></param>
		private void SynchronousMessageResponseHandler(GameTalkMessage gameTalkMessage)
		{
			uint synchroId = 0;
			if (!SyncReplyGameTalkMessage.GetSynchroId(gameTalkMessage, ref synchroId)) // Something has gone wrong
				return;
			mSynchronousResponseGtms.Add(synchroId, gameTalkMessage);
		}


		/// <summary>
		/// This will send a synchronous message to the specified target
		/// </summary>
		/// <param name="target">recipient of the message</param>
		/// <param name="gameTalkMessage">message to send</param>
		/// <param name="numOfPolls">number of times to idle</param>
		/// <param name="idleTime">Time to idle in milliseconds</param>
		/// <returns></returns>
		public GameTalkMessage SendSynchronousMessage(string target, 
			GameTalkMessage gameTalkMessage, int numOfPolls, int idleTime)
		{
			uint synchroMsgId = ++mSyncMsgUidCounter;
			string syncMsgUidCounterStr = Convert.ToString(synchroMsgId);
			string synchronousMessageHandlerChannel = gameTalkMessage.ChannelId + "." + syncMsgUidCounterStr;
			for (int i = syncMsgUidCounterStr.Length; i < 10; i++)
				synchronousMessageHandlerChannel += "x";

			// In case the user expects synchronous response GameTalkMessage to contain their original channel
			string userChannel = gameTalkMessage.ChannelId;
			gameTalkMessage.ChannelId = GameTalkMessage.sSyncMessageChannel + "." + synchronousMessageHandlerChannel;
			gameTalkMessage.AddChunkData(BitConverter.GetBytes(synchroMsgId), (uint)GameTalkMessage.InternalType.SynchroId);
			// Tag on the "@." to ensure this is sent only to the intended recipient and no other
			this.Send("@." + target, gameTalkMessage);
			GameTalkMessage messageToReturn = null;
			for (int i = 0; (i < numOfPolls) && (!mSynchronousResponseGtms.TryGetValue(synchroMsgId, out messageToReturn)); i++)
				System.Threading.Thread.Sleep(idleTime);

			if (mSynchronousResponseGtms.ContainsKey(synchroMsgId)) // Regardless of whether we timed out - remove the message we received
				mSynchronousResponseGtms.Remove(synchroMsgId);

			if (messageToReturn != null) // Set the received GameTalkMessage's channel to the channel the user originally supplied
				messageToReturn.ChannelId = userChannel;

			return messageToReturn;
		}
		#endregion

		#region Methods to facilitate single-threaded mode of GameTalk
		/// <summary>
		/// Returns the number of full GameTalkMessages that are available for processing
		/// </summary>
		/// <returns></returns>
		public int Peek()
		{
			return this.mConnection.NumPacketsToProcessFromWriteBuffer();
		}

		/// <summary>
		/// Allows user to recurse into the processing of buffered data
		/// Avoid using this unless absolutely necessary - this has been added to address a very specific and unique user requirement
		/// There should be no reason to use this except within the context of a GameTalk callback/delegate
		/// </summary>
		public void PollBufferedData()
		{
			this.mConnection.PollBufferedData();
		}

		/// <summary>
		/// This can be used when operating in single-threaded mode
		/// </summary>
		public void Poll()
		{
			Debug.Assert(!mGameTalkConfig.MultiThreaded, "Poll should only be used in single-threaded mode");
			this.mConnection.Poll();
		}
		#endregion

		#region Helper methods that could be made static
		/// <summary>
		/// This method could be made static - thus is not exposed in IGameTalkInstance
		/// </summary>
		/// <param name="targetMetaData"></param>
		/// <returns></returns>
		public string GetPlatformFromTargetMetaData(string targetMetaData)
		{
			string platform = string.Empty;
			int colonPosition = targetMetaData.IndexOf(":");
			if (colonPosition > 0)
				return targetMetaData.Substring(0, colonPosition);

			return platform;
		}

		/// <summary>
		/// This method could be made static - thus is not exposed in IGameTalkInstance
		/// </summary>
		/// <param name="targetMetaData"></param>
		/// <returns></returns>
		public string GetVersionFromTargetMetaData(string targetMetaData)
		{
			string version = string.Empty;
			int colonPosition = targetMetaData.IndexOf(":");
			if ((colonPosition > 0) && (colonPosition < targetMetaData.Length))
				version = targetMetaData.Substring(colonPosition + 1);

			return version;
		}
		#endregion
	}
}
