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

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;

// Strictly for the Network class
using System.Net;
using System.Net.Sockets;

using Microsoft.Win32;

namespace EA.GameTalkMessages
{
    /// <summary>
    /// The message is the object transferred from one client to another.
    /// The implementation doesn't use DictionaryList because there is no direct possibility to access key and content by index
    /// Also a dictionary won't be able to manage several key of the same value in the same message
    /// </summary>
	public class GameTalkMessage
	{
		public enum Format 
		{
			String = 0,
			Binary = 1,
			Serialized = 2, // Our native deserialization won't support serializing GameTalkMessages in string format
			GameTalkMessage = 3,
			Char = 4,
			Int8 = 5,
			UInt8 = 6,
			Int16 = 7,
			UInt16 = 8,
			Int32 = 9,
			UInt32 = 10,
			Int64 = 11,
			UInt64 = 12,
			Float = 13,
			Double = 14,
			Decimal = 15,
			Reserved1 = 16,
			Reserved2 = 18,
		}

		enum Endian
		{
			LittleEndian,
			BigEndian = 128
		}

		public enum InternalType
		{
			Sender = 64,
			SynchroId,
		}

		// We may want to prevent clients from using the string assigned to sGameTalkServerID - but even if
		// they use it everything will work fine - so not really important
		public const string sGameTalkServerID = "GameTalkServer";
		public const string sClientMessageID = "Client Message";
		public const string sServerMessageID = "Server Message";
		public const string sGameTalkServerPingID = sGameTalkServerID + ".PING";
		public const string sSyncMessageChannel = "GameTalk.Synchronous";
		public const string sSynchroResponseMessageID = "GameTalk.SynchroResponse";
		public const string sSynchronousGameTalkIssue = "SynchronousGameTalkIssue";
		public enum ClientServerMessages
		{
			StartBuffering,
			StopBuffering,
			ClearBuffer,
			VersionRequest,
			VersionResponse,
            RegisteredTargetsRequest,
            RegisteredTargetsResponse,
            ConnectedTargetsMessage,
			ConnectedTargetMessage,
			DisconnectedTargetMessage,
			AddChannelFilter,
			RemoveChannelFilter,
			UpdateTargetName,
			GetAllConnections,
			StartRepeatedSends,
			StopRepeatedSends,
			SecureTrust,
			SecureTrustStartApp,
			RegisteredTargetAddRequest,
			RegisteredTargetAddResponse,
			RegisteredTargetRemoveRequest,
			RegisteredTargetRemoveResponse,
		}

		public const string sPlatformKeywordMessage = "gametalk.config.platform";
		public const string sVersionKeywordMessage = "gametalk.config.version";

		public const string sBroadcastToAllString = "*";
		public const string sBroadcastToAllExceptSelfString = "*!";
		public const string sSendOnlyToSpecificTargetString = "@";

		/// <summary>
		/// Private Key Content class for tracking the message content
		/// </summary>
		/// <remarks>Always keep this private to abstract from user</remarks>
		private class KeyContent
		{
			public string Key = string.Empty;
			public Format Type = Format.String;
			public object Content = null;
			public bool IsLittleEndian = false;
			public Format TypeCode = Format.String;
			public int TypeSize = 0;

			public KeyContent(string key, Format type, object content, bool isLittleEndian,
				Format typeCode, int typeSize)
			{
				Type = type;
				Key = key;
				Content = content;
				IsLittleEndian = isLittleEndian;
				TypeCode = typeCode;
				TypeSize = typeSize;
			}

			public override string ToString()
			{
				if (Type == Format.String)
					return (string)Content;
				else 
					return "Binary Data (0xffff)";
			}
		}

		protected string mChannelId = string.Empty;
		private List<KeyContent> mKeysContents = new List<KeyContent>();

		private byte[] mWholeMessageByteArray = null;
		private uint mNumKeys = 0;

		protected string mSender = null;
		private byte[] mLeftOvers = null;

        /// <summary>
        /// Gets the number of keys.
        /// </summary>
        public int NumberOfKeys
        {
            get 
			{
				if (mWholeMessageByteArray != null)
					return (int)mNumKeys;
				else
					return mKeysContents.Count; 
			}
        }

		/// <summary>
		/// Gets the channel Id.
		/// </summary>
		public string ChannelId
		{
			get { return mChannelId; }
			set { mChannelId = value; }
		}

		/// <summary>
		/// Get the sender of this GameTalkMessage
		/// </summary>
		public string Sender
		{
			get { return mSender; }
		}

		/// <summary>
        /// Constructs a Message with a channel Id.
        /// We can't construct a message without channel Id.
        /// </summary>
        /// <param name="ChannelId">The message Id.</param>
		public GameTalkMessage(string channelId)
		{
			Debug.Assert(channelId != null);
			mChannelId = channelId;
		}

		/// <summary>
		/// This is used to changed the endian of some data
		/// </summary>
		/// <param name="byteArray"></param>
		/// <param name="index"></param>
		/// <param name="count"></param>
		/// <param name="endian"></param>
		/// <returns></returns>
		private byte[] SwapBytes(byte[] byteArray, int index, int count, Endian endian)
		{
			if ((BitConverter.IsLittleEndian) && (endian == Endian.LittleEndian))
				return byteArray;
			// We could also test if we're big endian and the message has a big endian encoding - but I don't think this code will run on a big endian machine
			byte[] resultBytes = new byte[byteArray.Length];
			int decrementor = 1;
			for (int i = index; i < index + count; i++)
			{
				resultBytes[i] = byteArray[index + count - decrementor];
				++decrementor;
			}
			return resultBytes;
		}

		private object Deserialize(Format typeCode, byte[] contentBytes, Endian endian)
		{
			object attemptConversion = contentBytes;
			try
			{
				if (typeCode == Format.Float)
				{
					if (contentBytes.Length == 4)
						attemptConversion = BitConverter.ToSingle(SwapBytes(contentBytes, 0, 4, endian), 0);
					else if (contentBytes.Length > 4)
					{
						List<float> floats = new List<float>();
						for (int i = 0; i < contentBytes.Length; i += 4)
							floats.Add(BitConverter.ToSingle(SwapBytes(contentBytes, i, 4, endian), i));
						attemptConversion = floats.ToArray();
					}
				}
				else if (typeCode == Format.Double)
				{
					if (contentBytes.Length == 8)
						attemptConversion = BitConverter.ToDouble(SwapBytes(contentBytes, 0, 8, endian), 0);
					else if (contentBytes.Length > 8)
					{
						List<double> myList = new List<double>();
						for (int i = 0; i < contentBytes.Length; i += 8)
							myList.Add(BitConverter.ToDouble(SwapBytes(contentBytes, i, 8, endian), i));
						attemptConversion = myList.ToArray();
					}
				}
				else if (typeCode == Format.Int8)
				{
					if (contentBytes.Length == 1) // Convert.ToSByte doesn't seem to work on Byte data
						attemptConversion = (SByte)contentBytes[0];
					else if (contentBytes.Length > 1)
					{
						List<SByte> tempList = new List<SByte>();
						for (int i = 0; i < contentBytes.Length; i += 1)
							tempList.Add((SByte)contentBytes[i]);
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.UInt8)
				{
					if (contentBytes.Length == 1)
						attemptConversion = (Byte)contentBytes[0];
					else if (contentBytes.Length > 1)
					attemptConversion = contentBytes;
				}
				else if (typeCode == Format.Int16)
				{
					if (contentBytes.Length == 2)
						attemptConversion = BitConverter.ToInt16(SwapBytes(contentBytes, 0, 2, endian), 0);
					else if (contentBytes.Length > 2)
					{
						List<Int16> tempList = new List<Int16>();
						for (int i = 0; i < contentBytes.Length; i += 2)
							tempList.Add(BitConverter.ToInt16(SwapBytes(contentBytes, i, 2, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.UInt16)
				{
					if (contentBytes.Length == 2)
						attemptConversion = BitConverter.ToUInt16(SwapBytes(contentBytes, 0, 2, endian), 0);
					else if (contentBytes.Length > 2)
					{
						List<UInt16> tempList = new List<UInt16>();
						for (int i = 0; i < contentBytes.Length; i += 2)
							tempList.Add(BitConverter.ToUInt16(SwapBytes(contentBytes, i, 2, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.Int32)
				{
					if (contentBytes.Length == 4)
						attemptConversion = BitConverter.ToInt32(SwapBytes(contentBytes, 0, 4, endian), 0);
					else if (contentBytes.Length > 4)
					{
						List<int> tempList = new List<int>();
						for (int i = 0; i < contentBytes.Length; i += 4)
							tempList.Add(BitConverter.ToInt32(SwapBytes(contentBytes, i, 4, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.UInt32)
				{
					if (contentBytes.Length == 4)
						attemptConversion = BitConverter.ToUInt32(SwapBytes(contentBytes, 0, 4, endian), 0);
					else if (contentBytes.Length > 4)
					{
						List<uint> tempList = new List<uint>();
						for (int i = 0; i < contentBytes.Length; i += 4)
							tempList.Add(BitConverter.ToUInt32(SwapBytes(contentBytes, i, 4, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.Int64)
				{
					if (contentBytes.Length == 8)
						attemptConversion = BitConverter.ToInt64(SwapBytes(contentBytes, 0, 8, endian), 0);
					else if (contentBytes.Length > 8)
					{
						List<Int64> tempList = new List<Int64>();
						for (int i = 0; i < contentBytes.Length; i += 8)
							tempList.Add(BitConverter.ToInt64(SwapBytes(contentBytes, i, 8, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.UInt64)
				{
					if (contentBytes.Length == 8)
						attemptConversion = BitConverter.ToUInt64(SwapBytes(contentBytes, 0, 8, endian), 0);
					else if (contentBytes.Length > 8)
					{
						List<UInt64> tempList = new List<UInt64>();
						for (int i = 0; i < contentBytes.Length; i += 8)
							tempList.Add(BitConverter.ToUInt64(SwapBytes(contentBytes, i, 8, endian), i));
						attemptConversion = tempList.ToArray();
					}
				}
				else if (typeCode == Format.GameTalkMessage)
				{
					GameTalkMessage gtm = new GameTalkMessage(contentBytes);
					attemptConversion = gtm;
				}
			}
			catch { }
			return attemptConversion;
		}

		/// <summary>
		/// Get a byte array of the ChunkData (of a particular type)
		/// </summary>
		/// <param name="chunkType"></param>
		/// <returns></returns>
		public byte[] GetChunkData (uint chunkType)
		{
			using (MemoryStream memStream = new MemoryStream(mLeftOvers))
			{
				using (BinaryReader binaryReader = new BinaryReader(memStream))
				{
					try
					{
						while (binaryReader.BaseStream.Position + 8 < binaryReader.BaseStream.Length)
						{
							UInt32 internalType = binaryReader.ReadUInt32();
							UInt32 totalChunkSize = binaryReader.ReadUInt32();
							if (totalChunkSize < 8)
								break;
							byte[] bytes = binaryReader.ReadBytes((int)totalChunkSize - 8);
							if (internalType == chunkType)
								return bytes;
						}
					}
					catch
					{
					}
				}
			}
			return null;
		}

		/// <summary>
		/// Add ChunkData of a particular type to this GameTalkMessage
		/// </summary>
		/// <param name="bytes"></param>
		/// <param name="chunkType"></param>
		public void AddChunkData(byte[] bytes, UInt32 chunkType)
		{
			if (chunkType == (uint)InternalType.Sender)
			{
				if (!string.IsNullOrEmpty(mSender)) // Don't re-add the sender ChunkData
					return;
				else
					mSender = System.Text.ASCIIEncoding.ASCII.GetString(bytes);
			}
			// Note that we give the MemoryStream the precise size of the byte array we'll create - so we can invoke GetBuffer instead of ToArray
			byte[] byteArrayToRecreate = null;
			int byteArrayToRecreateLength = 0;
			if (mWholeMessageByteArray != null)
			{
				byteArrayToRecreate = mWholeMessageByteArray;
				byteArrayToRecreateLength = byteArrayToRecreate.Length;
			}
			else if (mLeftOvers != null)
			{
				byteArrayToRecreate = mLeftOvers;
				byteArrayToRecreateLength = byteArrayToRecreate.Length;
			}

			MemoryStream memStream = new MemoryStream(byteArrayToRecreateLength + 8 + bytes.Length);
			BinaryWriter binaryWriter = new BinaryWriter(memStream);
			try
			{
				if (byteArrayToRecreate != null)
					binaryWriter.Write(byteArrayToRecreate);
				binaryWriter.Write((UInt32)chunkType);
				binaryWriter.Write((UInt32)(8 + bytes.Length));
				binaryWriter.Write(bytes);
				if (mWholeMessageByteArray != null)
					mWholeMessageByteArray = memStream.GetBuffer();
				else
					mLeftOvers = memStream.GetBuffer();
			}
			catch { }
			finally
			{
				memStream.Close();
				binaryWriter.Close();
			}
		}

		/// <summary>
		/// This method will use the byte array to completely create a GameTalkMessage - key content and all
		/// </summary>
		/// <param name="buffer"></param>
		private void FullyConstructGameTalkMessage(byte[] buffer)
		{
			System.IO.MemoryStream memoryStream = new MemoryStream(buffer);
			System.IO.BinaryReader binaryReader = new BinaryReader(memoryStream);
			try
			{
				// Read the message ID
				this.mChannelId = ReadString(binaryReader);

				// Read the number of keys.
				UInt32 numKeys = binaryReader.ReadUInt32();

				// Now read each key, content pair.
				for (UInt32 i = 0; i < numKeys; i++)
				{
					string key = ReadString(binaryReader);
					object val = null;

					// Read the type.
					uint keyContentMetaData = binaryReader.ReadUInt32();
					Format type = (Format)(keyContentMetaData & 3);
					// decimal 128 = binary 10000000
					Endian endian = (Endian)(keyContentMetaData & 128);
					// decimal 65280 = binary 1111111100000000 - I don't think the &'ing is necessary - but meh...
					Format typeCode = (Format)((keyContentMetaData & 65280) >> 8);
					// decimal 16711680 = binary 111111110000000000000000 - I don't think the &'ing is necessary - but meh...
					int typeSize = (int)(keyContentMetaData & 16711680) >> 16;
					switch (type)
					{
						case Format.Binary:
							val = ReadBinary(binaryReader);
							break;
						case Format.String:
							val = ReadString(binaryReader);
							break;
					}

					// Add the key content pair to the message.
					this.AddKeyContent(key, val, type, typeCode, typeSize);
				}

				if (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length) // There is more data - check for sender data
				{
					mLeftOvers = binaryReader.ReadBytes((int)binaryReader.BaseStream.Length);

					byte[] senderBytes = GetChunkData((UInt32)InternalType.Sender);
					if ((senderBytes != null) && (senderBytes.Length > 0))
						mSender = ReadStringWithNoLengthPrefix(senderBytes);
				}
			}
			catch
			{
				// Clear the stream
				binaryReader.BaseStream.Position = 0;
				byte[] data = binaryReader.ReadBytes((int)binaryReader.BaseStream.Length);
			}
			finally
			{
				memoryStream.Close();
				binaryReader.Close();
			}
		}

		public GameTalkMessage(byte[] buffer, bool parseEntireMessage)
		{
			if (!parseEntireMessage)
			{
				System.IO.MemoryStream memoryStream = new MemoryStream(buffer);
				System.IO.BinaryReader binaryReader = new BinaryReader(memoryStream);

				try
				{
					// Read the message ID
					this.mChannelId = ReadString(binaryReader);

					// Read the number of keys.
					mNumKeys = binaryReader.ReadUInt32();

					binaryReader.BaseStream.Position = 0;
					mWholeMessageByteArray = binaryReader.ReadBytes((int)binaryReader.BaseStream.Length);
				}
				catch (Exception exception)
				{
					Trace.WriteLine("Exception in construction of GameTalkMessage: " + exception.Message + Environment.NewLine + exception.StackTrace);
				}
				finally
				{
					memoryStream.Close();
					binaryReader.Close();
				}
			}
			else
				FullyConstructGameTalkMessage(buffer);
		}

		/// <summary>
		/// Constructs a Message from a buffer.
		/// </summary>
		/// <param name="buffer">The buffer for the message.</param>
		public GameTalkMessage(byte[] buffer)
		{
			FullyConstructGameTalkMessage(buffer);
		}

		#region Key Management Methods

        /// <summary>
        /// Gets the key at the specified index.
        /// </summary>
        /// <param name="index">Index of the key</param>
        /// <returns>The corresponding key string</returns>
		public string GetKey(int index)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			KeyContent k = mKeysContents[index];
			return k.Key;
		}

        /// <summary>
        /// Gets the content at the specified index.
        /// </summary>
        /// <param name="index">Index of the content</param>
        /// <returns>The corresponding content value</returns>
		public object GetContent(int index)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			KeyContent k = mKeysContents[index];
			object val = null;
			if (k.Content is byte[])
				val = Deserialize(k.TypeCode, (byte[])k.Content, (k.IsLittleEndian) ? Endian.LittleEndian : Endian.BigEndian);
			else
				val = k.Content;
			return val;
		}

		/// <summary>
		/// Gets the content at the specified index.
		/// </summary>
		/// <param name="index">Index of the content</param>
		/// <returns>The corresponding content value</returns>
		public System.Byte[] GetContentAsBinary(int index)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			if (GetType(index) == Format.Binary)
			{
				return (System.Byte[])GetContent(index);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Gets the content at the specified index.
		/// </summary>
		/// <param name="index">Index of the content</param>
		/// <returns>The corresponding content value</returns>
		public string GetContentAsString(int index)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			if (GetType(index) == Format.String)
			{
				return (string)GetContent(index);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Gets the format type at the specified index.
		/// </summary>
		/// <param name="index">Index of the content</param>
		/// <returns>The corresponding format of the content</returns>
		public Format GetType(int index)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			KeyContent k = mKeysContents[index];
			return k.Type;
		}

        /// <summary>
        /// Gets a content by looking for a key.
        /// </summary>
        /// <param name="key">the key string</param>
        /// <returns>The content of the first key found, null if the key doesn't exist</returns>
        public object GetContent(string key)
        {
			int index = GetIndexOfKey(key);
			if (index >= 0)
			{
				return GetContent(index);
			}
			return null;
		}
		public int GetIndexOfKey(string key)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			for(int i = 0; i < mKeysContents.Count;i++)
            {
				KeyContent k = mKeysContents[i];
                if (k.Key == key)
                {
					return i;
                }
            }

            // Didn't find the content
			return -1;
        }

		/// <summary>
		/// Get float
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public float GetFloat(int index)
		{
			return (float)GetContent(index);
		}

		/// <summary>
		/// Get float array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public float[] GetFloatArray(int index)
		{
			return (float[])GetContent(index);
		}

		/// <summary>
		/// Get double
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public double GetDouble(int index)
		{
			return (double)GetContent(index);
		}

		/// <summary>
		/// Get double array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public double[] GetDoubleArray(int index)
		{
			return (double[])GetContent(index);
		}

		/// <summary>
		/// Get SByte
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public SByte GetInt8(int index)
		{
			return (SByte)GetContent(index);
		}

		/// <summary>
		/// Get SByte array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public SByte[] GetInt8Array(int index)
		{
			return (SByte[])GetContent(index);
		}

		/// <summary>
		/// Get UInt8
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Byte GetUInt8(int index)
		{
			return (Byte)GetContent(index);
		}

		/// <summary>
		/// Get UInt8 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Byte[] GetUInt8Array(int index)
		{
			return (Byte[])GetContent(index);
		}

		/// <summary>
		/// Get Int16
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int16 GetInt16(int index)
		{
			return (Int16)GetContent(index);
		}

		/// <summary>
		/// Get Int16 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int16[] GetInt16Array(int index)
		{
			return (Int16[])GetContent(index);
		}

		/// <summary>
		/// Get UInt16
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt16 GetUInt16(int index)
		{
			return (UInt16)GetContent(index);
		}

		/// <summary>
		/// Get UInt16 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt16[] GetUInt16Array(int index)
		{
			return (UInt16[])GetContent(index);
		}

		/// <summary>
		/// Get Int32
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int32 GetInt32(int index)
		{
			return (Int32)GetContent(index);
		}

		/// <summary>
		/// Get Int32 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int32[] GetInt32Array(int index)
		{
			return (Int32[])GetContent(index);
		}

		/// <summary>
		/// Get UInt32
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt32 GetUInt32(int index)
		{
			return (UInt32)GetContent(index);
		}

		/// <summary>
		/// Get UInt32 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt32[] GetUInt32Array(int index)
		{
			return (UInt32[])GetContent(index);
		}

		/// <summary>
		/// Get Int64
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int64 GetInt64(int index)
		{
			return (Int64)GetContent(index);
		}

		/// <summary>
		/// Get Int64 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Int64[] GetInt64Array(int index)
		{
			return (Int64[])GetContent(index);
		}

		/// <summary>
		/// Get UInt64
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt64 GetUInt64(int index)
		{
			return (UInt64)GetContent(index);
		}

		/// <summary>
		/// Get UInt64 array
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		[CLSCompliantAttribute(false)]
		public UInt64[] GetUInt64Array(int index)
		{
			return (UInt64[])GetContent(index);
		}

		/// <summary>
		/// Adds a key and its content to a message.
		/// </summary>
		/// <param name="key">The key string.</param>
		/// <param name="content">The content string.</param>
		public void AddKeyContent(string key, string content)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			AddKeyContent(key, content, Format.String);
		}

		/// <summary>
		/// Adds a key and its content to a message.
		/// </summary>
		/// <param name="key">The key string.</param>
		/// <param name="content">The content object.</param>
		/// <param name="type">The content format.</param>
		public void AddKeyContent(string key, object content, Format type)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			if (key == null)
			{
				Debug.Assert(key != null, "No support for adding null key's to GameTalkMessages - Key/Content has not been added.");
				return;
			}

			// Should we default 0 for TypeCode (or should we use type for both format and type?) and 1 for TypeSize?  Or would it be better to be 0 for TypeSize so that we maintain backward compatibility (0 or 1 for string or binary in KeyContent pair metadata)
			KeyContent k = new KeyContent(key, type, content, BitConverter.IsLittleEndian, 0, 1);
			mKeysContents.Add(k);
		}

		private void AddKeyContent(string key, object content, Format format, Format typeCode, int typeSize)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			// Should we default 0 for TypeCode (or should we use type for both format and type?) and 1 for TypeSize?  Or would it be better to be 0 for TypeSize so that we maintain backward compatibility (0 or 1 for string or binary in KeyContent pair metadata)
			KeyContent k = new KeyContent(key, format, content, BitConverter.IsLittleEndian, typeCode, typeSize);
			mKeysContents.Add(k);
		}

		/// <summary>
		/// This method will handle most native types.  For the full list this method can
		/// handle look at the overloaded Add method.
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void AddObject(string key, object content)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			KeyContent k = null;
			// If this is a primitive data type (native) then it is binary for format + then the matching format for the type
			if ((content is float) || (content is float[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Float, sizeof(float));
			else if ((content is double) || (content is double[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Double, sizeof(double));
			else if ((content is SByte) || (content is SByte[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Int8, sizeof(SByte));
			else if ((content is Byte) || (content is Byte[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.UInt8, sizeof(Byte));
			else if ((content is Int16) || (content is Int16[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Int16, sizeof(Int16));
			else if ((content is UInt16) || (content is UInt16[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.UInt16, sizeof(UInt16));
			else if ((content is Int32) || (content is Int32[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Int32, sizeof(Int32));
			else if ((content is UInt32) || (content is UInt32[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.UInt32, sizeof(UInt32));
			else if ((content is Int64) || (content is Int64[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Int64, sizeof(Int64));
			else if ((content is UInt64) || (content is UInt64[]))
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.UInt64, sizeof(UInt64));
			else if (content is bool)
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.Int8, sizeof(SByte));
			else if (content is GameTalkMessage)
			{
				GameTalkMessage gtm = (GameTalkMessage)content;
				// We end up parsing entire GameTalkMessage twice - not sure if that will ever be noticeable performance wise
				byte[] gtmBytes = gtm.GetBuffer();
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, Format.GameTalkMessage, gtmBytes.Length);
			}
			else if (content is string)
			{
				// Should we default 0 for TypeCode and 1 for TypeSize?  
				// If we set both TypeCode and TypeSize to zero - then we'd be more
				// likely to be backward compatible.  That is, if the GameTalk code
				// compares the KeyContent meta-data number to zero or one (like it did
				// back in GameTalk 2.6.x or earlier) - it would be more likely to match 
				// (since the only other data we put in the KeyContent meta-data is the endian.
				k = new KeyContent(key, Format.String, content, BitConverter.IsLittleEndian, 0, 1);
			}
			else if (content is byte[])
			{
				// Should we default 0 for TypeCode and 1 for TypeSize?  
				// If we set both TypeCode and TypeSize to zero - then we'd be more
				// likely to be backward compatible.  That is, if the GameTalk code
				// compares the KeyContent meta-data number to zero or one (like it did
				// back in GameTalk 2.6.x or earlier) - it would be more likely to match 
				// (since the only other data we put in the KeyContent meta-data is the endian.
				k = new KeyContent(key, Format.Binary, content, BitConverter.IsLittleEndian, 0, 1);
			}
			else
				Debug.Assert(false, "Unrecognized type - can not auto-serialize this data");

			if (k != null)
				mKeysContents.Add(k);
		}

		/// <summary>
		/// Add float
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, float content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add float array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, float[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add double
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, double content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add double array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, double[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add SByte
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, SByte content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add SByte array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, SByte[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Byte
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Byte content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Byte array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Byte[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int16
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Int16 content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int16 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Int16[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt16
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, UInt16 content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt16 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, UInt16[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int32
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, int content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int32 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, int[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt32
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, uint content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt32 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, uint[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int64
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Int64 content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add Int64 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		public void Add(string key, Int64[] content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt64
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, UInt64 content)
		{
			AddObject(key, content);
		}

		/// <summary>
		/// Add UInt64 array
		/// </summary>
		/// <param name="key"></param>
		/// <param name="content"></param>
		[CLSCompliantAttribute(false)]
		public void Add(string key, UInt64[] content)
		{
			AddObject(key, content);
		}

		public void Add(string key, GameTalkMessage message)
		{
			AddObject(key, message);
		}

		/// <summary>
		/// Adds a key and its content to a message.
		/// </summary>
		/// <param name="key">The key string.</param>
		/// <param name="content">The content object.</param>
		public void AddKeyContentAsBinary(string key, System.Byte[] content)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			AddKeyContent(key, content, Format.Binary);
		}

		/// <summary>
		/// Adds a key and its content to a message.
		/// </summary>
		/// <param name="key">The key string.</param>
		/// <param name="content">The content object.</param>
		public void AddKeyContentAsString(string key, string content)
		{
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

			AddKeyContent(key, content, Format.String);
		}

        /// <summary>
        /// Modifies a key and the corresponding content by its index.
        /// </summary>
        /// <param name="index">The index of the pair key / content to modify</param>
        /// <param name="newKey">The new key value</param>
        /// <param name="newContent">The new content value</param>
        public void ModifyContent(int index, string newKey, object newContent)
        {
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
			
            mKeysContents[index].Key = newKey;
			mKeysContents[index].Content = newContent;
        }

        /// <summary>
        /// Deletes a key and its corresponding content by its index.
        /// </summary>
        /// <param name="index">The index of the pair key / content to modify</param>
        public void DeleteKey(int index)
        {
			Debug.Assert(mWholeMessageByteArray == null, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");

            mKeysContents.RemoveAt(index);
		}

		#endregion

		#region Encoding / Decoding Methods

		/// <summary>
		/// Convert data to bytes from a binary reader.
		/// </summary>
		/// <param name="reader">The reader containing the data.</param>
		/// <returns>The byte array.</returns>
		private static byte[] ReadBinary(BinaryReader reader)
		{
			// Read the lengt of the string.
			System.UInt32 length = reader.ReadUInt32();

			// Read the ascii string bytes.
			System.Byte[] bytes = reader.ReadBytes((int)length);

			return bytes;
		}

		/// <summary>
		/// This presumes that the bytes have a uint prefix in the byte array that indicates the length of the string
		/// </summary>
		/// <param name="bytes"></param>
		/// <returns></returns>
		private static string ReadString(byte[] bytes)
		{
			MemoryStream memStream = new MemoryStream(bytes);
			BinaryReader binaryReader = new BinaryReader(memStream);
			try
			{
				return ReadString(binaryReader);
			}
			catch
			{
			}
			finally
			{
				memStream.Close();
				binaryReader.Close();
			}
			return null;
		}

		/// <summary>
		/// Convert data to string from a binary reader.
		/// </summary>
		/// <param name="reader">The reader containing the data.</param>
		/// <returns>The string.</returns>
		private static string ReadString(BinaryReader reader)
		{
			// Read the lengt of the string.
			System.UInt32 length = reader.ReadUInt32();

			// Read the ascii string bytes.
			System.Byte[] bytes = reader.ReadBytes((int)length);
			// Get the string from the ascii bytes.
			string text = null;
			if ((bytes != null) && (bytes.Length > 0) && (bytes[bytes.Length - 1] == 0)) // If this is a null delimited string - drop it so .NET can handle this
				text = System.Text.ASCIIEncoding.ASCII.GetString(bytes, 0, bytes.Length - 1);
			else
				text = System.Text.ASCIIEncoding.ASCII.GetString(bytes);

			return text;
		}

		/// <summary>
		/// Convert this byte array to a string - strip null delimiter if there is one
		/// </summary>
		/// <param name="bytes"></param>
		/// <returns></returns>
		private static string ReadStringWithNoLengthPrefix(byte[] bytes)
		{
			// Get the string from the ascii bytes.
			string text = null;
			if ((bytes != null) && (bytes.Length > 0) && (bytes[bytes.Length - 1] == 0)) // If this is a null delimited string - drop it so .NET can handle this
				text = System.Text.ASCIIEncoding.ASCII.GetString(bytes, 0, bytes.Length - 1);
			else
				text = System.Text.ASCIIEncoding.ASCII.GetString(bytes);

			return text;
		}

		private static void WriteString(BinaryWriter writer, string input)
		{
			Byte[] buffer = System.Text.Encoding.ASCII.GetBytes(input);
			WriteString(writer, buffer);
		}

		private static void WriteString(BinaryWriter writer, byte[] buffer)
		{
			// Write the lengt of the string.
			writer.Write(buffer.Length);

			// Write the ascii string bytes.
			writer.Write(buffer);
		}

		private bool GetKeyContentMetaData(int index, out int format, out int typeCode, out int typeSize)
		{
			// What are good defaults for the following three out parameters
			format = 0;
			typeCode = 0;
			typeSize = 1;
			if (mWholeMessageByteArray != null)
			{
				Debug.Assert(false, "This GameTalkMessage has not been parsed and therefore its key structure cannot be examined");
				return false;
			}
			// Just let .NET throw exception - instead of doing an out of range check

			KeyContent k = mKeysContents[index];
			format = (int)k.Type;
			typeCode = (int)k.TypeCode;
			typeSize = k.TypeSize;

			return true;
		}

		private byte[] Serialize(object content)
		{
			// Should we use an empty byte array instead of null?
			byte[] serilizedBytes = null;
			if (content is float)
				serilizedBytes = BitConverter.GetBytes((float)content);
			else if (content is float[])
			{
				List<byte> bytes = new List<byte>();
				float[] floatArray = (float[])content;
				for (int i = 0;i < floatArray.Length;i++)
					bytes.AddRange(BitConverter.GetBytes(floatArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is double)
				serilizedBytes = BitConverter.GetBytes((double)content);
			else if (content is double[])
			{
				List<byte> bytes = new List<byte>();
				double[] myArray = (double[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is SByte)
				serilizedBytes = BitConverter.GetBytes((SByte)content);
			else if (content is SByte[])
			{
				List<byte> bytes = new List<byte>();
				SByte[] myArray = (SByte[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is Byte)
				serilizedBytes = new byte[]{(Byte)content};
			else if (content is Byte[])
				serilizedBytes = (Byte[])content;
			else if (content is Int16)
				serilizedBytes = BitConverter.GetBytes((Int16)content);
			else if (content is Int16[])
			{
				List<byte> bytes = new List<byte>();
				Int16[] myArray = (Int16[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is UInt16)
				serilizedBytes = BitConverter.GetBytes((UInt16)content);
			else if (content is UInt16[])
			{
				List<byte> bytes = new List<byte>();
				UInt16[] myArray = (UInt16[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is Int32)
				serilizedBytes = BitConverter.GetBytes((int)content);
			else if (content is Int32[])
			{
				List<byte> bytes = new List<byte>();
				Int32[] myArray = (Int32[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is UInt32)
				serilizedBytes = BitConverter.GetBytes((UInt32)content);
			else if (content is UInt32[])
			{
				List<byte> bytes = new List<byte>();
				UInt32[] myArray = (UInt32[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is Int64)
				serilizedBytes = BitConverter.GetBytes((Int64)content);
			else if (content is Int64[])
			{
				List<byte> bytes = new List<byte>();
				Int64[] myArray = (Int64[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is UInt64)
				serilizedBytes = BitConverter.GetBytes((UInt64)content);
			else if (content is UInt64[])
			{
				List<byte> bytes = new List<byte>();
				UInt64[] myArray = (UInt64[])content;
				for (int i = 0; i < myArray.Length; i++)
					bytes.AddRange(BitConverter.GetBytes(myArray[i]));
				serilizedBytes = bytes.ToArray();
			}
			else if (content is Boolean)
			{
				serilizedBytes = new byte[1];
				serilizedBytes[0] = (bool)content ? (byte)1 : (byte)0;
			}
			else if (content is GameTalkMessage)
			{
				GameTalkMessage tempGtm = (GameTalkMessage)content;
				serilizedBytes = tempGtm.GetBuffer();
			}

			return serilizedBytes;
		}

		/// <summary>
		/// Get the buffer for this message to send by TCP/IP.
		/// </summary>
		/// <returns>The bytes representing the message.</returns>
		public byte[] GetBuffer()
		{
			if (mWholeMessageByteArray != null)
				return mWholeMessageByteArray;

			// Either we get the exact buffer size (using GetBufferSize()) and then use stream.GetBuffer() (which doesn't make a copy of the contained byte array)
			// or we don't use (GetBufferSize()) and just use a bufferSize of 8 - then use stream.ToArray() - ToArray() incurs the cost of doing an in memory copy - we need
			// to profile this to determine which approach is better for the "average type of traffic"
			//int bufferSize = GetBufferSize();
			// Or just say - it is eight and hope the .net's resizing performance isn't worse than us iterating over all the content twice (instead of just once)
			int bufferSize = 8;
			using (MemoryStream stream = new MemoryStream(bufferSize))
			{
				using (BinaryWriter writer = new BinaryWriter(stream, System.Text.Encoding.ASCII))
				{
					// Write the message Id
					WriteString(writer, this.ChannelId);

					// Write the number of keys.
					writer.Write(this.NumberOfKeys);

					// Write each key content pair.
					for (int i = 0; i < this.NumberOfKeys; i++)
					{
						WriteString(writer, this.GetKey(i));
						int format, typeCode, typeSize;
						GetKeyContentMetaData(i, out format, out typeCode, out typeSize);
						// Write the type.
						uint keyContentMetaData = Convert.ToUInt32(format);
						if (!BitConverter.IsLittleEndian)
							keyContentMetaData |= 128;
						keyContentMetaData |= (Convert.ToUInt32(typeCode)) << 8;
						keyContentMetaData |= (Convert.ToUInt32(typeSize)) << 16;
						// We could inject other data (using bit operations) into the keyContentMetaData at this point - as long as we don't modify the least significant bit
						writer.Write(keyContentMetaData);
						if (Format.String == (Format)format)
							WriteString(writer, (string)this.GetContent(i));
						else // Don't bother using if (Format.Binary == type) - we just assume binary regardless of type (float, int, etc)
						{
							object contentObj = this.GetContent(i);
							byte[] buffer = new byte[0];
							if (contentObj is byte[])
								buffer = (byte[])contentObj;
							else
								buffer = Serialize(contentObj);
							WriteString(writer, buffer);
						}
					}

					if (mLeftOvers != null)
						writer.Write(mLeftOvers);

					// Flush the writer.
					writer.Flush();

					//return stream.GetBuffer();
					return stream.ToArray();
				}
			}
		}

		/// <summary>
		/// This method iterates over all the Key/Content pairs in a GameTalkMessage - all just to get the exact number of bytes
		/// that it will serialize to
		/// </summary>
		/// <returns></returns>
		private int GetBufferSize()
		{
			if (mWholeMessageByteArray != null)
				return mWholeMessageByteArray.Length;

			int bufferCount = 0;
			byte[] buffer = null;

			// Count the message Id
			buffer = System.Text.Encoding.ASCII.GetBytes(this.ChannelId);
			bufferCount += 4 + buffer.Length;
			
			// Count the number of keys.
			bufferCount += 4;

			// Count all content in each key content pair.
			for (int i = 0; i < this.NumberOfKeys; i++)
			{
				buffer = System.Text.Encoding.ASCII.GetBytes(this.GetKey(i));
				bufferCount += 4 + buffer.Length;
				
				// Count the content type.
				Format type = this.GetType(i);
				bufferCount += 4;

				if (Format.String == type)
				{
					buffer = System.Text.Encoding.ASCII.GetBytes((string)this.GetContent(i));
					bufferCount += 4 + buffer.Length;
				}
				else // Don't need to do this "if (Format.Binary == type)" - because if it isn't string - it is binary (even if it is Float or some other format)
				{
					object contentObj = this.GetContent(i);
					byte[] contentBuffer = new byte[0];
					if (contentObj is byte[])
						contentBuffer = (byte[])contentObj;
					else
						contentBuffer = Serialize(contentObj);

					bufferCount += 4 + contentBuffer.Length;
				}
			}

			if (mLeftOvers != null)
				bufferCount += mLeftOvers.Length;

			return bufferCount;
		}

		#endregion

		public string ToStringMinusChannel()
		{
			StringBuilder output = new StringBuilder();
			if (mWholeMessageByteArray != null)
				output.Append(" Number of keys: " + this.NumberOfKeys + " Full Message Bytes: " + mWholeMessageByteArray.Length);
			else
			{
				for (int i = 0; i < this.NumberOfKeys; i++)
				{
					output.Append(" [(");
					output.Append(this.GetKey(i));
					output.Append(") (");
					output.Append(this.GetContent(i).ToString());
					output.Append(")]");
				}
			}
			return output.ToString();
		}

		public new string ToString()
		{
			StringBuilder output = new StringBuilder();
			output.Append(this.ChannelId);
			output.Append(ToStringMinusChannel());
			return output.ToString();
		}
	}

	public class SyncReplyGameTalkMessage : GameTalkMessage
	{
		// We use a send delegate to avoid having GameTalkMessages "know" (have a circular dependency) on GameTalk
		public delegate bool SendDelegate(string target, GameTalkMessage message);

		private SendDelegate mSendDelegate = null;

		private bool mReplied = false;

		public bool Replied
		{
			get { return mReplied; }
		}

		public SyncReplyGameTalkMessage(GameTalkMessage gameTalkMessage, SendDelegate sendDelegate,
			string synchroReplyChannel)
			: base(synchroReplyChannel)
		{
			mSender = gameTalkMessage.Sender;
			mSendDelegate = sendDelegate;

			uint synchroId = 0;
			if (!SyncReplyGameTalkMessage.GetSynchroId(gameTalkMessage, ref synchroId)) // Something has gone wrong
			{
				Debug.Assert(false, "The synchronized message received does not contain a synchro id - something has gone wrong!");
				return;
			}

			AddChunkData(BitConverter.GetBytes(synchroId), (uint)GameTalkMessage.InternalType.SynchroId); 
		}

		public bool Reply()
		{
			// In order for mReplied to be true - the user must not only try to 
			// send a reply - the send of the reply must also succeed
			mReplied = mSendDelegate("@."+mSender, this);
			return mReplied;
		}

		public static bool GetSynchroId(GameTalkMessage gameTalkMessage, ref uint synchroId)
		{
			byte[] synchroIdBytes = gameTalkMessage.GetChunkData((uint)GameTalkMessage.InternalType.SynchroId);
			if ((synchroIdBytes == null) || (synchroIdBytes.Length != 4))
				return false;

			synchroId = BitConverter.ToUInt32(synchroIdBytes, 0);
			return true;
		}
	}

	public class Network
    {
        #region Ip Address Management

        /// <summary>
        /// Convert a Dns name to an ip address.
        /// </summary>
        /// <param name="hostName">The host name to be resolved.</param>
        /// <returns>The ip address if resolved otherwise the host name is returned.</returns>
        public static string ResolveIpAddress(string hostName)
        {
            string ipAddress = hostName;
            try
            {
                // Then using host name, get the IP address list..
                // Important that we use Dns.GetHostAddresses instead of Dns.GetHostEntry - since if you give GetHostEntry
                // an IP address it can attempt a reverse look-up and end up with a host name that was unintended - this
                // only comes up once in a blue moon - but GetHostEntry will get the wrong address list if it is handed a raw IP address
                IPAddress[] addressList = Dns.GetHostAddresses(hostName);
                for (int i = 0; i < addressList.Length; i++)
                {
                    // We will force use of IPV4 - since IPV4 and IPV6 aren't compatible and we can't guarantee that all 
                    // computers interested in connecting over GameTalk will have IPV6 (AddressFamily.InterNetworkV6)
                    if (addressList[i].AddressFamily == AddressFamily.InterNetwork)
                    {
                        ipAddress = addressList[i].ToString();
                        break;
                    }
                }

            }
            catch
            {
            }

            return ipAddress;
        }

		public static bool IsLocalPcIpAddress(string ipAddress)
		{
			if ((ipAddress.ToLower() == "localhost") || (ipAddress == "127.0.0.1"))
				return true;

			string localHostName = Dns.GetHostName();
			IPAddress[] localIpAddresses = Dns.GetHostAddresses(localHostName);
			foreach (IPAddress localIpAddress in localIpAddresses)
			{
				if (localIpAddress.ToString() == ipAddress)
					return true;
			}

			IPAddress[] queriedIpAddresses = Dns.GetHostAddresses(ipAddress);
			foreach (IPAddress localIpAddress in localIpAddresses)
			{
				foreach (IPAddress queriedIpAddress in queriedIpAddresses)
				{
					if (localIpAddress.Equals(queriedIpAddress))
						return true;
				}
			}

			return false;
		}

        #endregion

    }

	public enum LoggingLevel
	{
		INVALID = 0,
		NO_LOGGING = 1,
		NO_MESSAGE_LOGGING = 2,
		META_DATA = 3,
		FULL_LOGGING = 4,
	}

	[Serializable()]
	public class Target : ICloneable
	{
		private string mName = string.Empty;
		private string mNameLowerCase = string.Empty;
		private string mPlatform = string.Empty;
		private string mVersion = string.Empty;
		private string mDnsName = string.Empty;
		private string mIpAddress = string.Empty;
		private bool mAutoResolveIp = false;
		private bool mSameIpAddressAndPortAsServer = false;

		public string Name
		{
			get { return mName; }
			set
			{
				if (value != null)
				{
					mName = value;
					mNameLowerCase = mName.ToLower();
				}
			}
		}
		public string NameLowerCase
		{
			get { return mNameLowerCase; }
		}
		public string Platform
		{
			get { return mPlatform; }
			set { if (value != null) mPlatform = value; }
		}
		public string DnsName
		{
			get { return mDnsName; }
			set { if (value != null) mDnsName = value; }
		}
		public string IpAddress
		{
			get { return mIpAddress; }
			set { if (value != null) mIpAddress = value; }
		}
		public bool AutoResolveIp
		{
			get { return mAutoResolveIp; }
			set { mAutoResolveIp = value; }
		}
		public int Port = 9876;

		public bool SameIpAddressAndPortAsServer
		{
			get { return mSameIpAddressAndPortAsServer; }
			set { mSameIpAddressAndPortAsServer = value; }
		}

		public string Version
		{
			get { return mVersion; }
			set { mVersion = value; }
		}

		public Target(string name, string platform, string version, string dnsName, string ipAddress, bool autoResolveIp, int port)
		{
			mName = name;
			mNameLowerCase = name.ToLower();
			mPlatform = platform;
			mVersion = version;
			mDnsName = dnsName;
			mIpAddress = ipAddress;
			mAutoResolveIp = autoResolveIp;
			Port = port;
		}

		/// <summary>
		/// In the case of registered targets (passive mode) - the version isn't known until the target sends its information to the GameTalkServer
		/// </summary>
		/// <param name="name"></param>
		/// <param name="platform"></param>
		/// <param name="dnsName"></param>
		/// <param name="ipAddress"></param>
		/// <param name="autoResolveIp"></param>
		/// <param name="port"></param>
		public Target(string name, string platform, string dnsName, string ipAddress, bool autoResolveIp, int port)
		{
			mName = name;
			mNameLowerCase = name.ToLower();
			mPlatform = platform;
			mDnsName = dnsName;
			mIpAddress = ipAddress;
			mAutoResolveIp = autoResolveIp;
			Port = port;
		}

		public object Clone()
		{
			Target targetCopy = new Target(this.Name, this.Platform, this.Version, this.DnsName, this.IpAddress, this.AutoResolveIp, this.Port);
			return targetCopy;
		}
	}
}

