using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Diagnostics;

using System.IO.Pipes;

using EA.GameTalkMessages;

namespace EA.GameTalkRouter
{
	class NamedPipesLogger : ILogger
	{
		private LoggingEventHandler mLoggerEventHandler = null;

		NamedPipeServerStream mTrafficPipeServer = null;
		StreamWriter mTrafficStreamWriter = null;
		//EA.GameTalkMessages.StreamString mTrafficStreamString = null;
		Thread mTrafficWriterConnectionThread = null;
		Thread mTrafficWriterThread = null;

		NamedPipeServerStream mConnectionPipeServer = null;
		StreamWriter mConnectionStreamWriter = null;
		//EA.GameTalkMessages.StreamString mConnectionStreamString = null;
		Thread mConnectionsWriterConnectionThread = null;
		Thread mConnectionsWriterThread = null;
		
		NamedPipeServerStream mMiscPipeServer = null;
		StreamWriter mMiscStreamWriter = null;
		//EA.GameTalkMessages.StreamString mMiscStreamString = null;
		Thread mMiscWriterConnectionsThread = null;
		Thread mMiscWriterThread = null;

		bool mActive = true;

		private const int mMaxSizeOfBufferedLogs = 1024 * 1024;

		internal NamedPipesLogger()
		{
			try
			{
				string versionStr = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
				// May want to append the version to our "named pipes"?  So different versions don't read other versions' data?
				//mTrafficPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkTraffic+versionStr, PipeDirection.InOut, 1);
				mTrafficPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkTraffic, PipeDirection.InOut);
				mConnectionPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkConnections, PipeDirection.InOut);
				mMiscPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkMisc, PipeDirection.InOut);
			}
			catch { }

			try
			{
				mTrafficWriterConnectionThread = new Thread(new ThreadStart(TrafficWriterConnectionThread));
				mTrafficWriterThread = new Thread(new ThreadStart(WriteTrafficLog));

				mConnectionsWriterConnectionThread = new Thread(new ThreadStart(ConnectionWriterConnectionThread));
				mConnectionsWriterThread = new Thread(new ThreadStart(WriteConnectionsLog));
				
				mMiscWriterConnectionsThread = new Thread(new ThreadStart(MiscWriterConnectionThread));
				mMiscWriterThread = new Thread(new ThreadStart(WriteMiscLog));

				mTrafficWriterConnectionThread.Start();
				mTrafficWriterThread.Start();
				mConnectionsWriterConnectionThread.Start();
				mConnectionsWriterThread.Start();
				mMiscWriterConnectionsThread.Start();
				mMiscWriterThread.Start();
			}
			catch (Exception exception)
			{
				string outerException = exception.Message + Environment.NewLine + exception.StackTrace;
			}

			// Create our event handler
			mLoggerEventHandler = new LoggingEventHandler(ServerEventReceiver);
			ServerEventManager.LoggerRegister(mLoggerEventHandler);
		}

		internal NamedPipesLogger(string loggingPath):this()
		{
		}

		internal NamedPipesLogger(string loggingPath, string miscLogFilename,
			string trafficLogFilename, string connectionsLogFilename):this(loggingPath)
		{
		}

		~NamedPipesLogger()
		{
			mActive = false;

			// If you try to Close a stream that is already closed - you get a System.ObjectDisposedException - with additional information "Cannot access a closed pipe"
			try
			{
				if (mTrafficStreamWriter != null)
					mTrafficStreamWriter.Close();
			}
			catch { }
			try
			{
			if(mTrafficPipeServer != null)
				mTrafficPipeServer.Close();
			}
			catch { }

			try
			{
			if (mConnectionStreamWriter != null)
				mConnectionStreamWriter.Close();
			}
			catch { }
			try
			{
			if (mConnectionPipeServer != null)
				mConnectionPipeServer.Close();
			}
			catch { }
			
			try
			{
			if (mMiscStreamWriter != null)
				mMiscStreamWriter.Close();
			}
			catch { }
			try
			{
			if (mMiscPipeServer != null)
				mMiscPipeServer.Close();
			}
			catch { }

			try
			{
				mTrafficWriterConnectionThread.Join(1000);
				mConnectionsWriterConnectionThread.Join(10);
				mMiscWriterConnectionsThread.Join(10);

				mTrafficWriterThread.Join(10);
				mConnectionsWriterThread.Join(10);
				mMiscWriterThread.Join(10);
			}
			catch { }
		}

		private void TrafficWriterConnectionThread()
		{
			while (mActive)
			{
				try
				{
					bool trafficPipeServerConnected = true;
					lock (mTrafficPipeServer)
					{
						trafficPipeServerConnected = mTrafficPipeServer.IsConnected;
					}
					while (!trafficPipeServerConnected)
					{
						try
						{
							lock (mTrafficPipeServer)
							{
								//pipeServer.ReadMode = PipeTransmissionMode.Message;
								mTrafficPipeServer.WaitForConnection();

								mTrafficStreamWriter = new StreamWriter(mTrafficPipeServer);
								mTrafficStreamWriter.AutoFlush = true;
								//mTrafficStreamString = new StreamString(mTrafficPipeServer);
							}
						}
						catch (Exception e)
						{
							// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
							//Debug.WriteLine("[TrafficWriterConnectionThread] Exception:\n    {0}{1}{2}", e.Message, Environment.NewLine, e.StackTrace);
							try
							{
								mTrafficPipeServer.Disconnect();
								mTrafficPipeServer.Close();
							}
							catch { }
							mTrafficPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkTraffic, PipeDirection.InOut);
						}
						Thread.Sleep(100);
						lock (mTrafficPipeServer)
						{
							trafficPipeServerConnected = mTrafficPipeServer.IsConnected;
						}
					}

					lock (mTrafficPipeServer)
					{
						trafficPipeServerConnected = mTrafficPipeServer.IsConnected;
					}
					if (trafficPipeServerConnected) // no rush - we're already connected
						Thread.Sleep(1000);
					else
						Thread.Sleep(100);
				}
				catch 
				{
					Thread.Sleep(100);
				}
			}
		}

		private void ConnectionWriterConnectionThread()
		{
			while (mActive)
			{
				try
				{
					bool connectionPipeServerConnected = true;
					lock (mConnectionPipeServer)
					{
						connectionPipeServerConnected = mConnectionPipeServer.IsConnected;
					}
					while (!connectionPipeServerConnected)
					{
						try
						{
							lock (mConnectionPipeServer)
							{
								//pipeServer.ReadMode = PipeTransmissionMode.Message;
								mConnectionPipeServer.WaitForConnection();

								mConnectionStreamWriter = new StreamWriter(mConnectionPipeServer);
								mConnectionStreamWriter.AutoFlush = true;
								//mConnectionStreamString = new EA.GameTalkMessages.StreamString(mConnectionPipeServer);
							}
						}
						catch (Exception e)
						{
							// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
							//Debug.WriteLine("[ConnectionWriterConnectionThread] Exception:\n    {0}{1}{2}", e.Message, Environment.NewLine, e.StackTrace);
							try
							{
								mConnectionPipeServer.Disconnect();
								mConnectionPipeServer.Close();
							}
							catch { }
							mConnectionPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkConnections, PipeDirection.InOut);
						}
						Thread.Sleep(100);
						lock (mConnectionPipeServer)
						{
							connectionPipeServerConnected = mConnectionPipeServer.IsConnected;
						}
					}

					lock (mConnectionPipeServer)
					{
						connectionPipeServerConnected = mConnectionPipeServer.IsConnected;
					}
					if (connectionPipeServerConnected) // no rush - we're already connected
						Thread.Sleep(1000);
					else
						Thread.Sleep(100);
				}
				catch
				{
					Thread.Sleep(100);
				}
			}
		}

		private void MiscWriterConnectionThread()
		{
			while (mActive)
			{
				try
				{
					bool miscPipeServerConnected = true;
					lock (mMiscPipeServer)
					{
						miscPipeServerConnected = mMiscPipeServer.IsConnected;
					}
					while (!miscPipeServerConnected)
					{
						try
						{
							lock (mMiscPipeServer)
							{
								//pipeServer.ReadMode = PipeTransmissionMode.Message;
								mMiscPipeServer.WaitForConnection();

								mMiscStreamWriter = new StreamWriter(mMiscPipeServer);
								mMiscStreamWriter.AutoFlush = true;
								//mMiscStreamString = new StreamString(mMiscPipeServer);
							}
						}
						catch (Exception e)
						{
							// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
							//Debug.WriteLine("[MiscWriterConnectionThread] Exception:\n    {0}{1}{2}", e.Message, Environment.NewLine, e.StackTrace);
							try
							{
								mMiscPipeServer.Disconnect();
								mMiscPipeServer.Close();
							}
							catch { }
							mMiscPipeServer = new NamedPipeServerStream(NamedPipes.kGameTalkMisc, PipeDirection.InOut);
						}
						Thread.Sleep(100);
						lock (mMiscPipeServer)
						{
							miscPipeServerConnected = mMiscPipeServer.IsConnected;
						}
					}

					lock (mMiscPipeServer)
					{
						miscPipeServerConnected = mMiscPipeServer.IsConnected;
					}
					if (miscPipeServerConnected) // no rush - we're already connected
						Thread.Sleep(1000);
					else
						Thread.Sleep(100);
				}
				catch
				{
					Thread.Sleep(100);
				}
			}
		}

		private void StartListener()
		{
		}

		private bool CloseWriter(ref StreamWriter streamWriter, string fileName, bool deleteFile)
		{
			return false;
		}

		private void StopListener()
		{
		}

		/// <summary>
		/// Without synchronizing on this entire method - we have a VERY unlikely
		/// race condition - if a ServerEventType.ClearLog and a different e.Type
		/// are received by this receiver at the same time (on different threads)
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ServerEventReceiver(object sender, LoggingEventArgs e)
		{
			if (LoggingEventType.ClearLog == e.Type)
			{
				StopListener();
				return;
			}

			StartListener();

			switch (e.Type)
			{
				case LoggingEventType.LogConnection:
					string connectionMessage = "Connection: " + e.Param.ToString().Trim();
					ConnectionWrite(connectionMessage);
					break;
				case LoggingEventType.LogConnectionName:
					string connectionNameMessage = "TargetName: " + e.Param.ToString().Trim();
					ConnectionWrite(connectionNameMessage);
					break;
				case LoggingEventType.LogDisconnection:
					string disconnectionMessage = "Disconnection: " + e.Param.ToString().Trim();
					ConnectionWrite(disconnectionMessage);
					break;
				case LoggingEventType.LogMessageReceived:
					if (ConfigReader.Instance.LogLevel == GameTalkMessages.LoggingLevel.NO_MESSAGE_LOGGING)
						return;

					string messageReceived = "MessageReceived: " + e.Param.ToString().Trim();
					TrafficWrite(messageReceived);
					break;
				case LoggingEventType.LogMessageSent:
					if (ConfigReader.Instance.LogLevel == GameTalkMessages.LoggingLevel.NO_MESSAGE_LOGGING)
						return;

					string messageSent = "MessageSent: " + e.Param.ToString().Trim();
					TrafficWrite(messageSent);
					break;
				case LoggingEventType.LogComment:
					string currentDateTime = "";
					try
					{
						currentDateTime = DateTime.Now.ToString();
					}
					catch { }
					WriteMisc(string.Format("Comment: At {0} {1}", currentDateTime, e.Param.ToString().Trim()));
					break;
				case LoggingEventType.LogHiddenMessage:
					TrafficWrite("DoNotDisplay: " + e.Param.ToString().Trim());
					break;
				case LoggingEventType.LogConfiguration:
					{
						break;
					}
				case LoggingEventType.LogStartProcess:
					WriteMisc("StartProcess: " + e.Param.ToString().Trim());
					break;
			}
		}

		StringBuilder mMiscLog = new StringBuilder();
		private void WriteMisc(string text)
		{
			try
			{
				lock (mMiscLog)
				{
					if (mConnectionsLog.Length > mMaxSizeOfBufferedLogs) // Need to stop the GameTalkServer from buffer endlessly
						mConnectionsLog.Clear();
					mMiscLog.AppendLine(text);
				}
			}
			catch (Exception exception)
			{
				string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
			}
		}


		private void WriteMiscLog()
		{
			while (mActive)
			{
				try
				{
					string miscLog = null;
					lock (mMiscLog)
					{
						if (mMiscLog.Length > 0)
							miscLog = mMiscLog.ToString();
					}

					bool wroteLog = false;
					if (miscLog != null)
					{
						lock (mMiscPipeServer)
						{
							if (!mMiscPipeServer.CanWrite || !mMiscPipeServer.IsConnected)
								continue;

							//mMiscStreamString.WriteString(text);
							//mMiscStreamWriter.WriteLine(text);
							mMiscStreamWriter.Write(miscLog);
							wroteLog = true;
						}
					}

					if (wroteLog)
					{
						lock (mMiscLog)
						{
							mMiscLog.Clear();
						}
					}
				}
				catch (Exception exception) // Mostly commonly we'll be catching the IOException that is raised if the pipe is broken or disconnected. 
				{
					// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
					string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
				}
				Thread.Sleep(50);
			}
		}

		StringBuilder mConnectionsLog = new StringBuilder();
		private void ConnectionWrite(string text)
		{
			try
			{
				lock (mConnectionsLog)
				{
					if (mConnectionsLog.Length > mMaxSizeOfBufferedLogs) // Need to stop the GameTalkServer from buffer endlessly
						mConnectionsLog.Clear();
					mConnectionsLog.AppendLine(text);
				}
			}
			catch (Exception exception)
			{
				string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
			}
		}

		private void WriteConnectionsLog()
		{
			while (mActive)
			{
				try
				{
					// We shouldn't throw away any connection information - since we want to ensure the GameTalkMonitor knows everyone that is currently connected - so check that we have a usable logging connection before we read from the connections log/buffer
					lock (mConnectionPipeServer)
					{
						if (!mConnectionPipeServer.CanWrite || !mConnectionPipeServer.IsConnected)
						{
							Thread.Sleep(50);
							continue;
						}
					}

					string connectionLog = null;
					lock (mConnectionsLog)
					{
						if (mConnectionsLog.Length > 0)
							connectionLog = mConnectionsLog.ToString();
					}

					bool wroteLog = false;
					if (connectionLog != null)
					{
						lock (mConnectionPipeServer)
						{
							if (!mConnectionPipeServer.CanWrite || !mConnectionPipeServer.IsConnected)
								continue;

							//mConnectionsStreamString.WriteString(text);
							//mConnectionStreamWriter.WriteLine(text);
							mConnectionStreamWriter.Write(connectionLog);
							wroteLog = true;
						}
					}

					if (wroteLog)
					{
						lock (mConnectionsLog)
						{
							mConnectionsLog.Clear();
						}
					}
				}
				catch (Exception exception) // Mostly commonly we'll be catching the IOException that is raised if the pipe is broken or disconnected. 
				{
					// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
					string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
				}
				Thread.Sleep(50);
			}
		}

		StringBuilder mTrafficLog = new StringBuilder();
		private void TrafficWrite(string text)
		{
			try
			{
				lock (mTrafficLog)
				{
					mTrafficLog.AppendLine(text);
				}
			}
			catch (Exception exception)
			{
				string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
			}
		}

		/// <summary>
		/// Warning!  The current flow means that if we don't currently have a connection - we'll throw away all traffic data every 50 milliseconds
		/// We could buffer the traffic - but this could also mean that we'll endlessly buffer traffic data and grow very large in memory
		/// </summary>
		private void WriteTrafficLog()
		{
			while (mActive)
			{
				try
				{
					string trafficLog = null;
					lock (mTrafficLog)
					{
						if (mTrafficLog.Length > 0)
						{
							trafficLog = mTrafficLog.ToString();
							// I'm more concerned about the buffered traffic log growing massive - so purge it regularly
							mTrafficLog.Clear();
						}
					}

					bool wroteLog = false;
					if (trafficLog != null)
					{
						lock (mTrafficPipeServer)
						{
							if (!mTrafficPipeServer.CanWrite || !mTrafficPipeServer.IsConnected)
								continue;

							// I don't really know what the WaitForPipeDrain method does - so leaving it here in case it might prove useful at a later date
							//mTrafficPipeServer.WaitForPipeDrain();

							//mTrafficStreamString.WriteString(text);
							//mTrafficStreamWriter.WriteLine(text);
							mTrafficStreamWriter.Write(trafficLog);
							wroteLog = true;
						}
					}
				}
				catch (Exception exception) // Mostly commonly we'll be catching the IOException that is raised if the pipe is broken or disconnected. 
				{
					// Logging exceptions within the logger is a bit dangerous (i.e. infinite recursion)
					string exceptionMsg = exception.Message + Environment.NewLine + exception.StackTrace;
				}
				Thread.Sleep(50);
			}
		}
	}
}
