package cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.transactionhandler;

import java.io.IOException;
import java.util.Locale;

import cdac.medinfo.sdk.hl7282.baselibs.messages.AbstractQuery;
import cdac.medinfo.sdk.hl7282.baselibs.messages.Message;
import cdac.medinfo.sdk.hl7282.baselibs.segments.BHS;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSH;
import cdac.medinfo.sdk.hl7282.baselibs.segments.RCP;
import cdac.medinfo.sdk.hl7282.baselibs.segments.Segment;
import cdac.medinfo.sdk.hl7282.commoninterface.IDataType;
import cdac.medinfo.sdk.hl7282.commoninterface.IHL7Stream;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegment;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidMessageException;
import cdac.medinfo.sdk.hl7282.exceptions.ParseException;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7Batch;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7BatchFile;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ITransactionHandler;
import cdac.medinfo.sdk.hl7282.hl7net.commons.InvalidTransactionException;
import cdac.medinfo.sdk.hl7282.hl7net.commons.StandardUtilityMethods;
import cdac.medinfo.sdk.hl7282.hl7streams.HL7BufferStream;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageCode;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageStructure;
import cdac.medinfo.sdk.hl7282.utils.EnumSegments;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;

/**
 * TransactionHandler validates the message on client side.
 * It validates message according to its MessageControllerID and Query with QueryID.
 */
public class TransactionHandler implements ITransactionHandler
{

	//This collection stores the whole information about Message.
	private ISingleCollection<MessageData> objMessageDataCollection = null;

	//This stores the collection of MessageControlID.
	private ISingleCollection<String> objMesasgectrlIDCollection = null;

	/**
	 * Instantiates ClientSideTransactionHandler
	 */
	public TransactionHandler()
	{
		objMessageDataCollection = new SingleCollection<MessageData>();
		objMesasgectrlIDCollection = new SingleCollection<String>();
	}

	/**
	 *This method initiates one operation. An operation is completed when,
	 * 1]When sending Immediate or Deffered Query.
	 * 2]When sending event.
	 * 3]When sending ACK for Deferred Query.
	 * 4]When Accepting Response for Deffered or Immediate response.
	 * This method will keep Record for sending and receiving messsages.
	 * @param objMessage Message for which to perform validation.
	 * @throws InvalidTransactionException Thrown when message validation fails.
	 */
	public void processMessageTransaction(Message objMessage)	throws InvalidTransactionException 
	{
		try
		{	
			//This will check for Same Message Control id if present then throw exception or log.
			if(!objMesasgectrlIDCollection.contains((String)objMessage.getHeader().getMessageControlID().getValue()))
			{
				if(objMessage.isQuery())
				{
					objMesasgectrlIDCollection.add((String)objMessage.getHeader().getMessageControlID().getValue());
					AbstractQuery queryMessageToSend = ((AbstractQuery)objMessage);
					String strQueryIdentifier = null;
					if(queryMessageToSend.getQueryIdentifier()!=null)
						strQueryIdentifier = (String)queryMessageToSend.getQueryIdentifier().getValue();

					//If query is called previously then throw exception
					if(!isPreviousCalled(strQueryIdentifier,queryMessageToSend))//It will check for Query id 
					{
						if(strQueryIdentifier != null)
							addMessageData(objMessage,null);
						else
						{
								throw new InvalidMessageException(ResourceBundleWrapper.getValue("exceptions", "QueryIdentifier",new Locale(HL7Config.createInstance().getLocale())));
						}

					}
					else
					{	
						
							throw new InvalidMessageException(ResourceBundleWrapper.getValue("exceptions", "QC_SameQuery",new Locale(HL7Config.createInstance().getLocale()))+strQueryIdentifier);
					}

					if(HL7Config.createInstance().isLoggingOn())
					{
						String strSendingMessage = ResourceBundleWrapper.getValue("exceptions", "Trans_SendingMessage",new Locale(HL7Config.createInstance().getLocale()))+objMessage.getHeader().getMessageControlID().getValue();
						HL7Logger objHLogger = HL7Logger.getLogger();
						objHLogger.log(strSendingMessage);
					}
				}
				else if(objMessage.isResponse())
				{
					objMesasgectrlIDCollection.add((String)objMessage.getHeader().getMessageControlID().getValue());

					String strReceivedQueryIdentifier = null; 
					AbstractQuery objAbstractQuery = (AbstractQuery)objMessage;
					if(objAbstractQuery.getQueryIdentifier()!=null)
						strReceivedQueryIdentifier = (String)objAbstractQuery.getQueryIdentifier().getValue();

					String strReceivedMessageControllerID = (String)objMessage.getHeader().getMessageControlID().getValue();
					if(strReceivedQueryIdentifier!=null)
					{
						if(isQueryIdentifierPresent(strReceivedQueryIdentifier))
						{
							EnumMessageCode enumMessageCodeSearched = getMessageCodeByQueryIdentifier(strReceivedQueryIdentifier);
							String strTriggerEvent = getTriggerEventByQueryIdentifier(strReceivedQueryIdentifier);
							EnumMessageStructure enumExpectedMessageStructure = StandardMethods.getExpectedMessageStructure(enumMessageCodeSearched.getValue(),strTriggerEvent);
							String strReceivedMessageStructure = objMessage.getMessageStructure();
							if(enumExpectedMessageStructure.getValue().equals(strReceivedMessageStructure) == false)
							{
									throw new InvalidMessageException(objMessage.getClass().getSimpleName() + ResourceBundleWrapper.getValue("exceptions", "UnexpectedMessage", new Locale(HL7Config.createInstance().getLocale())) + objMessage.getClass().getSimpleName());
							}

							//Remove the MessageData for Query and if Response is Deferred then create MessaData for it.
							removeByMessageControllerID(objMessage.getMessageControlID());
						}
					}
					else//raised when query identifier is absent
					{
							throw new InvalidMessageException(ResourceBundleWrapper.getValue("exceptions", "QueryIdentifier", new Locale(HL7Config.createInstance().getLocale())) + objMessage.getClass().getSimpleName());
					}

					if(HL7Config.createInstance().isLoggingOn())
					{
						String strRecievedMessage = ResourceBundleWrapper.getValue("exceptions", "Trans_RecievedMessage",new Locale(HL7Config.createInstance().getLocale()))+strReceivedMessageControllerID;
						HL7Logger objHLogger = HL7Logger.getLogger();
						objHLogger.log(strRecievedMessage);
					}

				}
				else if(objMessage.getMessageStructure().equals(EnumMessageStructure.ACK.getValue()))
				{
					objMesasgectrlIDCollection.add((String)objMessage.getHeader().getMessageControlID().getValue());

					String	strReceivedMessageControllerID = objMessage.getMessageControlID();
					String strReceivedTriggerEvent = objMessage.getTriggerEvent();
					//Check with received MesasgeControlID is any response or query exists in messageDataCollection.
					//If not then should through exception.
					//Check for unexpected ACK
					String strTriggerEventSearched = getTriggerEventByMessageControllerID(strReceivedMessageControllerID);
					if(strTriggerEventSearched!=null)
					{
						if(strReceivedTriggerEvent.equals(strTriggerEventSearched) == false)
						{
								throw new InvalidMessageException(objMessage.getClass().getSimpleName() + ResourceBundleWrapper.getValue("exceptions", "UnexpectedMessage", new Locale(HL7Config.createInstance().getLocale())) + objMessage.getClass().getSimpleName());
						}
					}
					removeByMessageControllerID(strReceivedMessageControllerID);//removes data with received messageControlID by ACK.

					//If received message is verified successful then log message with messageControlID.  
					if(HL7Config.createInstance().isLoggingOn())
					{
						String strRecievedMessage = ResourceBundleWrapper.getValue("exceptions", "Trans_RecievedMessage",new Locale(HL7Config.createInstance().getLocale()))+strReceivedMessageControllerID;
						HL7Logger objHLogger = HL7Logger.getLogger();
						objHLogger.log(strRecievedMessage);
					}		
				}//For event if validation mode is low then just return do not keep data for the event.
				else 
				{
					objMesasgectrlIDCollection.add((String)objMessage.getHeader().getMessageControlID().getValue());
					addMessageData(objMessage,null);
					if(HL7Config.createInstance().isLoggingOn())
					{
						String strSendingMessage = ResourceBundleWrapper.getValue("exceptions", "Trans_SendingMessage",new Locale(HL7Config.createInstance().getLocale()))+objMessage.getHeader().getMessageControlID().getValue();
						HL7Logger objHLogger = HL7Logger.getLogger();
						objHLogger.log(strSendingMessage);
					}
				}
			}
			else
			{
					throw new InvalidMessageException(ResourceBundleWrapper.getValue("exceptions", "QC_SameMessageControlID",new Locale(HL7Config.createInstance().getLocale()))+objMessage.getHeader().getMessageControlID().getValue());
			}	
		}
		catch(InvalidMessageException ime)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(ime);
			}
			throw new InvalidTransactionException(ResourceBundleWrapper.getValue("exceptions", "UnexpectedMessage", new Locale(HL7Config.createInstance().getLocale())) + objMessage.getClass().getSimpleName());
		}
	}

	/**
	 * Processes the batch and verified each message in it. 
	 * @param objHL7Batch - Batch for which to perform verification.
	 * @throws InvalidTransactionException Thrown when batch transaction fails.
	 */
	private void processBatchTransaction(IHL7Batch objHL7Batch)	throws InvalidTransactionException 
	{
		//If batch is received with this batch control id then this is sending batch.
		//so remove the message for this from message data.
		if(isBatchRecieved(objHL7Batch.getBatchControlID()))
		{
			ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
			for(Message objMessage : objMessageCollection)
			{
				processMessageTransaction(objMessage);
			}
		}
		else
		{
			ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
			for(Message objMessage : objMessageCollection)
			{
				processMessageTransaction(objMessage);
				processMessageData(objMessage,objHL7Batch.getBatchControlID());
			}
		}
	}

	/**
	 * Processes the batchfile and verified each batch in it. 
	 * @param objHL7BatchFile - BatchFile for which to perform verification.
	 * @throws InvalidTransactionException Thrown when batch transaction fails.
	 */
	public void processBatchTransaction(IHL7BatchFile objHL7BatchFile)	throws InvalidTransactionException 
	{
		ISingleCollection<IHL7Batch> objHL7BatchCollection=objHL7BatchFile.getHL7BatchCollection();
		for(IHL7Batch objHL7Batch :objHL7BatchCollection)
		{
			processBatchTransaction(objHL7Batch);
		}
	}

	/**
	 * This method takes byte buffer in retrieves the batch control id from it and checks that 
	 * any data related to this batch or message is exist then remove it.
	 * @param e- Exception Occoured. 
	 * @param objBytebuffer - error byte buffer.  
	 */
	public void processOnExceptionTransaction(Exception e, byte[] objBytebuffer)
	{
		String strMessageControlID =null;
		IHL7Stream objHL7BufferStream = new HL7BufferStream();
		StandardUtilityMethods.createReplicateStream(objHL7BufferStream, objBytebuffer);
		Segment objSegment;
		try 
		{
			objSegment = StandardUtilityMethods.getMessageBatchHeader(objHL7BufferStream);

			if(objSegment!=null)
			{
				String strSegment= objSegment.getName().getValue();
				if (strSegment.equals(EnumSegments.HL7_SEG_BHS.getValue()))
				{
					BHS objBHS = (BHS)objSegment;
					strMessageControlID= (String)objBHS.getBatchControlID().getValue();
					removeByBatchId(strMessageControlID);
				}
				else if (strSegment.equals(EnumSegments.HL7_SEG_MSH.getValue()))
				{
					MSH objMSH = (MSH)objSegment;
					strMessageControlID =(String)objMSH.getMessageControlID().getValue();
					removeByMessageControllerID(strMessageControlID);
				}
			}
		} 
		catch (ParseException pe)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(pe);
			}

		} 
		catch (IOException ie) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(ie);
			}
		}

	}

	//This returns the boolean status for the batch received whether 
	//batch with this batch control id is received or not.
	//if found then true else false.
	private boolean isBatchRecieved(String strBatchControlID)
	{
		for (MessageData objMessagaData: objMessageDataCollection)
		{
			String strBatchControlIDData = objMessagaData.getBatchControlID();
			if(strBatchControlIDData!=null)
			{
				if(strBatchControlIDData.equals(strBatchControlID))
					return true;
			}
		}
		return false;
	}

	//This method checks if Query with similar Query Id is already been called.
	//if it is then throw exception of same query can not be called.
	//This check will not be applicable for QCN^J01 and interactive continuation query.
	//QCN^J01 contains query id of previously called query.
	//Interactive continuation query contains the same query id.
	//This check is not applicable only for LOW Validation mode. 
	private boolean isPreviousCalled(String strQueryIdentifier,AbstractQuery queryMessageToSend)
	{
		ISegment objRCPSegment = null;
		if (!(queryMessageToSend.getMessageCode().equals(EnumMessageCode.QCN.getValue())))
		{
			ISingleCollection<ISegment> objSegmentCollection = queryMessageToSend.getSegment(EnumSegments.HL7_SEG_RCP);

			if(objSegmentCollection != null)
				objRCPSegment = objSegmentCollection.elementAt(0);
			IDataType quantityLimitedRequest = null;

			if(objRCPSegment != null)
				quantityLimitedRequest = ((RCP)objRCPSegment).getQuantityLimitedRequest();

			if(quantityLimitedRequest == null)
			{
				for(MessageData objMessageData :objMessageDataCollection)
				{
					if(strQueryIdentifier!=null)
					{
						if(strQueryIdentifier.equals(objMessageData.getQueryIdentifier()))
							return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Adds Message Data.
	 * @param messageToSend - Message to send
	 * @param strBatchControlID - Batcg control id
	 */
	private  void addMessageData(Message messageToSend,String strBatchControlID)
	{
		String strQueryIdentifier = null;
		boolean bDeferred = false;
		boolean bBatchExpected = false;
		MessageData objMessageData = new MessageData(messageToSend);
		if(messageToSend.isQuery() || messageToSend.isResponse())
		{
			AbstractQuery queryMessageToSend = ((AbstractQuery)messageToSend);
			strQueryIdentifier = null;
			if(queryMessageToSend.getQueryIdentifier()!=null)
				strQueryIdentifier = (String)queryMessageToSend.getQueryIdentifier().getValue();

			bDeferred = queryMessageToSend.isDeferred();
			bBatchExpected = queryMessageToSend.isBatchRSPExpected();
		}
		objMessageData.setQueryIdentifier(strQueryIdentifier);
		objMessageData.setDeferred(bDeferred);
		objMessageData.setBatchControlID(strBatchControlID);
		objMessageData.setBatchExpected(bBatchExpected);
		objMessageDataCollection.add(objMessageData);
	}

	//////////////////Utility methods

	/**
	 * Check for Presence MessageControllerID.
	 * @param strMessageControllerID - String of Message ControllerID
	 * @return boolean - true, if Message Controller ID is present.
	 */
	public boolean isMessageControllerIDPresent(String strMessageControllerID)
	{
		for (MessageData messageData : objMessageDataCollection) 
		{
			if(strMessageControllerID.equals(messageData.getMessageControllerID()))
				return true;
		}
		return false;
	}

	/**
	 * Removes MessageData depending upon MessageControllerID.
	 * @param strMessageControllerID 
	 */
	public void removeByMessageControllerID(String strMessageControllerID)
	{
		for (MessageData  messageData : objMessageDataCollection) 
		{
			if(strMessageControllerID.equals(messageData.getMessageControllerID()))
			{
				objMessageDataCollection.remove(messageData);
				break;
			}
		}
	}

	/**
	 * Check for Presence QueryIdentifier.
	 * @param strQueryIdentifier specified Query IDenfitifer
	 * @return boolean - true , if Query Identifier is present.
	 */
	public boolean isQueryIdentifierPresent(String strQueryIdentifier)
	{
		if(strQueryIdentifier!=null)
		{
			for (MessageData messageData : objMessageDataCollection) 
			{
				if(strQueryIdentifier.equals(messageData.getQueryIdentifier()))
					return true;
			}
		}
		return false;
	}

	/**
	 * Retrieves priority of query for QueryIdentifier.if Query identifier is not present returns false.
	 * @param strQueryIdentifier specified Query Identifier.
	 * @return boolean - false if priority with this query id is not set else return the status.
	 */
	public boolean isDeferredByQueryIdentifier(String strQueryIdentifier)
	{
		boolean deferred = false;
		if(strQueryIdentifier!=null)
		{
			for (MessageData  messageData : objMessageDataCollection) 
			{
				if(strQueryIdentifier.equals(messageData.getQueryIdentifier()))
				{
					deferred = messageData.getDeferred();
					break;
				}
			}
		}
		return deferred;
	}

	/**
	 * Retrieves Query Identifier for Message ControllerID
	 * @param strMessageControllerID specified Message Controller ID
	 * @return String - String of Query Identifier
	 */
	public String getQueryIdentifierByMessageControllerID(String strMessageControllerID)
	{
		String strQueryIdentifier = null;
		for (MessageData  messageData : objMessageDataCollection) 
		{
			if(strMessageControllerID.equals(messageData.getMessageControllerID()))
			{
				strQueryIdentifier = messageData.getQueryIdentifier();		
				break;
			}
		}
		return strQueryIdentifier;
	}

	/**
	 * Retrieves Trigger Event ID for Message ControllerID.
	 * @param strMessageControllerID specified Message Controller ID
	 * @return String - Trigger Event ID for Message ControllerID.
	 */
	public String getTriggerEventByMessageControllerID(String strMessageControllerID)
	{
		String strTriggerEvent = null;
		for (MessageData  messageData : objMessageDataCollection) 
		{
			if(strMessageControllerID.equals(messageData.getMessageControllerID()))
			{
				strTriggerEvent = messageData.getTriggerEvent();				
				break;
			}
		}
		return strTriggerEvent;
	}
	/**
	 * Retrieves Trigger Event for Query Identifier.
	 * @param strQueryIdentifier specified Query Identifier
	 * @return String - Trigger Event for Query Identifier.
	 */
	public final String getTriggerEventByQueryIdentifier(String strQueryIdentifier)
	{
		String strTriggerEvent = null;
		for (MessageData  messageData : objMessageDataCollection) 
		{
			if(strQueryIdentifier.equals(messageData.getQueryIdentifier()))
			{
				strTriggerEvent = messageData.getTriggerEvent();				
				break;
			}
		}
		return strTriggerEvent;
	}

	/**
	 * This method returns the messageCode for message by Queryidentifier. 
	 * @param strQueryIdentifier received query identifier. 
	 * @return enumMessageCode MessageCode for query identifier. 
	 */
	public EnumMessageCode getMessageCodeByQueryIdentifier(String strQueryIdentifier)
	{
		EnumMessageCode enumMessageCode = null;
		for(MessageData messageData : objMessageDataCollection)
		{
			if(strQueryIdentifier.equals(messageData.getQueryIdentifier()))
			{
				enumMessageCode = messageData.getEnumMessageCode();
				break;
			}
		}
		return enumMessageCode;
	}

	/**
	 * Checks whether Query Priority is Deferred or Immediate.
	 * @param message Message, which Query Priority to be checked.
	 * @return boolean - true, if Query Priority is Deferred either false. 
	 */
	public boolean isDeferred(Message message)
	{
		AbstractQuery queryMessageToSend = ((AbstractQuery)message);
		String strQueryIdentifier = null;
		if(queryMessageToSend.getQueryIdentifier()!=null)
		{
			strQueryIdentifier = (String)queryMessageToSend.getQueryIdentifier().getValue();
			for(MessageData messageData : objMessageDataCollection)
			{
				if(strQueryIdentifier.equals(messageData.getQueryIdentifier()))
				{
					return messageData.getDeferred();
				}
			}
		}
		return false;
	}

	/**
	 * Removes MessageData depending upon QueryIdentifier.
	 * @param strQueryIdentifier specified Query IDenfitifer
	 */
	private void removeByBatchId(String strBatchID)
	{
		for (MessageData  messageData : objMessageDataCollection) 
		{
			if(strBatchID.equals(messageData.getBatchControlID()))
			{
				objMessageDataCollection.remove(messageData);
				break;
			}
		}
	}

	//This method adds the batch control id to each messageData object for 
	//which processing is already done. 
	private void processMessageData(Message objMessage,String strBatchControlID)
	{
		for(MessageData objMessageData :objMessageDataCollection)
		{
			if(objMessageData.getMessageControllerID().equals(objMessage.getHeader().getMessageControlID().getValue()))
				objMessageData.setBatchControlID(strBatchControlID);
		}
	}
	/**
	 * Closes the Client Side ClientSideTransactionHandler
	 */
	public void close()
	{		
		objMessageDataCollection = null;
	}
	/**
	 * This set the batch agent on transaction handler.
	 * @param objBatchProcessor - object of batch agent.
	 */
	public void setBatchAgent(IBatchProcessor objBatchProcessor) 
	{
	}
}
