using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;

using EA.GameTalkMessages;
using EA.FileTalkMessages;
using EA.GameTalk;

namespace EA.FileTalk
{
	class FileNotificationResponse
	{
		/// <summary>
		/// This is a listing of FileSystemWatcher's that have been created using
		/// FileNotificationTransaction
		/// </summary>
		private Hashtable mFileSystemWatchers = new Hashtable();

		/// <summary>
		/// We queue the notification messages and send them when we receive an ack 
		/// from the client
		/// </summary>
		private Queue<NotificationData> mNotificationMessages = new Queue<NotificationData>();

		/// <summary>
		/// This flag dictates whether the FileNotificationResponse will send a notification message
		/// </summary>
		private bool mEnabled = false;

		/// <summary>
		/// We only need this for sending the GameTalkMessage
		/// </summary>
		private string mTarget = null;

		private ClientContext.AppendFileRootDelegate mAppendFileRootDelegate;

		/// <summary>
		/// Indication of the endian of the client that is connecting
		/// </summary>
		private bool mBigEndian = true;

		public bool BigEndian
		{
			set { mBigEndian = value; }
		}

		/// <summary>
		/// Initialize the FileNotificationResponse
		/// </summary>
		/// <param name="target"></param>
		public FileNotificationResponse(string target, ClientContext.AppendFileRootDelegate appendFileRootDelegate)
		{
			mTarget = target;
			mAppendFileRootDelegate = appendFileRootDelegate;
		}
		
		private void QueueNotificationData(uint eventHandlerID, System.IO.FileSystemEventArgs e,
			FileSystemWatcherMetaData fsmMetaData)
		{
			// There is a MS bug that results in updates getting triggered/fired twice
			if ((fsmMetaData.mDateTime != DateTime.MinValue) &&
				(DateTime.Now.Subtract(fsmMetaData.mDateTime).TotalMilliseconds < 100))
			{
				return;
			}

			fsmMetaData.mDateTime = DateTime.Now;

			// If you put a breakpoint anywhere below in this method you will cause change events
			// to be sent to the client that registered this listener - because of the 
			// aforementioned MS bug

			// If you find an existing notification on the same path and same changetype
			// don't add it
			foreach (NotificationData notifyData in mNotificationMessages)
			{
				if ((notifyData.mEventArgs.FullPath == e.FullPath) &&
					(notifyData.mEventArgs.ChangeType == e.ChangeType))
					return;
			}

			NotificationData notificationData = new NotificationData();
			notificationData.mEventArgs = e;
			notificationData.mEventHandlerId = eventHandlerID;
			notificationData.mFswMetaData = fsmMetaData;

			mNotificationMessages.Enqueue(notificationData);

			// In case the user enabled us prior to this new notification
			SendFileTransactionResponseFromQueue();
		}

		/// <summary>
		/// Method used by all the FileSystemWatched event handlers to send a 
		/// FileNotificationTransaction back to the client
		/// </summary>
		/// <param name="notificationData"></param>
		private void SendFileTransactionResponseFromQueue()
		{
			if (!mEnabled)
				return;

			// Queue will throw exception if you try to dequeue empty queue
			if (mNotificationMessages.Count == 0)
				return;

			// We remove the item here - even though this could be a notification about
			// the same file as the previous notification - the user may only be updating
			// a single file multiple successive times
			NotificationData notificationData = mNotificationMessages.Dequeue();

			GameTalkMessage messageResponse = new GameTalkMessage(notificationData.mFswMetaData.mChannelId);
#if(DEBUG)
			// The following sends a string of the specific transaction response being sent - shows up in GameTalkMonitor
			messageResponse.AddKeyContentAsBinary(FileTalkMessage.MessageID.FileNotificationResponse.ToString(),
				ClientContext.GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileNotificationResponse, mBigEndian));
#else
			messageResponse.AddKeyContentAsBinary("",
				ClientContext.GetEnumIndexBytes((int)FileTalkMessage.MessageID.FileNotificationResponse, mBigEndian));
#endif
			// We must always leave the content portion of the second key/value pair with the empty
			// string so that we can put an error code in there if necessary
			messageResponse.AddKeyContentAsBinary("", new byte[0]);
			messageResponse.AddKeyContentAsString(notificationData.mEventArgs.FullPath, "");
			string eventHandlerEnumIndex = notificationData.mEventHandlerId.ToString();
			messageResponse.AddKeyContentAsString(notificationData.mEventArgs.Name, eventHandlerEnumIndex);
			if (notificationData.mEventArgs is RenamedEventArgs)
			{
				RenamedEventArgs renamedEventArgs = (RenamedEventArgs)notificationData.mEventArgs;
				messageResponse.AddKeyContentAsString(renamedEventArgs.OldFullPath, renamedEventArgs.OldName);
			}
			GameTalkManager.Instance.Send(mTarget, messageResponse);

			mEnabled = false;
		}

		/// <summary>
		/// One of the four different types of file event handlers that can be created
		/// by FileTalk
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FileSystemWatcher_Changed(object sender, System.IO.FileSystemEventArgs e)
		{
			QueueNotificationData((uint)FileTalkMessage.EventHandler.Changed, e, (FileSystemWatcherMetaData)mFileSystemWatchers[sender]);
		}

		/// <summary>
		/// One of the four different types of file event handlers that can be created
		/// by FileTalk
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FileSystemWatcher_Created(object sender, System.IO.FileSystemEventArgs e)
		{
			QueueNotificationData((uint)FileTalkMessage.EventHandler.Created, e, (FileSystemWatcherMetaData)mFileSystemWatchers[sender]);
		}

		/// <summary>
		/// One of the four different types of file event handlers that can be created
		/// by FileTalk
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FileSystemWatcher_Deleted(object sender, System.IO.FileSystemEventArgs e)
		{
			QueueNotificationData((uint)FileTalkMessage.EventHandler.Deleted, e, (FileSystemWatcherMetaData)mFileSystemWatchers[sender]);
		}

		/// <summary>
		/// One of the four different types of file event handlers that can be created
		/// by FileTalk
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FileSystemWatcher_Renamed(object sender, System.IO.RenamedEventArgs e)
		{
			QueueNotificationData((uint)FileTalkMessage.EventHandler.Renamed, e, (FileSystemWatcherMetaData)mFileSystemWatchers[sender]);
		}

		/// <summary>
		/// This method is used to remove all listeners that have been registered for a
		/// particular client
		/// </summary>
		/// <param name="target"></param>
		internal void DeregisterListeners(string channelId)
		{
			ArrayList keysToRemove = new ArrayList();
			IDictionaryEnumerator dictionaryEnum = mFileSystemWatchers.GetEnumerator();
			while (dictionaryEnum.MoveNext())
			{
				DictionaryEntry dictionaryEntry = (DictionaryEntry)dictionaryEnum.Current;
				FileSystemWatcherMetaData fswMetaData = (FileSystemWatcherMetaData)dictionaryEntry.Value;
				// Don't remove the entry in this loop since you are iterating on the container in here
				if (channelId == null)
					keysToRemove.Add(dictionaryEntry.Key);
				else if (channelId == fswMetaData.mChannelId)
					keysToRemove.Add(dictionaryEntry.Key);
			}

			for (int i = 0; i < keysToRemove.Count; i++)
			{
				FileSystemWatcher fsw = (FileSystemWatcher)keysToRemove[i];
				fsw.EnableRaisingEvents = false;
				mFileSystemWatchers.Remove(keysToRemove[i]);
			}
		}

		internal void ClearNotificationQueue(string channelId)
		{
			if (channelId == null)
			{
				mNotificationMessages.Clear();
			}
			else
			{
				Queue<NotificationData> newNotificationMessages = new Queue<NotificationData>();
				foreach (NotificationData notifyData in mNotificationMessages)
				{
					if (notifyData.mFswMetaData.mChannelId != channelId)
					{
						newNotificationMessages.Enqueue(notifyData);
					}
				}
				mNotificationMessages = newNotificationMessages;
			}
		}

		/// <summary>
		/// FileTalkServer uses this to respond to FileNotification requests
		/// Basically creates the necessary FileTalkWatcher
		/// If people have lots of file events happening on their systems we may 
		/// want to expose a way to allow them to increase the default InternalBufferSize
		/// http://msdn2.microsoft.com/en-us/library/system.io.filesystemwatcher.internalbuffersize.aspx
		/// </summary>
		/// <param name="message"></param>
		internal void RespondToFileNotification(GameTalkMessage message)
		{
			// All file notification messages should have at least two key/content pairs
			if (message.NumberOfKeys < 2)
				return;

			if (message.GetContent(1).ToString() == "Deregister")
			{
				DeregisterListeners(message.ChannelId);
				ClearNotificationQueue(message.ChannelId);
				return;
			}
			else if (message.GetContent(1).ToString() == "Reenable")
			{
				mEnabled = true;
				SendFileTransactionResponseFromQueue();
				return;
			}

			// Other than the deregister message - all file notification transactions should
			// have three parts
			if (message.NumberOfKeys < 3)
				return;

			string filePath = message.GetKey(2);
			filePath = mAppendFileRootDelegate(filePath);
			string filter = message.GetContentAsString(2);

			MemoryStream mStream = new MemoryStream((byte[])message.GetContent(1));
			PlatformBinaryReader platformBinaryReader = new PlatformBinaryReader(mBigEndian, mStream);
			bool includeSubdirectories = (platformBinaryReader.ReadUint32() == 1) ? true : false;
			uint notifyFilterFlag = platformBinaryReader.ReadUint32();
			uint eventsFlag = platformBinaryReader.ReadUint32();
			platformBinaryReader.Close();

			System.IO.FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(filePath, filter);
			fileSystemWatcher.EnableRaisingEvents = true;
			//fileSystemWatcher.SynchronizingObject = this;
			fileSystemWatcher.IncludeSubdirectories = includeSubdirectories;

			if (((uint)FileTalkMessage.NotifyFilterFlags.Attributes & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.Attributes;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.CreationTime & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.CreationTime;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.DirectoryName & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.DirectoryName;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.FileName & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.FileName;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.LastAccess & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.LastAccess;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.LastWrite & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.LastWrite;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.Security & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.Security;
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.NotifyFilterFlags.Size & notifyFilterFlag) != 0)
			{
				fileSystemWatcher.NotifyFilter |= NotifyFilters.Size;
			}

			// Don't change this to "else if"
			if (((uint)FileTalkMessage.EventHandler.Changed & eventsFlag) != 0)
			{
				fileSystemWatcher.Changed += new System.IO.FileSystemEventHandler(this.FileSystemWatcher_Changed);
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.EventHandler.Created & eventsFlag) != 0)
			{
				fileSystemWatcher.Created += new System.IO.FileSystemEventHandler(this.FileSystemWatcher_Created);
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.EventHandler.Deleted & eventsFlag) != 0)
			{
				fileSystemWatcher.Deleted += new System.IO.FileSystemEventHandler(this.FileSystemWatcher_Deleted);
			}
			// Don't change this to "else if"
			if (((uint)FileTalkMessage.EventHandler.Renamed & eventsFlag) != 0)
			{
				fileSystemWatcher.Renamed += new System.IO.RenamedEventHandler(this.FileSystemWatcher_Renamed);
			}

			FileSystemWatcherMetaData fsmMetaData = new FileSystemWatcherMetaData();
			fsmMetaData.mChannelId = message.ChannelId;
			fsmMetaData.mDateTime = DateTime.MinValue;
			mFileSystemWatchers.Add(fileSystemWatcher, fsmMetaData);

			// When we initially create the listener/watcher - the user will receive notifications 
			// without having to explicitly reenable us - however all subsequent notifications
			// will require the user to explicitly reenable us
			mEnabled = true;
		}

		internal class NotificationData
		{
			public FileSystemWatcherMetaData mFswMetaData = null;
			public System.IO.FileSystemEventArgs mEventArgs = null;
			public uint mEventHandlerId = 0;
		}

		internal class FileSystemWatcherMetaData
		{
			public string mChannelId = null;
			public DateTime mDateTime = DateTime.MinValue;
		}
	}
}
