using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Microsoft.Win32;

using EA.GameTalkMessages;
using EA.GameTalkRouter;

namespace EA.GameTalkServer
{
	class GameTalkTcpServer : TcpServer
	{
        private readonly static string mPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"GameTalkServer\");

		protected ConnectionInitiator mConnectionInitiator = null;
		private Thread mConnectionInitiatorThread = null;

		public GameTalkTcpServer(int port)
			: base(port, mPath, GameTalkConfigReader.Singleton)
		{
			mConnectionInitiator = new ConnectionInitiator(new BufferMessageDelegate(BufferMessage),
				new IsClientWithSameNameConnectedDelegate(IsClientWithSameNameConnected),
				new GetConnectionForIpAndPortDelegate(GetConnectionForIpAndPort),
				new AddListenerDelegate(AddListener), 
				new UpdateRegisteredClientsDelegate(UpdateRegisteredTargets),
				new GetAllListenersDelegate(GetAllListeners),
				mDefaultServer.ToString(), ServerPort);

			PluginManager.Instance.LoadServices();
			foreach (IGameTalkService gameTalkService in PluginManager.Instance.GameTalkServices)
			{
				gameTalkService.RegisterAddListenerDelegate(AddListener);
				gameTalkService.RegisterGetAllListenersDelegate(GetAllListeners);
				gameTalkService.Start();
			}
		}

		public override void StartServer()
		{
			base.StartServer();

			// Start the thread that will attempt to connect to registered clients/targets
			mConnectionInitiatorThread = new Thread(mConnectionInitiator.Start);
			mConnectionInitiatorThread.Name = "ConnectionInitiator";
			mConnectionInitiatorThread.Start();
		}

		protected override void StopServer()
		{
			base.StopServer();
			// Before we call base.StopServer - lets stop our Connection-Initiator thread from establishing new conneciton or firing any events
			mConnectionInitiator.Stop();
			if (mConnectionInitiatorThread != null)
			{
				mConnectionInitiatorThread.Join(500);
				if (mConnectionInitiatorThread.IsAlive)
					mConnectionInitiatorThread.Abort();

				mConnectionInitiatorThread = null;
			}

			base.StopServer();
		}

		/// <summary>
		/// Possibly distribute onto own thread - because this will happen on EVERY message sent
		/// </summary>
		/// <param name="lowerCaseToTargetName"></param>
		/// <param name="m"></param>
		private void TellUserIfTheTargetWithTheSameNameDidNotReceiveMessage(string lowerCaseToTargetName, MessageEventArgs m)
		{
			// The negation of this statement doesn't prove anything (to determine if a target with 
			// an exact matching name wasn't sent the message) because the m.FoundTargetWithExactMatchingName 
			// will only be set to true if the "channel traversal" succeeds
			if (m.FoundTargetWithExactMatchingName && m.SentToTargetWithExactMatchingName)
				return;
			else
			{
				// The following statement is linear (i.e. expensive) on the number of connected targets (hence for the motivation to potentially put this on its own thread)
				Listener[] socketListeners = GetSocketListenersUsingTargetName(lowerCaseToTargetName);
				if ((socketListeners.Length > 0) && !m.SentToTargetWithExactMatchingName)
				{
					if (!socketListeners[0].ContainsChannel(m.Message.ChannelId))
						Logger.LogCommentMessage(string.Format("Attempted to send message to {0} on channel {1} - but it was not sent because the intended recipient hasn't registered on that channel.", lowerCaseToTargetName, m.Message.ChannelId));
					else
						Logger.LogCommentMessage(string.Format("Attempted to send message to {0} on channel {1} - but it was not sent.  Oddly enough the intended recipient is registered on that channel.", lowerCaseToTargetName, m.Message.ChannelId));
				}
			}
		}

		protected override void BroadcastMessage(MessageEventArgs m)
		{
			string channel = m.Message.ChannelId;
			string toTarget = m.ToTargetLowerCase;
			// Since clients could receive a message from the server before they can register their channels - we'll always 
			// send them server messages
			if (channel == GameTalkMessage.sServerMessageID)
			{
				SendMessageToMatchingTargets(m, toTarget);
				return;
			}

			// Send message to any listeners who have matching channels
			SendMessageToMatchingTargetsFilterChannel(m, toTarget, channel);
			// We could provide the condition "if (!m.SendOnlyToSpecificTarget)" - but I don't think it really saves us anything by adding it
			// Send message to any listeners who are listening on all channels (the empty string)
			SendMessageToMatchingTargetsFilterChannel(m, toTarget, "");

			TellUserIfTheTargetWithTheSameNameDidNotReceiveMessage(toTarget, m);

			// See if there is any registered client whose name would match this 
			// message using the "target matching semantics" of GameTalk
			mConnectionInitiator.AddMessage(m);
		}
	}
}
