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

using EA.FileTalkMessages;

namespace EA.FileTalk
{
	/// <summary>
	/// Used to Send file data from the user's local file-system to the FileTalkServer
	/// </summary>
	public class SendFileTransaction : Transaction
	{
		private string mFilePath;
		private byte[] mByteArray;
		// Since the following are being compared with Array.Length uint makes more sense
		private uint mNumFragments = 0;
		private uint mFragmentSize = 0;
		private int mFileTalkThreadMillis = 5;

		/// <summary>
		/// Internal because only the FileTalkManager should be instantiating these
		/// </summary>
		/// <param name="filePath"></param>
		/// <param name="byteArray"></param>
		/// <param name="fileTalkCallback"></param>
		/// <param name="transactionUID"></param>
		/// <param name="gameTalkInstance"></param>
		/// <param name="fragmentSize"></param>
		/// <param name="fileTalkThreadMillis"></param>
		/// <param name="overwriteFile"></param>
		internal SendFileTransaction(FileTalkManager.MessageReceivedDelegate fileTalkCallback,
			uint transactionUID, EA.GameTalk.GameTalkInstance gameTalkInstance, 
			string filePath, byte[] byteArray, uint fragmentSize, int fileTalkThreadMillis, 
			bool overwriteFile)
			: base(fileTalkCallback, gameTalkInstance, transactionUID)
		{
			mFilePath = filePath;
			mByteArray = byteArray;
			mFragmentSize = fragmentSize;
			mNumFragments = (uint) mByteArray.Length / mFragmentSize;
			mFileTalkThreadMillis = fileTalkThreadMillis;
			// The base Transaction MUST register the user's callback before we request
			// the file from FileTalkServer
			SendFileRequest(filePath, transactionUID, gameTalkInstance, overwriteFile);
		}

		/// <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() { }

		/// <summary>
		/// Used to actually send the initial GameTalkMessage to the FileTalkServer
		/// </summary>
		/// <param name="filePath"></param>
		/// <param name="transactionUID"></param>
		/// <param name="gameTalkInstance"></param>
		/// <param name="overwriteFile"></param>
		private void SendFileRequest(string filePath, uint transactionUID,
			EA.GameTalk.GameTalkInstance gameTalkInstance, bool overwriteFile)
		{
			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.SendFileRequest.ToString(),
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#else
			gameTalkMessage.AddKeyContentAsBinary("",
				BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#endif
			gameTalkMessage.AddKeyContent(mGameTalkInstance.TargetName, mFilePath);
			gameTalkMessage.AddKeyContentAsBinary(mByteArray.Length.ToString(),
				BitConverter.GetBytes(mByteArray.Length));
			if (overwriteFile)
				gameTalkMessage.AddKeyContent("overwrite", "");
			else
				gameTalkMessage.AddKeyContent("append", "");

			mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);

			Thread sendFileDataThread = new Thread(new ThreadStart(SendFileData));
			sendFileDataThread.Name = "SendFileData";
			sendFileDataThread.Start();
		}

		/// <summary>
		/// Threaded method that will run "behind the user's back" to send all the data
		/// to the selected file
		/// </summary>
		private void SendFileData()
		{
			// Since byteArrayIndex is being compared with Array.Length uint makes more sense
			uint byteArrayIndex = 0;
			for (uint fragmentIndex = 0; fragmentIndex < mNumFragments;fragmentIndex++)
			{
				byte [] tempByteArray = new byte[mFragmentSize];
				Array.Copy(mByteArray, byteArrayIndex, tempByteArray, 0, tempByteArray.Length);
				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.SendFileRequest.ToString(),
					BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#else
				gameTalkMessage.AddKeyContentAsBinary("",
					BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#endif
				gameTalkMessage.AddKeyContent(mGameTalkInstance.TargetName, mFilePath);
				gameTalkMessage.AddKeyContentAsBinary(string.Empty, tempByteArray);
				
				mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);
				byteArrayIndex += mFragmentSize;
				System.Threading.Thread.Sleep(mFileTalkThreadMillis);
			}
			if (byteArrayIndex != mByteArray.Length)
			{
				uint remainderToSend = ((uint)mByteArray.Length) - byteArrayIndex;
				byte [] tempByteArray = new byte[remainderToSend];
				Array.Copy(mByteArray, byteArrayIndex, tempByteArray, 0, tempByteArray.Length);
				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.SendFileRequest.ToString(),
					BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#else
				gameTalkMessage.AddKeyContentAsBinary("",
					BitConverter.GetBytes((int)FileTalkMessage.MessageID.SendFileRequest));
#endif
				gameTalkMessage.AddKeyContent(mGameTalkInstance.TargetName, mFilePath);
				gameTalkMessage.AddKeyContentAsBinary(string.Empty, tempByteArray);

				mGameTalkInstance.Send(FileTalkMessage.sFileTalkServerID, gameTalkMessage);
				byteArrayIndex += remainderToSend;
			}
		}

		/// <summary>
		/// GameTalk callback registered with this particular transaction's unique 
		/// channel ID (at least unique to this target) - i.e. other targets could
		/// use a transaction registered with the same channel ID
		/// </summary>
		/// <param name="message"></param>
		public override void MessageReceived(EA.GameTalkMessages.GameTalkMessage message)
		{
			// All SendFileTransaction responses from FileTalkServer must have three parts
			Debug.Assert(message.NumberOfKeys == 3, "SendFileTransaction received message with invalid content");
			// If I get anything other than a SendFileResponse - then I don't understand it
			Debug.Assert(BitConverter.ToInt32(message.GetContentAsBinary(0), 0) == (int)FileTalkMessage.MessageID.SendFileResponse, "SendFileTransaction has received a non-SendFile GameTalkMessage");

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

			if (message.GetKey(2) == "Done")
				TransactionIsComplete();
		}
	}
}
