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

using System;
using System.Collections.Generic;

using EA.GameTalkMessages;

namespace EA.GameTalkRouter
{
	/// <summary>
	/// Function prototype for event handler
	/// </summary>
	public delegate void ServerEventHandler(object sender, ServerEventArgs e);

	public delegate void LoggingEventHandler(object sender, LoggingEventArgs e);

	public enum ServerEventType
	{
		/// <summary>
		/// Broadcast the message to the intended recipients.
		/// </summary>
		BroadcastMessage,

		/// <summary>
		/// Configure the server using the message sent by the client.
		/// </summary>
		ClientConfigRequest,

		/// <summary>
		/// New target has connected - send any buffered messages to that client.
		/// </summary>
		SendBufferToTarget,

        /// <summary>
        /// Send a list of all clients that are currently connected to target.
        /// </summary>
        SendListOfClientsToTarget,

        /// <summary>
        /// Send a list of all registered clients (clients in the registry)
        /// </summary>
        SendListOfRegisteredClients,

		/// <summary>
		/// This is used in case we are dealing with an "old school" GameTalk client - in which
		/// case it will need to be registered to all channels
		/// </summary>
		RegisterClientToAllChannels,

		/// <summary>
		/// If we later learn this isn't an "old school" GameTalk client - take this client's
		/// "all channels" registration away.  Note: it is important that this unregistration
		/// occurs before the client registers its actual set of channels
		/// </summary>
		UnregisterClientFromAllChannels,
	}

	public enum LoggingEventType
	{
		/// <summary>
		/// Clear the log file
		/// </summary>
		ClearLog,

		/// <summary>
		/// Received connection.
		/// </summary>
		LogConnection,

		/// <summary>
		/// Receive name of connection
		/// </summary>
		LogConnectionName,

		/// <summary>
		/// Received disconnection.
		/// </summary>
		LogDisconnection,

		/// <summary>
		/// Message received from ip address
		/// </summary>
		LogMessageReceived,

		/// <summary>
		/// Message sent to target ip address
		/// </summary>
		LogMessageSent,

		/// <summary>
		/// Log a comment
		/// </summary>
		LogComment,

		/// <summary>
		/// Log a process that should be started
		/// </summary>
		LogStartProcess,

		/// <summary>
		/// Log message that will not be displayed in GameTalkMonitor
		/// </summary>
		LogHiddenMessage,

		/// <summary>
		/// Log configuration information - probably doesn't want to be displayed in same place as regular comments (i.e. LogComment)
		/// Also these messages are permanently cached for the life-time of the GameTalkServer so try to ensure that messages aren't being repeated
		/// </summary>
		LogConfiguration,
	}


	/// <summary>
	/// Base type for all events
	/// </summary>
	public class ServerEventArgs : System.EventArgs
	{
		protected ServerEventType _Type;
		private object _Param;

		/// <summary>
		/// Constructor for the Gui Events
		/// </summary>
		/// <param name="type">The type of event this object represents</param>
		/// <param name="param">The parameter for the event (type varies by event type)</param>
		public ServerEventArgs(ServerEventType type, object param)
		{
			_Type = type;
			_Param = param;
		}

		/// <summary>
		/// Constructor for the Gui Events
		/// </summary>
		/// <param name="type">The type of event this object represents</param>
		public ServerEventArgs(ServerEventType type)
		{
			_Type = type;
			_Param = null;
		}

		public ServerEventType Type
		{
			get { return _Type; }
		}

		public object Param
		{
			get { return _Param; }
		}
	}

	/// <summary>
	/// Please do not aggregate this EventArgs into any other - it is useful
	/// for profiling to see what impact logging is causing
	/// </summary>
	public class LoggingEventArgs : System.EventArgs
	{
		protected LoggingEventType _Type;
		private object _Param;

		/// <summary>
		/// Constructor for the Gui Events
		/// </summary>
		/// <param name="type">The type of event this object represents</param>
		/// <param name="param">The parameter for the event (type varies by event type)</param>
		public LoggingEventArgs(LoggingEventType type, object param)
		{
			_Type = type;
			_Param = param;
		}

		/// <summary>
		/// Constructor for the Gui Events
		/// </summary>
		/// <param name="type">The type of event this object represents</param>
		public LoggingEventArgs(LoggingEventType type)
		{
			_Type = type;
			_Param = null;
		}

		public LoggingEventType Type
		{
			get { return _Type; }
		}

		public object Param
		{
			get { return _Param; }
		}
	}

	/// <summary>
	/// Base type for all events
	/// 
	/// We do case insensitive matches on target names - so we might as well just switch the target names to lower case 
	/// and parse them once (in this constructor)
	/// </summary>
	public class MessageEventArgs : ServerEventArgs
	{
		private string _FromTarget = string.Empty;
		private string _FromTargetLowerCase = string.Empty;
		private string [] _FromTargetLowerArray = new string[0];
		private string _ToTarget = string.Empty;
		private string _ToTargetLowerCase = string.Empty;
		private string [] _ToTargetLowerArray = new string[0];
		private GameTalkMessage _Message = null;

		private static readonly char [] kSeparator = new char[]{'.'};

		private bool mSendOnlyToSpecificTarget = false;

		public bool SendOnlyToSpecificTarget
		{
			get { return mSendOnlyToSpecificTarget; }
		}

		// We use the following structure to remember which clients we've already sent this
		// message to - since we don't want to send the same message to the same client
		// twice.  If after profiling this looks expensive - we could simply mark the
		// TcpSocketListeners with a flag that indicates the message has been sent to 
		// that target - then iterate over them and clear the flag.  The only reason
		// I didn't use that approach is in case we ever find a way to truly multi-thread
		// sends across our container of TcpSocketListeners - if that happened - then 
		// you wouldn't know which thread marked the TcpSocketListener
		private Dictionary<string, string> mSocketsThatGotMessage = new Dictionary<string, string>();

		/// <summary>
		/// Currently this container is only being written to and not read - so it can 
		/// be removed (along with the functions that provide access to it)
		/// 
		/// Same as the above container, except indexed by name, instead of <ip address>:<port number>
		/// </summary>
		private Dictionary<string, string> mTargetsThatGotMessage = new Dictionary<string, string>();

		private bool mFoundTargetWithExactMatchingName = false;
		public bool FoundTargetWithExactMatchingName
		{
			get { return mFoundTargetWithExactMatchingName; }
			set { mFoundTargetWithExactMatchingName = value; }
		}

		private bool mSentToTargetWithExactMatchingName = false;
		public bool SentToTargetWithExactMatchingName
		{
			get { return mSentToTargetWithExactMatchingName; }
			set { mSentToTargetWithExactMatchingName = value; }
		}

		public void MessageHasBeenSent(string ipAddressAndPort, string targetName)
		{
			mSocketsThatGotMessage.Add(ipAddressAndPort, null);
			if (!mTargetsThatGotMessage.ContainsKey(targetName))
				mTargetsThatGotMessage.Add(targetName, null);
		}

		/// <summary>
		/// Allows user to query the MessageEventArgs to determine which targets the
		/// message
		/// </summary>
		/// <param name="ipAddressAndPort"></param>
		/// <returns></returns>
		public bool HasMessageBeenSent(string ipAddressAndPort)
		{
			return mSocketsThatGotMessage.ContainsKey(ipAddressAndPort);
		}

		/// <summary>
		/// Not currently in use
		/// 
		/// Allows user to query the MessageEventArgs to determine which targets the
		/// message
		/// </summary>
		/// <param name="targetName"></param>
		/// <returns></returns>
		public bool HasMessageBeenSentToName(string targetName)
		{
			return mTargetsThatGotMessage.ContainsKey(targetName);
		}

		/// <summary>
		/// Constructor for the Gui Events
		/// </summary>
		/// <param name="type">The type of event this object represents.</param>
		/// <param name="target">The intended recipient(s).</param>
		/// <param name="message">The message to send.</param>
		public MessageEventArgs(ServerEventType type, string fromTarget, string toTarget, GameTalkMessage message) :
			base(type)
		{
			_FromTarget = fromTarget;
			if (_FromTarget == null)
			{
				_FromTargetLowerCase = null;
				_FromTargetLowerArray = null;
			}
			else
			{
				_FromTargetLowerCase = _FromTarget.ToLower();
				_FromTargetLowerArray = _FromTargetLowerCase.Split(kSeparator);
			}
			
			_ToTarget = toTarget;
			if (_ToTarget == null)
			{
				_ToTargetLowerCase = null;
				_ToTargetLowerArray = null;
			}
			else
			{
				_ToTargetLowerCase = _ToTarget.ToLower();
				_ToTargetLowerArray = _ToTargetLowerCase.Split(kSeparator);
				// We shouldn't need this condition (_ToTargetLowerArray.Length > 0) - since if we're here we know the _ToTarget is non-null
				if (_ToTargetLowerArray[0] == GameTalkMessage.sSendOnlyToSpecificTargetString)
				{
					// If the first two characters of the recipient target is "@." - then this message will only be sent to the specified target - i.e. disable the traditional target matching semantics
					_ToTarget = toTarget.Substring(2);
					_ToTargetLowerCase = _ToTarget.ToLower();
					string[] tempToTargetLowerArray = new string[_ToTargetLowerArray.Length - 2];
					Array.Copy(_ToTargetLowerArray, 1, tempToTargetLowerArray, 0, tempToTargetLowerArray.Length);
					_ToTargetLowerArray = tempToTargetLowerArray;
					mSendOnlyToSpecificTarget = true;
				}
			}
			
			_Message = message;
		}

		public string FromTarget
		{
			get { return _FromTarget; }
		}

		public string FromTargetLowerCase
		{
			get { return _FromTargetLowerCase; }
		}

		public string [] FromTargetLowerArray
		{
			get { return _FromTargetLowerArray; }
		}

		public string ToTarget
		{
			get { return _ToTarget; }
		}

		public string ToTargetLowerCase
		{
			get { return _ToTargetLowerCase; }
		}

		public string [] ToTargetLowerArray
		{
			get { return _ToTargetLowerArray; }
		}

		public GameTalkMessage Message
		{
			get { return _Message; }
		}
	}
}
