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

namespace EA.FileTalk
{
	/// <summary>
	/// Abstract class that is extended by all FileTalk transactions
	/// </summary>
	public abstract class Transaction
	{
		private FileTalkManager.MessageReceivedDelegate mFileTalkCallBack;
		private bool mIsTransactionComplete = false;

		/// <summary>
		/// Used by Transaction subclasses to send messages to the FileTalkServer 
		/// (through the GameTalkServer).
		/// </summary>
		protected EA.GameTalk.GameTalkInstance mGameTalkInstance;

		/// <summary>
		/// If an error occurred this will house the string message
		/// </summary>
		protected string mErrorMsg = string.Empty;

		/// <summary>
		/// If an error occurred this will house the error code (hash code of error message)
		/// </summary>
		protected int mErrorCode = 0;

		/// <summary>
		/// If an error occurred (in the processing of this transaction) this will be true
		/// </summary>
		protected bool mErrorOccurred = false;

		/// <summary>
		/// This uniquely identifies this transaction.  This channel ID is ID that it will
		/// use to send its messages and it is also the channel ID that the GameTalkInstance
		/// will use to find this Transaction in its container of transactions
		/// </summary>
		protected string mChannelId = null;

		/// <summary>
		/// Only subclasses of Transaction use this constructor (since this is an 
		/// abstract class)
		/// </summary>
		/// <param name="fileTalkCallback"></param>
		/// <param name="gameTalkInstance"></param>
		/// <param name="transactionUID"></param>
		protected Transaction(FileTalkManager.MessageReceivedDelegate fileTalkCallback,
			EA.GameTalk.GameTalkInstance gameTalkInstance, uint transactionUID)
		{
			mChannelId = GenerateChannelId(transactionUID);
			mGameTalkInstance = gameTalkInstance;
			mFileTalkCallBack = fileTalkCallback;
		}

		/// <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 abstract void Dispose ();

		/// <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 abstract void MessageReceived(EA.GameTalkMessages.GameTalkMessage message);

		/// <summary>
		/// User's of the FileTalk API can poll this method to determine whether this
		/// transaction has completed - or simply rely on the FileTalk callback to be
		/// immediately notified upon completion
		/// </summary>
		/// <returns></returns>
		public bool IsTransactionComplete()
		{
			return mIsTransactionComplete;
		}

		/// <summary>
		/// All subclasses can use this method to set their status to "complete"
		/// This will update the internal flag so that polling users will see the 
		/// transaction is complete and will also invoke their callback (assuming they
		/// provided one).
		/// </summary>
		protected void TransactionIsComplete()
		{
			mFileTalkCallBack.Invoke(this);

			// After we've populated this object fully - we're "complete"
			mIsTransactionComplete = true;
		}

		/// <summary>
		/// This is used to create a standard transaction name
		/// </summary>
		/// <param name="transactionNum"></param>
		/// <returns></returns>
		private string GenerateChannelId(uint transactionNum)
		{
			return EA.FileTalkMessages.FileTalkMessage.sFileTalkTransactionChannelPrefix + "." + transactionNum;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public string GetChannelId()
		{
			return mChannelId;
		}

		/// <summary>
		/// All transactions can potentially return/have an error - the format of the error code
		/// is a 32 bit int with the hashcode of the string that follows.
		/// </summary>
		/// <param name="errorValue"></param>
		protected void ProcessErrorCode(byte [] errorValue)
		{
			if (errorValue.Length == 0)
				return;
			
			mErrorOccurred = true;
			MemoryStream mStream = new MemoryStream(errorValue);
			BinaryReader binaryReader = new BinaryReader(mStream);

			mErrorCode = binaryReader.ReadInt32();
			// Calculation below is to get the entire string (minus the int that has already been 
			// read) and the null delimiter
			byte[] stringByteArray = binaryReader.ReadBytes(errorValue.Length - sizeof(int) + 1);
			mErrorMsg = System.Text.ASCIIEncoding.ASCII.GetString(stringByteArray);
		}
		
		/// <summary>
		/// All transactions can potentially return/have an error - this will return the error 
		/// message if there was one (null otherwise)
		/// </summary>
		/// <returns></returns>
		public string GetErrorMsg()
		{
			return mErrorMsg;
		}

		/// <summary>
		/// All transactions can potentially return/have an error - this will return the hashcode of the message if there was one (0 otherwise)
		/// </summary>
		/// <returns></returns>
		public int GetErrorCode()
		{
			return mErrorCode;
		}

		/// <summary>
		/// Indicates whether an error occurred in the processing of this transaction
		/// </summary>
		/// <returns></returns>
		public bool ErrorOccurred()
		{
			return mErrorOccurred;
		}
	}
}
