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

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

using EA.GameTalkMessages;

namespace EA.GameTalkRouter
{
	/// <summary>
	/// Summary description for TcpSocketListener.
	/// </summary>
	public class TcpSocketListener : Listener
	{
		/// <summary>
		/// Variables that are accessed by other classes indirectly.
		/// </summary>
		protected Socket mClientSocket = null;
		protected TcpConnect mTcpConnect = null;

		protected string mIpAddress = string.Empty;
		protected int mPort = 0;

		public string IpAddress
		{
			get { return mIpAddress; }
		}

		public int Port
		{
			get { return mPort; }
		}

		/// <summary>
		/// Is our client connected
		/// </summary>
		public override bool Connected
		{
			get
			{
				if (mClientSocket != null)
					return mClientSocket.Connected;
				else
					return false;
			}
		}

		public override string RemoteEndPoint
		{
			get
			{
				if (mClientSocket != null)
					return mClientSocket.RemoteEndPoint.ToString();

				return string.Empty;
			}
		}

		/// <summary>
		/// Active mode constructor - this is used when the target is the one initiating 
		/// connection to the target.
		/// </summary>
		/// <param name="clientSocket">The socket to listen on</param>
		public TcpSocketListener(Socket clientSocket)
		{
			mClientSocket = 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();
		}

		/// <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 TcpSocketListener(string targetName, string ipAddress, int port, string platformName)
		{
			mTargetName = (targetName == null) ? string.Empty : targetName;
			mPlatformName = (platformName == null)?string.Empty:platformName;
			mIpAddress = ipAddress;
			mPort = port;
			mUniqueName = string.Format("{0}:{1}", mIpAddress, mPort);

			ParseTargetName();
		}

		protected override void OnSuccessfulConnection()
		{
			if (string.IsNullOrEmpty(mIpAddress))
			{
				try
				{
					System.Net.IPEndPoint ipEndPoint = mClientSocket.RemoteEndPoint as System.Net.IPEndPoint;
					mIpAddress = ipEndPoint.Address.ToString();
					mPort = ipEndPoint.Port;
					mUniqueName = string.Format("{0}:{1}", mIpAddress, mPort);
				}
				catch (Exception exception) // If we get an exception - fall back on this code that presumes it knows the formatting of Socket.RemoteEndPoint.ToString() (i.e. "<ip address>:<port>")
				{
					Trace.WriteLine("Exception: RemoteEndPoint: " + mClientSocket.RemoteEndPoint + " RemoteEndPoint Type: " + mClientSocket.RemoteEndPoint.GetType() + Environment.NewLine + exception.Message + Environment.NewLine + exception.StackTrace);
					// Going to call mClientSocket.RemoteEndPoint directly instead of this.RemoteEndPoint - in case RemoteEndPoint does something different one day
					mUniqueName = mClientSocket.RemoteEndPoint.ToString();
					string[] ipAddressAndPortArray = mUniqueName.Split(new char[] { ':' });
					if (ipAddressAndPortArray.Length == 2)
					{
						mIpAddress = ipAddressAndPortArray[0];
						try
						{
							mPort = Convert.ToInt32(ipAddressAndPortArray[1]);
						}
						catch { }
					}
				}
			}

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

		/// <summary>
		/// Poll the connection to make sure it is still alive
		/// 
		/// Potential optimization would be to record a date/time of the last time
		/// we received a message from this target or the last time we successfully
		/// sent a message (i.e. the last time we had successful communication with 
		/// the target).  If that was successful within the last second - then no need
		/// to ping.
		/// </summary>
		public override void Poll()
		{
			// Make sure we are still connected
			if ((mClientSocket == null) || (!mClientSocket.Connected))
			{
				ShutdownAndMarkForDeletion();
			}
			else
			{
				base.Poll();
			}
		}

		/// <summary>
		/// Start our various reader threads.
		/// </summary>
		public override void StartReaderThreads()
		{
			if (mClientSocket != null)
				base.StartReaderThreads();
		}

		private void SendCallback(IAsyncResult ar)
		{
			try
			{
				// Actually send the data  
				int bytesSent = mClientSocket.EndSend(ar);
			}
			catch (NullReferenceException)
			{
				// We can get a "NullReferenceException" since this method is invoked on a different thread
				// it after we KillThisConnectionsThreads - but this should just mean that the client has disconnected
				// we could throw a check in before we try EndSend - but we should be sending more often than disconnecting
			}
			catch (Exception e)
			{
				if (!this.mProcessDataFromWire || (!this.Connected)) // If the socket is already disconnected - nothing more to be done
					return;

				string msgForSend = string.Empty;
				try
				{
					if (ar.AsyncState is GameTalkMessage)
					{
						GameTalkMessage gtm = (GameTalkMessage)ar.AsyncState;
						msgForSend = gtm.ToString();
					}
				}
				catch { }
				LogMessage("SendCallback exception: " + e.Message + " for message: " + msgForSend);
				if (e is SocketException)
					ShutdownAndMarkForDeletion();
			}
		}

		protected override void SendData(byte[] buffer, int offset, int size, GameTalkMessage message)
		{
			try
			{
				// It'd be nice if we could name this callback thread - since in the debugger it just shows up as No Name
				mClientSocket.BeginSend(buffer, offset, size, SocketFlags.None, new AsyncCallback(SendCallback), message);
			}
			catch (Exception e)
			{
				if (e is SocketException)
					ShutdownAndMarkForDeletion();
				// rethrow so the higher level Listener can do what it likes with this exception
				throw;
			}
		}

		protected override int ReceiveData(byte[] buffer, int offset, int size)
		{
			return mClientSocket.Receive(buffer, offset, size, SocketFlags.None);
		}

		/// <summary>
		/// Method that stops all threads (listening, processing, etc) associated with this connection.
		/// </summary>
		public override void KillThisConnectionsThreads()
		{
			if (mClientSocket != null)
			{
				OnDisconnection();

				ShutdownAndMarkForDeletion();
				mClientSocket.Close();

				base.KillThisConnectionsThreads();
				// Wait until after we attempt to shut-down the threads to assign this to null - since one of the threads might be trying to access/use this socket
				mClientSocket = null;

				try
				{
					if (mTcpConnect != null)
					{
						mTcpConnect.Close();
					}
				}
				catch { }

				mTcpConnect = null;
			}
		}
	}
}
