using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

using System.IO;
using System.ComponentModel;

namespace EA.GameTalkViewer
{
	class FileIOReader
	{
		Timer mTimer;
		private System.IO.FileSystemWatcher mFileSystemWatcher = new System.IO.FileSystemWatcher();
		// This is the most amount of data that we will read into memory at once
		private long MAX_BUFFER_SIZE = 10 * 1024 * 1024;

		public double TimerInterval
		{
			set { mTimer.Interval = value; }
		}

		public bool TimerEnabled
		{
			set { mTimer.Enabled = value; }
		}

		private readonly static string mPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"GameTalkServer\");
		private readonly static string mMiscFileName = Path.Combine(mPath, @"GameTalkServer.txt");
		private long mMiscPositionInFile = 0;
		private FileStream mMiscStream = null;
		private readonly string mTrafficFile2Name = Path.Combine(mPath, @"GameTalkServerTraffic2.txt");
		private long mTrafficPositionInFile = 0;
		private FileStream mTrafficStream = null;
		private readonly string mConnectionsFileName = Path.Combine(mPath, @"GameTalkServerConnections.txt");
		private long mConnectionsPositionInFile = 0;
		private FileStream mConnectionsStream = null;
		private const string mSyncFileForMonitorToDelete = "WaitForMonitorToDelete.txt";
		private const string mSyncFileForServerToDelete = "WaitForServerToDelete.txt";

		private ISynchronizeInvoke mUIElementForThreading;

		private CommonCallbacks.SuspendLayoutOfPanelsDelegate mSuspendLayoutOfPanelsDelegate;
		private CommonCallbacks.HaveConnectionsDelegate mHaveConnectionsDelegate;
		private CommonCallbacks.ShowNoClientsConnectedDelegate mShowNoClientsConnectedDelegate;
		private CommonCallbacks.ResumeLayoutOfPanelsDelegate mResumeLayoutOfPanelsDelegate;

		private CommonCallbacks.ProcessMiscDelegate mProcessMiscDelegate;
		private CommonCallbacks.ProcessConnectionsDelegate mProcessConnectionsDelegate;
		private CommonCallbacks.ProcessTrafficDelegate mProcessTrafficDelegate;

		public FileIOReader(ISynchronizeInvoke uiElementForThreading, 
			CommonCallbacks.SuspendLayoutOfPanelsDelegate suspendLayoutOfPanelsDelegate,
			CommonCallbacks.ResumeLayoutOfPanelsDelegate resumeLayoutOfPanelsDelegate,
			CommonCallbacks.HaveConnectionsDelegate haveConnectionsDelegate,
			CommonCallbacks.ShowNoClientsConnectedDelegate showNoClientsConnectedDelegate,
			CommonCallbacks.ProcessMiscDelegate processMiscDelegate,
			CommonCallbacks.ProcessConnectionsDelegate processConnectionsDelegate,
			CommonCallbacks.ProcessTrafficDelegate processTrafficDelegate
			)
		{
			if (!Directory.Exists(mPath))
				Directory.CreateDirectory(mPath);

			mUIElementForThreading = uiElementForThreading;
			mSuspendLayoutOfPanelsDelegate = suspendLayoutOfPanelsDelegate;
			mHaveConnectionsDelegate = haveConnectionsDelegate;
			mShowNoClientsConnectedDelegate = showNoClientsConnectedDelegate;
			mResumeLayoutOfPanelsDelegate = resumeLayoutOfPanelsDelegate;
			mProcessMiscDelegate = processMiscDelegate;
			mProcessConnectionsDelegate = processConnectionsDelegate;
			mProcessTrafficDelegate = processTrafficDelegate;
			// Had to remove the FileSystemWatcher from the designer code as it couldn't handle referencing member variables or dropping code in for the path
			InitializeFileSystemWatcher();

			mTimer = new Timer(100);
			mTimer.Elapsed += Timer_Elapsed;
			mTimer.SynchronizingObject = uiElementForThreading;
			mTimer.Start();
		}

		/// <summary>
		/// Had to remove the FileSystemWatcher from the designer code as it couldn't handle referencing member variables or dropping code in for the path
		/// This is just the initialization code for mFileSystemWatcher
		/// </summary>
		private void InitializeFileSystemWatcher()
		{
			this.mFileSystemWatcher.Filter = "GameTalkServer.txt";
			this.mFileSystemWatcher.NotifyFilter = ((System.IO.NotifyFilters)(((System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.LastWrite)
						| System.IO.NotifyFilters.CreationTime)));
			//this.mFileSystemWatcher.Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"GameTalkServer\");
			this.mFileSystemWatcher.Path = mPath;
			this.mFileSystemWatcher.SynchronizingObject = mUIElementForThreading;
			this.mFileSystemWatcher.Deleted += new System.IO.FileSystemEventHandler(this.FileSystemWatcher_Deleted);
			this.mFileSystemWatcher.Created += new System.IO.FileSystemEventHandler(this.FileSystemWatcher_Created);
			this.mFileSystemWatcher.EnableRaisingEvents = true;
		}

		void Timer_Elapsed(object sender, ElapsedEventArgs e)
		{
			// Do we need to stop and start the timer in the Elapsed call-back so we don't re-enter this multiple times?
			CheckForGameTalkServerFileSwap();
			ProcessFileContent();

			// The following will fix the redraw issue with the traffic messages not looking right when scrolled - but introduces a flickering issue
			//this.mListViewMessages.Invalidate();
		}

		/// <summary>
		/// Interestingly enough - if you don't make the FileStream parameter a ref parameter 
		/// this will still work.  HOWEVER, if we ever decide to leave the stream open between 
		/// successive reads - this will fail BADLY.  In other words, since in this case, the 
		/// FileStream is always getting reset to null at the end of this method the member will 
		/// always be null outside of this method.  But, we may want to leave the stream open 
		/// between successive reads.
		/// </summary>
		/// <param name="fileStream"></param>
		/// <param name="fileName"></param>
		/// <param name="positionInFile"></param>
		/// <returns></returns>
		private byte[] GetLatestUpdatesToFile(ref FileStream fileStream, string fileName, ref long positionInFile)
		{
			byte[] buffer = null;
			try
			{
				if (fileStream == null)
				{
					FileInfo fileInfo = new FileInfo(fileName);
					if ((fileInfo.Exists) && (fileInfo.Length == positionInFile)) // The file exists - but there is nothing to be read
						return buffer;
					else if (File.Exists(fileName))
						fileStream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
					else
						positionInFile = 0;

					if (fileStream != null)
					{
						long sizeOfBuffer = fileStream.Length - positionInFile;
						// The following conditional will prevent us from reading in over X megs of data into memory
						// If the log file has grown to over 100 megs in size - this could cause a spike in memory 
						// from GameTalkViewer/Monitor and if the user's system doesn't happen to have that much
						// memory available - CRASH!
						if (sizeOfBuffer > MAX_BUFFER_SIZE)
						{
							positionInFile += sizeOfBuffer - MAX_BUFFER_SIZE;
							sizeOfBuffer = MAX_BUFFER_SIZE;
						}
						buffer = new byte[sizeOfBuffer];
						fileStream.Seek(positionInFile, SeekOrigin.Begin);
						fileStream.Read(buffer, 0, (int)sizeOfBuffer);
						positionInFile += sizeOfBuffer;
					}
				}
				else
				{
					// if we wanted to optimize our seeks based on still having the stream open - we could do this:
					//fileStream.Seek(amountExceedingMaxBufferSize, SeekOrigin.Current);
				}
			}
			catch { }
			finally
			{
				if (fileStream != null)
				{
					try
					{
						fileStream.Close();
					}
					catch { }
					fileStream = null;
				}
			}
			return buffer;
		}

		public void ProcessFileContent()
		{
			mSuspendLayoutOfPanelsDelegate();
			byte[] miscBuffer = new byte[0];
			byte[] connectionsBuffer = new byte[0];
			byte[] trafficBuffer = new byte[0];
			try
			{
				// We could just check that one of the following files exists... 
				if (File.Exists(mMiscFileName) && File.Exists(mConnectionsFileName) && File.Exists(mTrafficFile2Name))
				{
					connectionsBuffer = GetLatestUpdatesToFile(ref mConnectionsStream, mConnectionsFileName, ref mConnectionsPositionInFile);
					// Regardless of whether we have any connections - we still want to display misc comments/log-messages
					miscBuffer = GetLatestUpdatesToFile(ref mMiscStream, mMiscFileName, ref mMiscPositionInFile);
					if (mHaveConnectionsDelegate())
						trafficBuffer = GetLatestUpdatesToFile(ref mTrafficStream, mTrafficFile2Name, ref mTrafficPositionInFile);
				}
				else
				{
					mConnectionsPositionInFile = 0;
					mTrafficPositionInFile = 0;
					mMiscPositionInFile = 0;

					mShowNoClientsConnectedDelegate(true);
				}
			}
			catch { }

			mProcessConnectionsDelegate(connectionsBuffer);
			// If ProcessTraffic has returned true - then we've read in a lot of data - get the garbage collection to run
			if (mProcessTrafficDelegate(trafficBuffer))
			{
				// If you don't set the byte array to null - the garbage collector won't reclaim the memory
				trafficBuffer = null;

				System.GC.WaitForPendingFinalizers();
				System.GC.Collect();
			}
			mProcessMiscDelegate(miscBuffer);

			mResumeLayoutOfPanelsDelegate();
		}

		#region File System Watcher Events

		private void FileSystemWatcher_Created(object sender, System.IO.FileSystemEventArgs e)
		{
		}

		private void FileSystemWatcher_Deleted(object sender, System.IO.FileSystemEventArgs e)
		{
			// Clear the logging panels (since we're about to reset the file indices) - particularly the comments/misc (panel at the bottom)
			mShowNoClientsConnectedDelegate(true);

			// We are resetting the file indices based on the single deletion of this one file - but there is 
			// no reason that the server would delete one and not the others - other than a file lock issue
			mConnectionsPositionInFile = 0;
			mTrafficPositionInFile = 0;
			mMiscPositionInFile = 0;
		}

		#endregion

		private void DeleteFileCreatedByServer()
		{
			bool successfullyDeleted = false;
			while (!successfullyDeleted)
			{
				try
				{
					if (File.Exists(mPath + mSyncFileForMonitorToDelete))
						File.Delete(mPath + mSyncFileForMonitorToDelete);

					successfullyDeleted = true;
				}
				catch (Exception)
				{
					System.Threading.Thread.Sleep(0);
				}
			}
		}

		/// <summary>
		/// We have a file communication protocol - where the GameTalkServer will 
		/// create a file that it wants GameTalkViewer/GameTalkMonitor to delete
		/// After we delete that - we will create a file that the GameTalkServer
		/// will delete - at which point we can switch files we read from
		/// 
		/// Note: this is a tad convoluted - it'd be nice if someone could come up with
		/// a simpler scheme that has the same performance
		/// </summary>
		private void CheckForGameTalkServerFileSwap()
		{
			if (File.Exists(mPath + mSyncFileForMonitorToDelete))
			{
				DeleteFileCreatedByServer();

				//Don't use File.Create since it doesn't close the stream immediately - quicker to:
				FileStream fileStream = new FileStream(mPath + mSyncFileForServerToDelete, FileMode.CreateNew);
				fileStream.Close();

				// Since we should only be switching the files we read from fairly 
				// infrequently - it should be okay to introduce this small delay here
				System.GC.WaitForPendingFinalizers();
				System.GC.Collect();

				// Once the GameTalkServer deletes this file - it has swapped the files,
				// so we can start reading again
				while (File.Exists(mPath + mSyncFileForServerToDelete))
					System.Threading.Thread.Sleep(0);

				mTrafficPositionInFile = 0;
			}
		}

	}
}
