using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

using EA.GameTalkMessages;

namespace EA.GameTalkRouter
{
	public class RegisteredTcpSocket : TcpSocketListener
	{
		class MessageAndSender
		{
			public GameTalkMessage mGameTalkMessage = null;
			public string mSender = null;

			public MessageAndSender(GameTalkMessage gameTalkMessage, string sender)
			{
				mGameTalkMessage = gameTalkMessage;
				mSender = sender;
			}
		}

		private Queue<MessageAndSender> mUnconnectedMessageQueue = new Queue<MessageAndSender>();

		private bool mSameIpAddressAndPortAsServer = false;

		public bool Connect()
		{
			if (Connected)
				return true;

			if (mSameIpAddressAndPortAsServer)
			{
				// Warn the user that they have a target whose ip address and port matches GameTalkServer's
				LoggingEventArgs e = new LoggingEventArgs(LoggingEventType.LogComment, "The target/client " + this.TargetNameLowerCase + " has the same IP address and port as the GameTalkServer - update its details in the TargetManager.");
				ServerEventManager.LoggingSend(this, e);
				return false;
			}

			mTcpConnect = new TcpConnect();
			bool result = false;
			if (mIpAddress.Length > 0)
			{
				result = mTcpConnect.Connect(mIpAddress, mPort);
				if (result)
				{
					mClientSocket = mTcpConnect.ClientSocket;

					// The default time for the KeepAlive to activate is 2 hours.
					// This is controlled by windows as far as I know...
					mClientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
					OnSuccessfulConnection();
					// Don't do the following - since it could result in messages being sent to this client - before 
					// it is actually added to our list of connected clients
					//OnReceiptOfConnectorsName();
					mProcessDataFromWire = true;
				}
			}
			return result;
		}

		/// <summary>
		/// Passive mode constructor - this is used when the GameTalkServer is the one 
		/// initiating the connection to the target.
		/// 
		/// NOTE: YOU MUST CALL OnReceiptOfConnectorsName after you invoke this constructor AND have added this TcpSocketListener
		/// to the list of connected TcpSocketListeners
		/// </summary>
		public RegisteredTcpSocket(string targetName, string ipAddress, int port, 
			string platformName, TcpServer.AddListenerDelegate addListenerDelegate,
			bool sameIpAddressAndPortAsServer):
			base(targetName, ipAddress, port, platformName)
		{
			mSameIpAddressAndPortAsServer = sameIpAddressAndPortAsServer;
			// By default our reading/processing threads aren't running
			mProcessDataFromWire = false;
			// We have to add this TcpSocketListener to the list before we can add it to all channels
			addListenerDelegate(this);

			ServerEventArgs serverEvent = new ServerEventArgs(ServerEventType.RegisterClientToAllChannels, mTargetNameLowerCase);
			ServerEventManager.Send(this, serverEvent);
		}

		~RegisteredTcpSocket()
		{
			KillThisConnectionsThreads();
		}

		public void SendAllQueuedMessages()
		{
			try
			{
				lock (mUnconnectedMessageQueue)
				{
					if (mUnconnectedMessageQueue.Count > 0) // We're connected - but we may have a queue of messages to process before we can send this next one
					{
						// Dunno that dequeue'ing is anymore efficient than iterating and clearing
						while (mUnconnectedMessageQueue.Count > 0)
						{
							MessageAndSender queuedMessage = mUnconnectedMessageQueue.Dequeue();
							SerializeAndAsyncSend(queuedMessage.mSender, queuedMessage.mGameTalkMessage, true);
						}
					}
				}
			}
			catch (Exception exception)
			{
				TcpServer.DebugLog("[RegisteredTcpSocket.SendAllQueuedMessages] exception: "+exception.Message);
			}
		}

		public void ClearQueuedMessages()
		{
			lock (mUnconnectedMessageQueue)
			{
				mUnconnectedMessageQueue.Clear();
			}
		}

		/// <summary>
		/// Send a message to the client
		/// </summary>
		/// <param name="message">The message to send to the client</param>
		public override void SendMessage(string sender, GameTalkMessage message, bool logMessage)
		{
			//if (!Connected) // Testing Connected before each send is costly in performance - so use mProcessDataFromWire instead
			if (!mProcessDataFromWire)
			{
				try
				{
					lock (mUnconnectedMessageQueue)
					{
						mUnconnectedMessageQueue.Enqueue(new MessageAndSender(message, sender));
					}
				}
				catch (Exception exception)
				{
					TcpServer.DebugLog("RegisteredTcpSocket.SendMessage] exception: " + exception.Message);
				}
				return;
			}
			else
				SendAllQueuedMessages();

			SerializeAndAsyncSend(sender, message, logMessage);
		}

		private bool mUserHasRemovedRegisteredListener = false;
		public override bool IsMarkedForDeletion
		{
			get { return mUserHasRemovedRegisteredListener; }
			set { mUserHasRemovedRegisteredListener = value; }
		}

		public bool HasAConnection
		{
			get
			{
				if (mClientSocket != null)
					return true;
				else
					return false;
			}
		}

		public override void KillThisConnectionsThreads()
		{
			// If mClientSocket is null - we've already shut down this connection
			if (mClientSocket == null)
				return;

			base.KillThisConnectionsThreads();
		}

		public void StopSocketAndReregister()
		{
			// If mClientSocket is null - we've already shut down this connection
			if (mClientSocket == null)
				return;

			base.KillThisConnectionsThreads();
			// It is possible this is already registered on "all channels" - necessary precaution
			ServerEventArgs serverEvent = new ServerEventArgs(ServerEventType.RegisterClientToAllChannels, mTargetNameLowerCase);
			ServerEventManager.Send(this, serverEvent);
		}

		public void UpdateDetails(Target target)
		{
			if ((this.IpAddress != target.IpAddress) ||
				(this.Port != target.Port) ||
				(this.PlatformName != target.Platform))
			{
				if (!this.Connected) // Checking whether we are connected is expensive - only do it if you notice a mismatch
				{
					this.mIpAddress = target.IpAddress;
					this.mPort = target.Port;
					this.mPlatformName = target.Platform;
					mUniqueName = string.Format("{0}:{1}", mIpAddress, mPort);
					this.mSameIpAddressAndPortAsServer = target.SameIpAddressAndPortAsServer;
				}
			}
		}
	}
}
