package cdac.medinfo.sdk.hl7282.hl7net.communication;

import java.io.IOException;

import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBufferSender;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IDataNotificationListener;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IDataProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7Batch;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7BatchFile;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IPublishAgent;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ISequenceAgent;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IServerInteractiveContinuation;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IServerQueryCancellation;
import cdac.medinfo.sdk.hl7282.baselibs.messages.AbstractQuery;
import cdac.medinfo.sdk.hl7282.baselibs.messages.Message;
import cdac.medinfo.sdk.hl7282.baselibs.messages.MessageFactory;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSH;
import cdac.medinfo.sdk.hl7282.baselibs.segments.Segment;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegment;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegmentSet;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.BatchException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidMessageException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidSequenceException;
import cdac.medinfo.sdk.hl7282.exceptions.MessageNotSupportedException;
import cdac.medinfo.sdk.hl7282.exceptions.ParseException;
import cdac.medinfo.sdk.hl7282.exceptions.ProcessFailedException;
import cdac.medinfo.sdk.hl7282.utils.EnumErrorCode;
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.EnumTriggerEvent;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;


/**
 * Server Side Message Agent validates received message according to Message Controller ID & other constants.
 * It sends  outgoing messages Messages to Auxiliary protocol Agent.   
 */
public class ServerSideMessageAgent implements IDataProcessor   
{
	

	/*Message Object */
	private Message objReceivedMessage = null;

	IDataNotificationListener objIDataNotificationListener = null;  
	IBufferSender objIBufferSender = null;

	private MessageFactory objMessageFactory = null;

	/*The Query Cancellation Agent*/
	IServerQueryCancellation objServerSideQCNAgent = null;

	/**The Interactive Continuation protocol.**/
	IServerInteractiveContinuation objServerSideQICAgent = null;

	/**The Publish Processor */
	private IPublishAgent objPublishAgent = null;

	/* The Sequence Number agent. */
	private ISequenceAgent objSequenceAgent;

	private IBatchProcessor objIBatchProcessor;

	/**
	 * Constructs Server Side Message Agent
	 */
	public ServerSideMessageAgent() 
	{
	}


	/**
	 * Retrieves the Batch Processor
	 * @return the objIBatchProcessor
	 */
	public IBatchProcessor getBatchProcessor() 
	{
		return objIBatchProcessor;
	}

	/**
	 * Sets the Batch Processor
	 * @param objIBatchProcessor the objIBatchProcessor to set
	 */
	public void setBatchProcessor(IBatchProcessor objIBatchProcessor) 
	{
		this.objIBatchProcessor = objIBatchProcessor;
	}

	/**
	 * Adds DataProcessors to Receive SegmentSet.
	 * @param objSegmentSet SegmentSet that is received as Data.
	 * @throws IOException Thrown when IO operation fails.
	 */
	public void onDataReceived(ISegmentSet objSegmentSet ) throws IOException
	{
			ISegment objSegment = objSegmentSet.getSegment(0);

			if(EnumSegments.HL7_SEG_MSH == objSegment.getName())
			{
				Message objReceivedMessage = getProcessedMessage(objSegmentSet);

				//Check for Sequence Number Protocol
				if(objSequenceAgent != null)
					objSequenceAgent.processReceive(objSegmentSet);

				if(objServerSideQICAgent!=null)
				{
					if(objReceivedMessage.isQuery())
					{
						if(objServerSideQICAgent.processReceive(objSegmentSet))
						{
							AbstractQuery objAbstractQuery = (AbstractQuery)objReceivedMessage;
							objAbstractQuery.setIsInteractiveQuery(true);
						}
					}

				}
				//If Query Cancellation agent is set then process the query by storing Query Id for QCN_J01.
				if(objServerSideQCNAgent!=null)
				{
					if(objReceivedMessage.isQuery())
					{
						AbstractQuery objAbstractQuery =(AbstractQuery)objReceivedMessage;
						if(objServerSideQCNAgent.processReceive(objReceivedMessage.getSegmentSet()))
							objAbstractQuery.setIsCanceledQuery(true);
					}
				}
				// IPublishAgent agent is set then check that this Message is subscription message or not.
				// Set Subscription type for Message.
				if(objPublishAgent!=null )
				{
					objReceivedMessage.setSubscriptionType(objPublishAgent.getSubscriptionType(objReceivedMessage.getSegmentSet()));
				}
				objIDataNotificationListener.notifyData(objReceivedMessage,EnumErrorCode.MESSAGE_ACCEPTED);
			}
			else
			{
				IHL7BatchFile objHL7BatchFile = objIBatchProcessor.processReceive(objMessageFactory, objSegmentSet);

				ISingleCollection<IHL7Batch> objHL7BatchCollection = objHL7BatchFile.getHL7BatchCollection();
				if(objServerSideQICAgent!=null )
				{
					for(IHL7Batch objBatch : objHL7BatchCollection)
					{
						ISingleCollection<Message> objMessageCollection = objBatch.getMessageCollection();
						for(Message objmessage :objMessageCollection)
						{
							if(objmessage.isQuery())
							{
								if(objServerSideQICAgent.processReceive(objmessage.getSegmentSet()))
								{
									AbstractQuery objAbstractQuery = (AbstractQuery)objmessage;
									objAbstractQuery.setIsInteractiveQuery(true);
								}
							}
						}
						objBatch.setMessageCollection(objMessageCollection);
					}
				}
				if(objServerSideQCNAgent!=null )
				{
					for(IHL7Batch objBatch : objHL7BatchCollection)
					{
						ISingleCollection<Message> objMessageCollection = objBatch.getMessageCollection();
						for(Message objMessage:objMessageCollection)
						{
							if(objMessage.isQuery())
							{
								AbstractQuery objAbstractQuery = (AbstractQuery)objMessage;
								if(objServerSideQCNAgent.processReceive(objMessage.getSegmentSet()))
									objAbstractQuery.setIsCanceledQuery(true);
							}
						}
						objBatch.setMessageCollection(objMessageCollection);
					}
				}

				//Check for Sequence Number Protocol
				if(objSequenceAgent != null)
				{
					for(IHL7Batch objBatch : objHL7BatchCollection)
					{
						ISingleCollection<Message> objMessageCollection = objBatch.getMessageCollection();
						for(Message objMessage : objMessageCollection)
						{
							objSequenceAgent.processReceive(objMessage.getSegmentSet());
						}
					}
				}
				objIDataNotificationListener.notifyData(objHL7BatchFile);// Decision about discarded Batches.
			}
	}


	/**
	 * Sets IDataNotoficationListener on MessageAgent.
	 * @param objIDataNotificationListener Specifies Data Notification Listener on Message Agent.
	 */
	public void addDataNotoficationListener(IDataNotificationListener objIDataNotificationListener)
	{
		this.objIDataNotificationListener = objIDataNotificationListener;
	}

	/**
	 * Sets Message Handler on Message Agent.
	 * @param objIMessageHandler Specifies Message Handler on Message Agent.
	 */
	public void setMessageFactory(MessageFactory objMessageFactory)
	{
		this.objMessageFactory = objMessageFactory;

	}


	/**
	 * Gets automatically invoked when Message is received from remote system.
	 * @param messageToSend Specified Message To send 
	 * @throws IOException Thrown when IO operation fails.
	 * @throws ProcessFailedException Thrown when Process operation fails.
	 */	
	public void sendMessage(Message messageToSend) throws IOException, ProcessFailedException ,InvalidMessageException, InvalidSequenceException
	{

		EnumTriggerEvent enumTriggerEventToSend = StandardMethods.getEnumTriggerEvent(messageToSend.getTriggerEvent());
		EnumMessageStructure enumMessageStructureToSend = StandardMethods.getEnumMessageStructure(messageToSend.getMessageStructure());
		EnumMessageCode enumMessageCodeToSend = StandardMethods.getEnumMessageCode(messageToSend.getMessageCode());

		if(((objMessageFactory.getSendSupportedRecepient(enumMessageCodeToSend,enumTriggerEventToSend)) == null) && ((enumMessageCodeToSend == EnumMessageCode.ACK) && (objMessageFactory.getSendSupportedSource(enumMessageCodeToSend,enumTriggerEventToSend) == null)))
			throw new MessageNotSupportedException(enumMessageStructureToSend.getValue());

		//Check for Sequence Number Protocol
		if(objSequenceAgent != null)
			objSequenceAgent.processSend(messageToSend.getSegmentSet());

		if(messageToSend.isResponse())
		{
			//If Server side QIC agent is present then store the data related to this message.
			if(objServerSideQICAgent!=null)
				objServerSideQICAgent.processSend(messageToSend.getSegmentSet());

			//If QCN agent is present then store data related to this message.
			if(objServerSideQCNAgent!=null)
				objServerSideQCNAgent.processSend(messageToSend.getSegmentSet());

		}
			objIBufferSender.sendData(messageToSend.getSegmentSet());
	}


	/**
	 * Gets automatically invoked when Batch is received from remote system.
	 * @param objBatchFileToSend Specifies object of batch file to send. 
	 * @throws IOException Thrown when IO operation fails.
	 * @throws BatchException Thrown when Batch can not be read or created.
	 */	
	public void sendBatch(IHL7BatchFile objBatchFileToSend) throws IOException, BatchException
	{
		try
		{
			//If query cancellation agent is set and sending response is cancelled then do not send this and 
			//throw exception of invalid message.

			if(objServerSideQICAgent!=null)
			{
				for(IHL7Batch batchToSend : objBatchFileToSend.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection = batchToSend.getMessageCollection();

					for(Message objMessage : objMessageCollection)
					{
						if(objMessage.isResponse())
							objServerSideQICAgent.processSend(objMessage.getSegmentSet());
					}
				}
			}
			if(objServerSideQCNAgent!=null)
			{
				for(IHL7Batch batchToSend : objBatchFileToSend.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection = batchToSend.getMessageCollection();

					for(Message objMessage : objMessageCollection)
					{
						objServerSideQCNAgent.processSend(objMessage.getSegmentSet());
					}
				}
			}

			//Check for Sequence Number Protocol
			if(objSequenceAgent != null)
			{
				for(IHL7Batch batchToSend : objBatchFileToSend.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection = batchToSend.getMessageCollection();

					for(Message objMessage : objMessageCollection)
					{
						objSequenceAgent.processSend(objMessage.getSegmentSet());
					}
				}
			}
			objIBufferSender.sendData(objIBatchProcessor.processSend( objBatchFileToSend));
		}catch(InvalidMessageException ime)
		{
			//This will send batch exception by wrapping invalid message exception 
			//by QCN agent if any message is invalid
			throw new BatchException(this.getClass().getSimpleName(),ime);
		}
	}

	/**
	 * Sets IBufferSender on MessageAgent.
	 * @param objDataSender Specifies object of Buffer Sender on Message Agent. 
	 */
	public void setBufferSender(IBufferSender objDataSender)
	{
		this.objIBufferSender = objDataSender;
	}

	/**
	 * Retrieves Message from UserSession.
	 * @return Message Specifies Message from UserSession
	 */
	public Message getMessage()
	{
		return objReceivedMessage;
	}

	/**
	 * Sets the Query Cancellation agent.
	 * @param objServerSideQCNAgent Specifies Server side Query Cancellation agent.
	 */
	public void setServerSideQCNAgent(IServerQueryCancellation objServerSideQCNAgent) 
	{
		this.objServerSideQCNAgent = objServerSideQCNAgent;
	}
	/**
	 * Sets the Interactive Continuation Agent. 
	 * @param objServerSideQICAgent Specifies Server Side Interactive Continuation Agent.
	 */
	public void setServerSideQICAgent(IServerInteractiveContinuation objServerSideQICAgent) 
	{
		this.objServerSideQICAgent = objServerSideQICAgent;
	}
	/**
	 * Sets instance of PublishAgent on ClientSession.
	 * @param objPublishAgent - instance of PublishAgent.
	 */
	public void setPublishAgent(IPublishAgent objPublishAgent) 
	{
		this.objPublishAgent = objPublishAgent;
	}

	/**
	 * Sets the Sequence Agent. Sequence Agent is required to enable Sequence Numbering in the message transactions. 
	 * @param objSequenceAgent Sequence Agent to be set on UserSession.
	 */
	public void setSequenceAgent(ISequenceAgent objSequenceAgent)
	{
		this.objSequenceAgent = objSequenceAgent;
	}

	/**
	 * Closes the Server Side Message Agent
	 */
	public void close()
	{		
		objMessageFactory = null;
		objIDataNotificationListener = null;  
		objIBufferSender = null;
	}

	/*
	 * Processes the Message from Given HL7 stream. 
	 * It uses proper source or recipients to process the message from given stream.
	 * @param objIHL7Stream instance of specified HL7Stream 
	 * @param isRequester this variable specifies whether server side processing is required or client side.
	 * @return Message processes message object from stream
	 * @throws IOException Thrown when IO operation fails.
	 */
	private Message getProcessedMessage(ISegmentSet objSegmentSet) throws IOException, ProcessFailedException
	{
		Message objMessage = null;
		try
		{
			String strTriggerEvent = null;
			String strMessageCode = null;
			Segment objTempSegment = (Segment)objSegmentSet.getSegment(0);
			if(objTempSegment.getName().equals(EnumSegments.HL7_SEG_MSH))
			{
				MSH objMSH = (MSH)objTempSegment;
				strTriggerEvent = (String)objMSH.getMessageType().getTriggerEvent().getValue();
				strMessageCode = (String)objMSH.getMessageType().getMessageCode().getValue();	
			}
			if(objMessageFactory != null)
				objMessage = objMessageFactory.populateReceiveSupportedMessage(strMessageCode, strTriggerEvent, objSegmentSet);
		} 
		catch (ParseException pe) 
		{
			throw new ProcessFailedException(pe.getMessage(),pe);
		}
		catch(MessageNotSupportedException mse)
		{
			throw new ProcessFailedException(mse.getMessage(),mse);
		}

		return objMessage;
	}
}

