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

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

import cdac.medinfo.sdk.hl7282.baselibs.messages.AbstractQuery;
import cdac.medinfo.sdk.hl7282.baselibs.messages.IMessageRecepient;
import cdac.medinfo.sdk.hl7282.baselibs.messages.IMessageSource;
import cdac.medinfo.sdk.hl7282.baselibs.messages.LocalExtensionAgent;
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.AuxillaryProtocolException;
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.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBufferSender;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IClientInteractiveContinuation;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IClientQueryCancellation;
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.ISequenceAgent;
import cdac.medinfo.sdk.hl7282.utils.EnumErrorCode;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageCode;
import cdac.medinfo.sdk.hl7282.utils.EnumSegments;
import cdac.medinfo.sdk.hl7282.utils.EnumTriggerEvent;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;


/**
 * Client Side Message Agent validates received message .
 * It sends  outgoing messages Messages to Auxiliary protocol Agent.   
 */
public class ClientSideMessageAgent implements IDataProcessor    
{
	

	private IBatchProcessor objIBatchProcessor;
	IDataNotificationListener objIDataNotificationListener = null;  
	IBufferSender objIBufferSender = null;
	IClientInteractiveContinuation objClientSideQICAgent = null;
	private MessageFactory objMessageFactory = null;

	private IClientQueryCancellation objClientSideQCNAgent = null;

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

	/**
	 * Creates a new instance of ClientSideMessageAgent.
	 */
	public ClientSideMessageAgent() 
	{
	}

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

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

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

	/**
	 * Sets IInteractiveContinuation on MessageAgent.
	 * @param objClientSideQICAgent Specifies Client Side Interactive Continuation Agent.
	 */
	public void setInteractiveContinuation(IClientInteractiveContinuation objClientSideQICAgent)
	{
		this.objClientSideQICAgent = objClientSideQICAgent;
	}

	/**
	 * 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;
	}

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


	/**
	 * Sets Message Handler on Message Agent.
	 * @param objMessageHandler Message handler to be set on Client Side Message Agent
	 */
	public void setMessageFactory(MessageFactory objMessageFactory)
	{
		this.objMessageFactory = objMessageFactory;
	}


	/**
	 * Sets max length limit of writing. 
	 * @param iWriteLength value of Writing max length.
	 */
	public void setWriteLength(int iWriteLength)
	{
		//iWriteMaxLength = iWriteLength;
	}

	/**
	 * 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 Numbering
			if(objSequenceAgent != null)
				objSequenceAgent.processReceive(objSegmentSet);
			
			if(objReceivedMessage.isResponse())
			{
				AbstractQuery objAbstractQuery =(AbstractQuery)objReceivedMessage;
				//To check whether Received Message is Response if it is Interactive then set true value for this message. 
				if (objClientSideQICAgent != null)
				{
					if(objClientSideQICAgent.processReceive(objReceivedMessage.getSegmentSet()))
						objAbstractQuery.setIsInteractiveResponse(true);
				}
				// if ClientSideQCNAgent is present then received Response will be verified and if it is 
				// canceled then cancellation flag will be set to this message for special treatment.
				if(objClientSideQCNAgent!=null)
				{
					if(objClientSideQCNAgent.processReceive(objReceivedMessage.getSegmentSet()))
						objAbstractQuery.setIsCanceledResponse(true);
				}
			}
			objIDataNotificationListener.notifyData(objReceivedMessage,EnumErrorCode.MESSAGE_ACCEPTED);
		}
		else
		{
			IHL7BatchFile objHL7BatchFile = objIBatchProcessor.processReceive(objMessageFactory, objSegmentSet);
			if(objClientSideQICAgent!=null)
			{
				for(IHL7Batch objHL7Batch : objHL7BatchFile.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection =	objHL7Batch.getMessageCollection();
					for(Message objMessage:objMessageCollection)
					{
						if(objMessage.isResponse())
						{
							if(objClientSideQICAgent.processReceive(objMessage.getSegmentSet()))
							{
								AbstractQuery objAbstractQuery = (AbstractQuery)objMessage;
								objAbstractQuery.setIsInteractiveResponse(true);
							}
						}
					}
				}
			}
			// if Client Side QCN Agent agent is present then received Response will be verified if it  
			// is canceled response then cancellation flag will be set to it for special treatment of it.
			if(objClientSideQCNAgent!=null )
			{
				for(IHL7Batch objHL7Batch : objHL7BatchFile.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection =	objHL7Batch.getMessageCollection();
					for(Message objMessage:objMessageCollection)
					{
						if(objMessage.isResponse())
						{
							AbstractQuery objAbstractQuery = (AbstractQuery)objMessage;
							if(objClientSideQCNAgent.processReceive(objMessage.getSegmentSet()))
								objAbstractQuery.setIsCanceledResponse(true);
						}
					}
					objHL7Batch.setMessageCollection(objMessageCollection);
				}
			}

			//Check for Sequence Number Protocol
			if(objSequenceAgent != null)
			{
				for(IHL7Batch objHL7Batch : objHL7BatchFile.getHL7BatchCollection())
				{
					ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
					for(Message objMessage : objMessageCollection)
					{
						objSequenceAgent.processReceive(objMessage.getSegmentSet());
					}
				}
			}
			// No Error Code required, because only valid batches will be processed.
			objIDataNotificationListener.notifyData(objHL7BatchFile);
		}
	}

	/**
	 * Gets automatically invoked when Message is received from remote system.
	 * @param messageToSend Specified message to be sent
	 * @throws IOException Thrown when IO Operation fails.
	 */
	public void sendMessage(Message messageToSend) throws IOException, ProcessFailedException, InvalidSequenceException
	{
		//checks whether message is supported for sending using available sources or recipients.
		if(objMessageFactory.isMessageSendSupported(messageToSend))
		{
			if(messageToSend.isQuery())
			{
				//Checks whether sending query is for Interactive Continuation or not.
				if(objClientSideQICAgent != null)
					objClientSideQICAgent.processSend(messageToSend.getSegmentSet());

				//Check that whether ClientSideQCNAgent is provided then process that message.
				//if message is invalid then it will be thrown.
				if(objClientSideQCNAgent!=null)
				{
					objClientSideQCNAgent.processSend(messageToSend.getSegmentSet());
				}
			}

			//Check whether agent for Sequence Number is set or not, if provided then process the message.
			if(objSequenceAgent != null)
			{
				objSequenceAgent.processSend(messageToSend.getSegmentSet());
			}
			objIBufferSender.sendData(messageToSend.getSegmentSet());
		}
		else
			throw new MessageNotSupportedException(messageToSend.getMessageStructure());
	}

	/**
	 * Gets automatically invoked when Batch is received from remote system.
	 * @param objHl7BatchFile object of Batch File.
	 * @throws IOException - if IO exception fails.
	 * @throws BatchException Thrown when Batch can not be read or created.
	 * @throws AuxillaryProtocolException Thrown when Auxillary Processing Fails.
	 */
	public void sendBatch(IHL7BatchFile objHl7BatchFile) throws IOException, BatchException, AuxillaryProtocolException
	{
		try
		{
			ISingleCollection<IHL7Batch> objHL7BatchCollection = objHl7BatchFile.getHL7BatchCollection();
			//checks whether all batches are supported for sending using available sources or recipients.
			for(IHL7Batch objHL7Batch : objHL7BatchCollection)
			{
				if(!isBatchSupported(objHL7Batch))
				{
					throw new BatchException(ResourceBundleWrapper.getValue("exceptions","Batch_BatchNotSupported",new Locale(HL7Config.createInstance().getLocale())));
				}
				if(objClientSideQICAgent!=null)
				{
					ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
					for(Message objMessage :objMessageCollection)
					{
						if(objMessage.isQuery())
							objClientSideQICAgent.processSend(objMessage.getSegmentSet());
					}
				}
				if(objClientSideQCNAgent!=null)
				{
					ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
					for(Message objMessage :objMessageCollection)
					{
						if(objMessage.isQuery())
						{
							objClientSideQCNAgent.processSend(objMessage.getSegmentSet());
						}
					}
				}

				//Check for Sequence Number Protocol
				if(objSequenceAgent != null)
				{
					ISingleCollection<Message> objMessageCollection = objHL7Batch.getMessageCollection();
					for(Message objMessage : objMessageCollection)
					{
						objSequenceAgent.processSend(objMessage.getSegmentSet());
					}
				}
			}
			objIBufferSender.sendData(objIBatchProcessor.processSend( objHl7BatchFile));
		}catch(InvalidMessageException ime)
		{
			//If any exception will raise from Client Side QCN agent then this will be catch 
			//and batchException will be thrown. 
			throw new BatchException(this.getClass().getSimpleName(),ime);
		}
	}

	/**
	 * Checks whether the messages within Batch are supported by processors and recepients. 
	 * @param messageToSend
	 * @return true - if all messages are supported.
	 * 		   false - otherwise. 
	 * @throws IOException Thrown when IO Operation fails.
	 */
	private boolean isBatchSupported(IHL7Batch objHl7Batch) throws IOException
	{
		ISingleCollection<Message> objMessageCollection = objHl7Batch.getMessageCollection();
		for(Message objMessage : objMessageCollection)
		{
			if(!objMessageFactory.isMessageSendSupported(objMessage))
				return false;
		}
		return true;
	}

	/**
	 * Closes the Client Side Message Agent
	 */
	public void close()
	{		
		objIDataNotificationListener = null;  
		objMessageFactory = null;
	}
	/**
	 * This will set the Client Side Query Cancellation agent.
	 * @param objClientSideQCNAgent Specifies Client side Query Cancellation agent.
	 */
	public void setClientSideQCNAgent(IClientQueryCancellation objClientSideQCNAgent)
	{
		this.objClientSideQCNAgent = objClientSideQCNAgent;
	}

	/*
	 * 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;
		EnumTriggerEvent enumTriggerEvent = null;
		EnumMessageCode enumMessageCode = null;

		try
		{
			Segment objTempSegment = (Segment)objSegmentSet.getSegment(0);
			String strTriggerEvent = null;
			String strMessageCode = null;
			if(objTempSegment.getName().equals(EnumSegments.HL7_SEG_MSH))
			{
				MSH objMSH = (MSH)objTempSegment;
				strTriggerEvent = (String)objMSH.getMessageType().getTriggerEvent().getValue();
				strMessageCode = (String)objMSH.getMessageType().getMessageCode().getValue();	
			}
			enumTriggerEvent =  StandardMethods.getEnumTriggerEvent(strTriggerEvent);
			enumMessageCode = StandardMethods.getEnumMessageCode(strMessageCode);
			// For server side recipients  will be used for message population
			IMessageSource objIMessageSource = objMessageFactory.getReceiveSupportedSource(enumMessageCode,enumTriggerEvent);
			if(objIMessageSource != null)
			{
				/*
				 * If local extension agent is provided then if msg is registered with agent then it checks
				 * whether extension is supported by source or recipient for this message or not.
				 * if message extension is supported then messages will be processed according to the agent.  
				 */
				LocalExtensionAgent objLocalExtensionAgent = objMessageFactory.getLocalExtensionAgent();
				if(objLocalExtensionAgent != null)
				{
					if(objLocalExtensionAgent.isMsgLocallyRegistered(strMessageCode, strTriggerEvent) || (strMessageCode.equals(EnumMessageCode.ACK.getValue())))
					{
						if(objIMessageSource.isMessageExtensionSupported(enumMessageCode, enumTriggerEvent))
						{
							objMessage = objLocalExtensionAgent.populateMessage(strMessageCode, strTriggerEvent, objSegmentSet);
						}
						return objMessage;
					}
				}
				if(enumTriggerEvent == null)
					throw new MessageNotSupportedException(strTriggerEvent);

				objMessage = objIMessageSource.populateMessage(enumMessageCode,enumTriggerEvent,objSegmentSet);
				return objMessage;
			}
			else if(enumMessageCode == EnumMessageCode.ACK)
			{
				IMessageRecepient objMessageRecepient = objMessageFactory.getReceiveSupportedRecepient(enumMessageCode,enumTriggerEvent);

				if(objMessageRecepient != null)
				{
					/*
					 * If local extension agent is provided then if msg is registered with agent then it checks
					 * whether extension is supported by source or recipient for this message or not.
					 * if message extension is supported then messages will be processed according to the agent.  
					 */
					LocalExtensionAgent objLocalExtensionAgent = objMessageFactory.getLocalExtensionAgent();
					if(objLocalExtensionAgent != null)
					{
						if(objLocalExtensionAgent.isMsgLocallyRegistered(strMessageCode, strTriggerEvent) || (strMessageCode.equals(EnumMessageCode.ACK.getValue())))
						{
							if(objMessageRecepient.isMessageExtensionSupported(enumMessageCode, enumTriggerEvent))
							{
								objMessage = objLocalExtensionAgent.populateMessage(strMessageCode, strTriggerEvent, objSegmentSet);
							}
							return objMessage;
						}
					}
					if(enumTriggerEvent == null)
						throw new MessageNotSupportedException(strTriggerEvent);

					objMessage = objMessageRecepient.populateMessage(enumMessageCode,enumTriggerEvent,objSegmentSet);
					return objMessage;
				}
				else
					//If not supported by source and recipient then exception will be generated
					throw new MessageNotSupportedException(strTriggerEvent);
			}

			//If not supported by source and recipient then exception will be generated
			throw new MessageNotSupportedException(strTriggerEvent);
		} 
		catch (ParseException pe) 
		{
			throw new ProcessFailedException(pe.getMessage(),pe);
		}
		catch(MessageNotSupportedException mse)
		{
			throw new ProcessFailedException(mse.getMessage(),mse);
		}
	}
}
