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

using EA.FileTalkMessages;

namespace EA.FileTalk
{
	/// <summary>
	/// This class is used to tell the FileTalkServer to set up file notifications which 
	/// includes anything from a file's security being updated to a folder being renamed
	/// </summary>
	public class FileNotificationTransaction : Transaction
	{
		private string mFilePath = null;
		private string mName = null;
		private string mOldFilePath = null;
		private string mOldName = null;
		private FileTalkMessage.EventHandler mEventHandler = FileTalkMessage.EventHandler.INVALID;

		internal FileNotificationTransaction(FileTalkManager.MessageReceivedDelegate fileTalkCallback,
			uint transactionUID, EA.GameTalk.GameTalkInstance gameTalkInstance, string filepath, 
			string filter, uint notifyFilterFlag, uint eventsFlag, bool includeSubdirectories):
			base(fileTalkCallback, gameTalkInstance, transactionUID)
		{
			MemoryStream mStream = new MemoryStream();
			PlatformBinaryWriter platformBinaryWriter = new PlatformBinaryWriter(false, mStream);
			uint includeSubdirectoriesInt = 0;
			if (includeSubdirectories)
				includeSubdirectoriesInt = 1;
			platformBinaryWriter.Write(includeSubdirectoriesInt);
			platformBinaryWriter.Write(notifyFilterFlag);
			platformBinaryWriter.Write(eventsFlag);
			long pos = mStream.Position;
			mStream.Seek(0, System.IO.SeekOrigin.Begin);
			byte[] byteArray = new byte[pos];
			mStream.Read(byteArray, 0, (int)pos);
			// Is this next commented out line needed if we are just throwing the stream away?
			//mStream.Seek(pos, System.IO.SeekOrigin.Begin);

			EA.GameTalkMessages.GameTalkMessage gameTalkMessage = new EA.GameTalkMessages.GameTalkMessage(mChannelId);
#if(DEBUG)
			// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
			gameTalkMessage.AddKeyContentAsBinary(
				FileTalkMessage.MessageID.FileNotificationRequest.ToString(),
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#else
			gameTalkMessage.AddKeyContentAsBinary("",
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#endif
			gameTalkMessage.AddKeyContent(mGameTalkInstance.TargetName, byteArray, 
				EA.GameTalkMessages.GameTalkMessage.Format.Binary);
			gameTalkMessage.AddKeyContent(filepath, filter);

			mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// We only need this so that when this transaction gets cleared from memory 
		/// all of its associated notifications get cleared from the FileTalkServer
		/// </summary>
		private void DeregisterListener()
		{
			if ((mGameTalkInstance == null) || (!mGameTalkInstance.IsConnected))
				return;

			// deregister the listener associated with this transaction
			EA.GameTalkMessages.GameTalkMessage gameTalkMessage = new EA.GameTalkMessages.GameTalkMessage(mChannelId);
#if(DEBUG)
			// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
			gameTalkMessage.AddKeyContentAsBinary(
				FileTalkMessage.MessageID.FileNotificationRequest.ToString(),
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#else
			gameTalkMessage.AddKeyContentAsBinary("",
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#endif
			// Not sure whether we want to store all FileTalk requests strings as static const members of Transaction
			gameTalkMessage.AddKeyContentAsString(mGameTalkInstance.TargetName, "Deregister");
			mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);
		}

		/// <summary>
		/// We need a way to tell transactions to release resources - somewhat like a delete in C++
		/// However, we can't rely on using a destructor - since we need to be able to control
		/// when this release/freeing occurs.  Currently the only benefit to this is to get
		/// FileNotificationTransaction to tell the FileTalkServer to release its listener(s).
		/// In actual fact - the FileTalkServer will receive a message when this client
		/// disconnects - at which point it will clear all listeners for this client anyway.  This
		/// just gives us more granular control.
		/// </summary>
		public override void Dispose() 
		{
			DeregisterListener();
		}

		/// <summary>
		/// Each time the FileNotificationTransaction receives a new message - it must
		/// clear the values in all of its members to ensure no stale data will remain
		/// in members that don't get updated
		/// </summary>
		private void ClearCurrentMembers()
		{
			mFilePath = null;
			mName = null;
			mOldFilePath = null;
			mOldName = null;
			mEventHandler = FileTalkMessage.EventHandler.INVALID;
		}

		/// <summary>
		/// FileNotificationTransaction's GameTalk callback
		/// </summary>
		/// <param name="message"></param>
		public override void MessageReceived(EA.GameTalkMessages.GameTalkMessage message)
		{
			// All GetFileTransaction responses from FileTalkServer must have at least three parts
			Debug.Assert(message.NumberOfKeys > 3, "FileNotificationTransaction received message with invalid content");
			// If I get anything other than a file notification response - then I don't understand it
			Debug.Assert(BitConverter.ToInt32(message.GetContentAsBinary(0), 0) == (int)FileTalkMessage.MessageID.FileNotificationResponse, "FileNotificationTransaction has received a non-FileNotification GameTalkMessage");

			ClearCurrentMembers();

			ProcessErrorCode(message.GetContentAsBinary(1));
			if (mErrorOccurred)
			{
				TransactionIsComplete();
				return;
			}

			mFilePath = message.GetKey(2);
			mName = message.GetKey(3);
			mEventHandler = (FileTalkMessage.EventHandler)Convert.ToUInt32(message.GetContent(3));

			if (message.NumberOfKeys == 5)
			{
				mOldFilePath = message.GetContentAsString(4);
				mOldName = message.GetKey(4);
			}

			TransactionIsComplete();
		}

		/// <summary>
		/// Simple accessor for the filepath that has been affected by the current file notification
		/// </summary>
		/// <returns></returns>
		public string GetFilePath ()
		{
			return mFilePath;
		}

		/// <summary>
		/// Simple accessor for the name that has been affected by the current file notification
		/// </summary>
		/// <returns></returns>
		public string GetName ()
		{
			return mName;
		}

		/// <summary>
		/// Simple accessor for the previous/old filepath that has been affected by the current file notification
		/// </summary>
		/// <returns></returns>
		public string GetOldFilePath()
		{
			return mOldFilePath;
		}

		/// <summary>
		/// Simple accessor for the previous/old name that has been affected by the current file notification
		/// </summary>
		/// <returns></returns>
		public string GetOldName()
		{
			return mOldName;
		}

		/// <summary>
		/// The event that caused this notification to be raised
		/// </summary>
		/// <returns></returns>
		public FileTalkMessage.EventHandler GetEventHandler()
		{
			return mEventHandler;
		}

		/// <summary>
		/// Used by user to enable receipt of subsequent notifications using the same
		/// instance of a FileNotificationTransaction
		/// </summary>
		public void Reenable()
		{
			EA.GameTalkMessages.GameTalkMessage gameTalkMessage = new EA.GameTalkMessages.GameTalkMessage(mChannelId);
#if(DEBUG)
			// The following sends a string of the specific transaction request being made - shows up in GameTalkMonitor
			gameTalkMessage.AddKeyContentAsBinary(
				FileTalkMessage.MessageID.FileNotificationRequest.ToString(),
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#else
			gameTalkMessage.AddKeyContentAsBinary("",
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.FileNotificationRequest));
#endif
			// Not sure whether we want to store all FileTalk requests strings as static const members of Transaction
			gameTalkMessage.AddKeyContentAsString(mGameTalkInstance.TargetName, "Reenable");
			mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);
		}
	}
}
