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

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Locale;

import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IClientInteractiveContinuation;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IClientQueryCancellation;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IConnectionListener;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IDataNotificationListener;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IExceptionReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IFragmentProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7Batch;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IHL7BatchFile;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IMLLP;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ISequenceAgent;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ITransactionHandler;
import cdac.medinfo.sdk.hl7282.hl7net.communication.AuxAgent;
import cdac.medinfo.sdk.hl7282.hl7net.communication.ClientSideMessageAgent;
import cdac.medinfo.sdk.hl7282.hl7net.communication.ClientSideNetworkAgent;
import cdac.medinfo.sdk.hl7282.hl7net.communication.MLLP;
import cdac.medinfo.sdk.hl7282.baselibs.messages.ACK;
import cdac.medinfo.sdk.hl7282.baselibs.messages.AbstractQuery;
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.parserserializer.HL7Parser;
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.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.InvalidSegmentException;
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.exceptions.SegmentMissingException;
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.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.IMultiValuedPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.MultiValuedPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;



/**
 *User Session provides method for client side to Query other systems & also to trigger any specific Event.
 */
public class UserSession implements IDataNotificationListener ,IExceptionReceiver  ,IConnectionListener
{
	

	/*Aysnc receiver for Deferred response*/
	private IExceptionListener objIExceptionListener;

	/* The obj network agent. */
	private ClientSideNetworkAgent objClientSideNetworkAgent;

	/* The Message agent. */
	private ClientSideMessageAgent objClientSideMessageAgent = null;

	/* The Interactive Continuation agent. */
	private IClientInteractiveContinuation objClientSideQICAgent;

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

	/*The Message Handler */
	private MessageFactory objMessageFactory;

	/* The objIProcess handler. */
	private IProcessHandler objIProcessHandler;

	/**The Fragment Processor */
	private IFragmentProcessor objFragmentProcessor = null;

	/**The Batch Processor */
	private IBatchProcessor objBatchProcessor = null;

	/* The Auxiliary Protocol Agent	 */
	private AuxAgent objAuxAgent;

	private int iReadMaxLength,iWriteMaxLength;

	/*Message Object */
	private Message objHL7Message;

	/* HL7 Batch File */
	private IHL7BatchFile objHL7BatchFile = null;

	/*Specified the amount of time for which User Session should wait for ACK/Response.*/
	private int waitPeriod;

	/*ACK collection*/
	private IMultiValuedPairedCollection<String,Message> objACKResponseCollection = null;

	/*HL7BatchCollection for Responses and ACK */
	private ISingleCollection<IHL7BatchFile> objHL7BatchResponseCollection = null;

	private EnumErrorCode errorCode;

	/* Server Session for starting listener */
	private ServerSession objServerSession = null;

	private int itimeout = 0;

	/*The Client Side Query Cancellation agent*/
	private IClientQueryCancellation objClientSideQCNAgent = null;

	/*This sets the value if cancellation query QCN_J01 or Subscription or Subscription Cancellation query is triggered*/
	private boolean isWaitingRequired = false;

	/* Batch receiver for Deferred response*/
	private IBatchReceiver objIBatchReceiver;

	/* Interactive Response receiver.*/
	private IInteractiveResponseReciever objInteractiveResponseReciever;

	/* Canceled Response receiver.*/
	private ICanceledResponseReceiver objCanceledResponseReceiver;

	/* Client Side Transaction Handler*/
	private ITransactionHandler objClientSideTransactionHandler = null;
	
	/** The MLLP Processor **/
	private IMLLP objIMLLP = null;

	/**
	 * Instantiates User Session
	 */
	public UserSession()
	{
		objACKResponseCollection = new MultiValuedPairedCollection<String, Message>();
		objHL7BatchResponseCollection = new SingleCollection<IHL7BatchFile>();
	}

	/**
	 * Instantiates User Session by given connection.
	 * @param objSocket Specified socket to which UserSessoin is to be connected
	 * @throws IOException thrown when IOOperation fails
	 */
	public UserSession(Socket objSocket)throws IOException
	{
		this();
		if(objSocket != null)
		{
			objAuxAgent = new AuxAgent();
			objAuxAgent.setExceptionReceiver(this);

			if(objMessageFactory == null)
				objMessageFactory = MessageFactory.createInstance();

			objClientSideMessageAgent = new ClientSideMessageAgent();
			objClientSideNetworkAgent = new ClientSideNetworkAgent();
			objClientSideNetworkAgent.setSocketInputstream(objSocket.getInputStream());
			objClientSideNetworkAgent.setSocketOutputstream(objSocket.getOutputStream());
			objClientSideNetworkAgent.setConnectionListener((IConnectionListener)this);
			this.objClientSideNetworkAgent.addBufferReceiver(objAuxAgent);
			objAuxAgent.setNetworkAgent(objClientSideNetworkAgent);
			objAuxAgent.setDataProcessor(objClientSideMessageAgent);

			objClientSideMessageAgent.addDataNotoficationListener(this);
			objClientSideMessageAgent.setMessageFactory(objMessageFactory);
			objClientSideMessageAgent.setBufferSender(objAuxAgent);
			objClientSideMessageAgent.setInteractiveContinuation(objClientSideQICAgent);
			objClientSideMessageAgent.setSequenceAgent(objSequenceAgent);
			if(objFragmentProcessor != null)
				objFragmentProcessor.setMaxLength(iWriteMaxLength);
			this.objClientSideNetworkAgent.start();
		}
	}

	/**
	 * Sets the Interactive Continuation Agent. 
	 * @param objClientSideQICAgent the ClientSideQICAgent to set.
	 */
	public void setClientSideQICAgent(IClientInteractiveContinuation objClientSideQICAgent)
	{
		this.objClientSideQICAgent = objClientSideQICAgent;
	}

	/**
	 * Sets the sequence agent on UserSession.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;
	}

	/**
	 * Invoked when exception is fetched while processing Message.
	 * @param ex Exception fetched
	 * @param buffer data for which error is fetched
	 * 
	 */
	public void onException(Exception ex, byte[] buffer) 
	{
		synchronized (this)
		{
			this.notify();
		}
		if (HL7Config.createInstance().isLoggingOn())
		{
			HL7Logger.getLogger().log(ex);
		}
		if(objIExceptionListener != null)
			objIExceptionListener.onException(ex, buffer);
	} 

	/**
	 * Invoked when exception is fetched while processing Message.
	 * @param ex Exception fetched
	 * @param buffer data for which error is fetched
	 * 
	 */
	public void onException(Exception ex,ISegmentSet objISegmentSet) 
	{
		synchronized (this)
		{
			this.notify();
		}
		if (HL7Config.createInstance().isLoggingOn())
		{
			HL7Logger.getLogger().log(ex);
		}
		if(objIExceptionListener != null)
			objIExceptionListener.onException(ex, objISegmentSet);
	} 

	/**
	 * Sets the Fragment Processor
	 * @param objIFragmentProcessor the objIFragmentProcessor to set
	 */
	public void setFragmentProcessor(IFragmentProcessor objIFragmentProcessor) 
	{
		if(this.objFragmentProcessor == null)
		{
			this.objFragmentProcessor = objIFragmentProcessor;
			if(objAuxAgent != null)
				objAuxAgent.setFragmentProcessor(objFragmentProcessor);
		}
	}
	
	/**
	 * Enable MLLP at User session.
	 */
	public void enableMLLP()
	{
		objIMLLP = new MLLP();
		setMLLPProcessor(objIMLLP);
	}
	
	/**
	 * Set MLLP at User session.
	 * @param objIMLLP the MLLP processor to set.
	 */
	private void setMLLPProcessor(IMLLP objIMLLP)
	{
		this.objIMLLP = objIMLLP;
	}

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

	/**
	 * Sets Message Factory
	 * @param objMessageFactory instance of Message Factory
	 */
	public void setMessageFactory(MessageFactory objMessageFactory)
	{
		this.objMessageFactory = objMessageFactory;
	}

	/**
	 * Retrieves the wait Period for which UserSession should wait till it receives ACK/Reponse for triggered event/Query.  
	 * @return int - wait period
	 */
	public int getWaitPeriod() 
	{
		return waitPeriod;
	}

	/**
	 * Sets max length limit of reading and writing. 
	 * @param iReadLength value of Reading max length
	 * @param iWriteLength value of Writing max length
	 */
	public void setReadWriteLength(int iReadLength,int iWriteLength)
	{
		if(iReadLength>0 && iWriteLength>0)
		{
			iReadMaxLength = iReadLength;
			iWriteMaxLength = iWriteLength;	
		}
		else
		{
			if(iReadLength>=0)
			{
				iReadMaxLength = iReadLength;
			}
			if(iWriteLength>=0)
			{
				iWriteMaxLength = iWriteLength;	
			}
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				String strLogMessage = ResourceBundleWrapper.getValue("warnings","Fragment_InvalidReadersWritersLength",new Locale(HL7Config.createInstance().getLocale()));
				objHL7Logger.log(strLogMessage);
			}
		}
	}

	/**
	 * Sets the wait period for which UserSession should wait till it receives ACK/reponse for triggered event/Query.
	 * @param waitPeriod Specifies wait period for UserSession.
	 */
	public void setWaitPeriod(int waitPeriod) 
	{
		if(waitPeriod > 0)
		{
			this.waitPeriod = waitPeriod;
		}
		else
		{
			//if wait period is set 0 or -ve value then set it as it is value for infinite wait period.
			this.waitPeriod = waitPeriod;

			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				String strLogMessage = ResourceBundleWrapper.getValue("warnings","InvalidWaitPeriod",new Locale(HL7Config.createInstance().getLocale()));
				objHL7Logger.log(strLogMessage);
			}
		}
	}

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

	/**
	 * Sets Message on UserSession.
	 * @param objHL7Message Specifies object of Message from UserSession.
	 */
	public void setMessage(Message objHL7Message) 
	{
		this.objHL7Message = objHL7Message;
	}	

	/**
	 * Retrieves instance of Listener Which implements IProcessHandler, All messages subscribed by this UserSession
	 * are received using this listener.
	 * @return IProcessHandler instance of listener implements IProcessHandler
	 */
	public IProcessHandler getListener() 
	{
		return objIProcessHandler;
	}

	/**
	 * Sets Listener Which implements IProcessHandler, All messages subscribed by this UserSession
	 * are received using this listener.
	 * @param objProcessHandler instance of listener implements IProcessHandler
	 */
	public void setListener(IProcessHandler objProcessHandler)
	{
		this.objIProcessHandler = objProcessHandler;
	}

	/**
	 * Register for Receiving Deferred Response.
	 * @param objIExceptionListener - implementor of IExceptionListener
	 */
	public void addExceptionListener(IExceptionListener objIExceptionListener)
	{
		this.objIExceptionListener = objIExceptionListener;
	}

	/**
	 * This will set the IInteractiveResponseReciever on user session.
	 * @param objInteractiveResponseReciever - implementor of IInteractiveResponseReciever
	 */
	public void setIInteractiveResponseReciever(IInteractiveResponseReciever objInteractiveResponseReciever)
	{
		this.objInteractiveResponseReciever = objInteractiveResponseReciever;
	}
	/**
	 * This will set the IBatchReceiver on user session.
	 * @param objIExceptionListener - implementor of IExceptionListener
	 */
	public void setIBatchReceiver(IBatchReceiver objIBatchReceiver)
	{
		this.objIBatchReceiver = objIBatchReceiver;
	}
	/**
	 * This will set the ICanceledResponseReceiver on user session.
	 * @param objCanceledResponseReceiver - implementor of ICanceledResponseReceiver.
	 */
	public void setICanceledResponseReceiver(ICanceledResponseReceiver objCanceledResponseReceiver)
	{
		this.objCanceledResponseReceiver = objCanceledResponseReceiver;
	}
	/**
	 * This will set the ITransactionHandler on User session.
	 * @param objClientSideTransactionHandler - implementor of ITransactionHandler
	 */
	public void setClientSideTransactionHandler(ITransactionHandler objClientSideTransactionHandler)
	{
		this.objClientSideTransactionHandler = objClientSideTransactionHandler;
	}
	/**
	 * Retrieves Error Code of received message. 
	 * @return EnumErrorCode - Error Code returned
	 */
	public EnumErrorCode getErrorCode() 
	{
		return errorCode;
	}

	/**
	 * Sets Error Code of received 
	 * @param enumErrorCode
	 */
	public void setErrorCode(EnumErrorCode enumErrorCode)
	{
		this.errorCode = enumErrorCode;
	}


	/**
	 * Invoked when Message is received from Remote End.
	 * @param objReceivedMessage Received Message
	 * @param enumErrorCode Received Error Code
	 */
	public void notifyData(Message objReceivedMessage,EnumErrorCode enumErrorCode) throws IOException, InvalidMessageException
	{
		try
		{
			this.objHL7Message = objReceivedMessage;
			setErrorCode(enumErrorCode);

			if(HL7Config.createInstance().isLoggingOn())
			{
				StatusLogger.logMessage(objReceivedMessage);
			}

			// This Client Side Transaction handler will process the 
			// Receiving message and keeps data for it.
			// internally it performs all validation for all Messages according to Mode set.
			if(objClientSideTransactionHandler!=null)
				objClientSideTransactionHandler.processMessageTransaction(objReceivedMessage);

			if(objReceivedMessage.isResponse())
			{
				if(objInteractiveResponseReciever!=null )
				{
					AbstractQuery objAbstractquery = (AbstractQuery)objReceivedMessage;
					if(objAbstractquery.isInteractiveResponse())
					{
						objInteractiveResponseReciever.onInteractiveResponseReceive(objReceivedMessage);
						synchronized(this)
						{
							this.notify();
						}
						return ;
					}
				}
				if(objCanceledResponseReceiver!=null )
				{
					AbstractQuery objAbstractquery = (AbstractQuery)objReceivedMessage;
					if(objAbstractquery.isCanceledResponse())
					{
						objCanceledResponseReceiver.onResponseCancellationReceive(objReceivedMessage);
						synchronized(this)
						{
							this.notify();
						}
						return ;
					}
				}
				objACKResponseCollection.put(objReceivedMessage.getMessageControlID(), objReceivedMessage);

				synchronized(this)
				{
					this.notify();
				}
			}
			else if (objReceivedMessage.getMessageStructure().equals(EnumMessageStructure.ACK.getValue()))
			{
				objACKResponseCollection.put(objReceivedMessage.getMessageControlID(),(ACK)objReceivedMessage);
				synchronized(this)
				{
					notify();
				}
			}
		}
		catch (InvalidMessageException ime)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(ResourceBundleWrapper.getValue("exceptions", "InvalidMessage", new Locale(HL7Config.createInstance().getLocale())) + objReceivedMessage.getMessageCode() + "_" + objReceivedMessage.getTriggerEvent());				
			}
		}
	}

	/**
	 * Stops listening for HL7 message if listener is started.
	 */
	public void stopListener() throws IOException
	{
		if(null != objServerSession)
			objServerSession.close();
		else 
			if(HL7Config.createInstance().isLoggingOn())
				HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings","PubSub_NoListenerStarted",new Locale(HL7Config.createInstance().getLocale())));		
	}

	/**
	 * Invoked when HL7BatchFile is received from Remote End.
	 * @param objBatchFile Received Message
	 */
	public void notifyData(IHL7BatchFile objBatchFile )
	{
		this.objHL7BatchFile = objBatchFile;

		if(HL7Config.createInstance().isLoggingOn())
		{
			StatusLogger.logBatchFile(objBatchFile);
		}

		// This Client Side Transaction handler will process the 
		// Received Batch File and Removed data for batch for which data is already received.
		// internally it performs all validation for all Batches according to Mode set.
		if(objClientSideTransactionHandler!=null)
			objClientSideTransactionHandler.processBatchTransaction(objBatchFile);

		//If received response is Interactive then this Response will be separately 
		//given to interface registered on method onInteractiveResponseReceive
		if(objInteractiveResponseReciever!=null)
		{
			for (IHL7Batch objBatch : objBatchFile.getHL7BatchCollection()) 
			{
				ISingleCollection<Message> objMessageCollection = objBatch.getMessageCollection();
				for(Message objMessage : objMessageCollection)
				{
					AbstractQuery objAbstractquery = (AbstractQuery)objMessage;
					if(objAbstractquery.isCanceledResponse())
					{
						objInteractiveResponseReciever.onInteractiveResponseReceive(objMessage);
						objMessageCollection.remove(objMessage);
					}
				}
			}
		}
		//If received response is Canceled then this Response will be separately 
		//given to interface registered ICanceledResponseReceiver on method onResponseCancellationReceive
		if(objCanceledResponseReceiver!=null)
		{
			for (IHL7Batch objBatch : objBatchFile.getHL7BatchCollection()) 
			{
				ISingleCollection<Message> objMessageCollection = objBatch.getMessageCollection();
				for(Message objMessage : objMessageCollection)
				{
					AbstractQuery objAbstractquery = (AbstractQuery)objMessage;
					if(objAbstractquery.isCanceledResponse())
					{
						objCanceledResponseReceiver.onResponseCancellationReceive(objMessage);
						objMessageCollection.remove(objMessage);
					}
				}
			}
		}
		if(objIBatchReceiver != null)
			objIBatchReceiver.onBatchReceive(objHL7BatchFile);

		//This will remove all batches from collection if it is stored and it will add latest.
		for(int i =0;i < objHL7BatchResponseCollection.size();i++)
		{
			objHL7BatchResponseCollection.remove(i);
		}

		objHL7BatchResponseCollection.add(objBatchFile);
		synchronized(this)
		{
			this.notify();
		}
	}

	/**
	 * Retrieves last received Response Batch collection.      
	 * @return ISingleCollection<IHL7BatchFile> - Collection of ACK messages received.
	 */
	public ISingleCollection<IHL7BatchFile> getResponseBatches()
	{
		return objHL7BatchResponseCollection;
	}

	/**
	 * Connects this UserSession to provided server IP Address and port
	 * @param strIP IP address specified in String format
	 * @param iPort specified Port 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void connect(String strIP, int iPort) throws IOException 
	{
		connect(new InetSocketAddress(strIP,iPort));
	}

	/**
	 * Connects this UserSession to provided server on local host to given port
	 * @param iPort int value of port.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void connect(int iPort) throws IOException
	{
		connect(new InetSocketAddress(InetAddress.getLocalHost(),iPort));
	}

	/**
	 * Connects this UserSession to provided server IP address and port.
	 * @param ip instance of INet Address.
	 * @param iPort int value of port.
	 * @throws IOException  Signals that an I/O exception has occurred.
	 */
	public void connect(InetAddress ip, int iPort)throws IOException
	{
		connect(new InetSocketAddress(ip,iPort));
	}

	/**
	 * Connects this UserSession to provided server Socket Address
	 * @param addr specified address
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void connect(InetSocketAddress addr) throws IOException
	{
		if(objMessageFactory == null)
			objMessageFactory = MessageFactory.createInstance();

		objAuxAgent = new AuxAgent();
		objAuxAgent.setExceptionReceiver((IExceptionReceiver)this);

		objClientSideMessageAgent = new ClientSideMessageAgent();
		objClientSideMessageAgent.setClientSideQCNAgent(objClientSideQCNAgent);
		objClientSideMessageAgent.setBatchProcessor(objBatchProcessor);
		objClientSideNetworkAgent = new ClientSideNetworkAgent();
		objClientSideNetworkAgent.setConnectionListener((IConnectionListener)this);
		this.objClientSideNetworkAgent.addBufferReceiver(objAuxAgent);

		if(objFragmentProcessor != null)
			objFragmentProcessor.setMaxLength(iWriteMaxLength);

		objAuxAgent.setFragmentProcessor(objFragmentProcessor);
		objAuxAgent.setMLLPProcessor(objIMLLP);
		objAuxAgent.setNetworkAgent(objClientSideNetworkAgent);
		objAuxAgent.setDataProcessor(objClientSideMessageAgent);


		objClientSideMessageAgent.addDataNotoficationListener(this);
		objClientSideMessageAgent.setBufferSender(objAuxAgent);
		objClientSideMessageAgent.setInteractiveContinuation(objClientSideQICAgent);
		objClientSideMessageAgent.setSequenceAgent(objSequenceAgent);
		objClientSideMessageAgent.setMessageFactory(objMessageFactory);
		objAuxAgent.setWriteLength(iWriteMaxLength);
		objClientSideNetworkAgent.setReadWriteLength(iReadMaxLength,iWriteMaxLength);

		this.objClientSideNetworkAgent.connect(addr,itimeout);
		this.objClientSideNetworkAgent.start();
		
		if(HL7Config.createInstance().isLoggingOn())
		{
			HL7Logger objHL7Logger = HL7Logger.getLogger();
			objHL7Logger.log(ResourceBundleWrapper.getValue("status", "SCUSessionStarted",new Locale(HL7Config.createInstance().getLocale())));
		}
	}


	/**
	 * Sends the message to server system.
	 * In case of Event or Query this method waits until an ACK is received. 
	 * @param messageToSend instance of message
	 * @return boolean - shows status as success or failure
	 * 						if sending operation performed successfully it returns true else false.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public boolean sendMessage(Message messageToSend) throws IOException 
	{
		isWaitingRequired = false;
		if(messageToSend != null)
		{
			try 
			{
				if(isMessageSupported(messageToSend))
				{
					if(messageToSend.validate())
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							StatusLogger.logMessage(messageToSend);
						}

						// This Client Side Transaction handler will process the 
						// sending message and keeps data for it.
						// internally it performs all validation for all Messages according to Mode set. 
						if(objClientSideTransactionHandler!=null)
							objClientSideTransactionHandler.processMessageTransaction(messageToSend);

						if(false == messageToSend.getMessageStructure().equals(EnumMessageStructure.ACK.getValue()))
						{
							synchronized(this)
							{
								do
								{   
									//This method checks that in case of QCN,QSX or Subscription message wait is required if yes then set.
									if(isWaitingRequired(messageToSend)) 
										isWaitingRequired = true;

									objClientSideMessageAgent.sendMessage(messageToSend);
									//if cancellation query is triggered then do not wait for it.
									if(!isWaitingRequired)
									{
										//for wait period of 0 or -ve values wait period is infinite.
										if(waitPeriod >  0)
											this.wait(waitPeriod);
										else 
											this.wait();
									}
									else 
										isWaitingRequired = false;

									//If for sending message is QCN_J01 or Subscription Message or Subscription Cancellation Message then there will be no recieving message will get.
									if(!isWaitingRequired ||!EnumMessageStructure.ACK.getValue().equals(objHL7Message.getMessageStructure()) || !objHL7Message.isResponse() || !(getErrorCode().toString().equals(EnumErrorCode.APPLICATION_ERROR.toString())))
										return true;
								}
								while(true);
							}
						}
						else  // If message sent is ACK
						{
							objClientSideMessageAgent.sendMessage(messageToSend);								
						}
						isWaitingRequired = false;
					}
					return true;
				}
				throw new InvalidMessageException(ResourceBundleWrapper.getValue("exceptions","MessageNotRegistered",new Locale(HL7Config.createInstance().getLocale()+ messageToSend.getMessageStructure())));
			} 
			catch (InterruptedException e) 
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(e);
				}
				return false;
			}
			catch (InvalidMessageException e) 
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(e);
				}
				return false;
			}
			catch (ProcessFailedException pfe) 
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(pfe);
				}
				return false;
			}
			catch (MessageNotSupportedException mse) 
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(mse);
				}
				return false;
			}
			catch (InvalidSequenceException ise)
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(ise);
				}
				return false;
			}
		}
		else
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				String strLogMessage = ResourceBundleWrapper.getValue("exceptions","NullMessage",new Locale(HL7Config.createInstance().getLocale()));
				objHL7Logger.log(strLogMessage);
			}
			return false;
		}
	}

	/**
	 * This method parses message from file located at given path.
	 * After parsing it sends the message to server system
	 * @param strFilePath specified path of File containing message
	 * @return boolean - shows status as success or failure
	 * 						if sending operation performed successfully it returns true else false.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public boolean sendMessage(String strFilePath) throws IOException
	{
		try 
		{
			boolean status = false;
			Message parsedMessage = getParsedMessage(strFilePath);
			if(parsedMessage != null)
				status = sendMessage(parsedMessage);	

			return status;
		}
		catch (ParseException pe) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(pe);
			}
			return false;
		}
		catch(MessageNotSupportedException mse)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(mse);
			}
			return false;
		}
	}

	/*
	 * This method parse the message using proper source or recipients for given stream.
	 */
	private Message getParsedMessage(String strPath) throws IOException
	{
		Message objMessage = null;
		try
		{
			HL7Parser objHL7Parser = new HL7Parser();

			ISegmentSet objSegmentSet = objHL7Parser.parse(strPath);
			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.createSendSupportedMessage(strMessageCode, strTriggerEvent);

			if(objMessage != null)
				objMessage.populate(objSegmentSet);
			/*
			 * For sending only source is supposed to create event or query message.
			 * To create the ack source or recipients any one of these two can be used.
			 * In this if-else block it checks if source to create ack 
			 * for given event is not present then it checks for available recipients. 
			 */

		}
		catch (InvalidMessageException ime)
		{
			throw new ParseException(ime.getMessage(), ime);
		}
		catch(SegmentMissingException ex)
		{
			throw new ParseException(ex.getMessage(),ex);
		}
		catch(InvalidSegmentException ex)
		{
			throw new ParseException(ex.getMessage(),ex);
		}
		catch (MessageNotSupportedException mns)
		{
			throw new ParseException(mns.getMessage(),mns);			
		}
		return objMessage;
	}


	/**
	 * Sends specified HL7BatchFile server system if Batch processor is supported. 
	 * @param objBatchFile instance of HL7 Batch file
	 * @return boolean - shows status as success or failure
	 * 						if sending operation performed successfully it returns true else false.
	 * @throws IOException Signals when IO error occurs. 
	 */
	public boolean sendBatchFile(IHL7BatchFile objBatchFile) throws IOException
	{
		try{
			// This Client Side Transaction handler will process the 
			// sending Batch File and keeps data for it.
			// internally it performs all validation for all Batches according to Mode set.
			if(objClientSideTransactionHandler!=null)
				objClientSideTransactionHandler.processBatchTransaction(objBatchFile);
			if(objBatchProcessor == null)
				throw new BatchException(ResourceBundleWrapper.getValue("exceptions","Batch_BatchNotSupported",new Locale(HL7Config.createInstance().getLocale())));
			if(objBatchFile == null)
				throw new BatchException(ResourceBundleWrapper.getValue("exceptions","Batch_BatchNotSupported",new Locale(HL7Config.createInstance().getLocale())));
			if(objBatchFile.validate())
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					StatusLogger.logBatchFile(objBatchFile);
				}

				synchronized(this)
				{
					objClientSideMessageAgent.sendBatch(objBatchFile);
					//for wait period of 0 or -ve values wait period is infinite.
					if(waitPeriod >  0)
						this.wait(waitPeriod);
					else 
						this.wait();
				}
				return true;
			}
		}
		catch (InvalidMessageException e) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(e);
			}
		}
		catch (BatchException e) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(e);
			}
		}
		catch (AuxillaryProtocolException ae) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(ae);
			}
		} 
		catch (InterruptedException e) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(e);
			}
		}
		return false;
	}

	/**
	 * This method parses HL7Batch from file located at given path if Batch Processor is supported.
	 * After parsing it sends the HL7Batch to server system.
	 * @param strFilePath specified path of File containing HL7Batch
	 * @return boolean - shows status as success or failure
	 * 						if sending operation performed successfully it returns true else false.
	 * @throws IOException Signals that an I/O exception has occurred. 
	 */
	public boolean sendBatchFile(String strFilePath) throws IOException
	{
		try 
		{
			if(objBatchProcessor == null)
				throw new BatchException(ResourceBundleWrapper.getValue("exceptions","Batch_BatchNotSupported",new Locale(HL7Config.createInstance().getLocale())));

			HL7Parser objHl7Parser = new HL7Parser();
			ISegmentSet objISegmentSet = objHl7Parser.parse(strFilePath);
			IHL7BatchFile objBatchFile = objBatchProcessor.createBatchFile(objMessageFactory, objISegmentSet);
			return sendBatchFile(objBatchFile);
		}
		catch(ParseException pe)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(pe);
			}
			return false;
		}
		catch (BatchException e) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(e);
			}
			return false;
		}
	}

	/**
	 * sets localextensionAgent on UserSession.
	 * LocalExtensionAgent is required for handling of locally extended messages.
	 * @param objLocalExtensionAgent Specified LocallyExtendedAgent to be set on UserSession.
	 */
	public void setLocalExtensionAgent(LocalExtensionAgent objLocalExtensionAgent)
	{
		if(objMessageFactory == null)
			objMessageFactory = MessageFactory.createInstance();
		objMessageFactory.setLocalExtensionAgent(objLocalExtensionAgent);
	}

	/**
	 * This function is automatically invoked when connection is closed by remote end.
	 */
	public void closeConnection() 
	{
		try 
		{
			close();
		} 
		catch (IOException e) 
		{
		}
	}


	/**
	 * Close.
	 * @return true, if successful
	 */
	public boolean close() throws IOException
	{
		// closing Agents
		if(objClientSideNetworkAgent!=null)
			objClientSideNetworkAgent.close();
		if(objClientSideMessageAgent!=null)
			objClientSideMessageAgent.close();		
		{
			// nullifying variables
			objIExceptionListener = null;
			objClientSideNetworkAgent = null;
			objClientSideMessageAgent = null;
			objMessageFactory = null;
			objIProcessHandler = null;
			objFragmentProcessor = null;
			objIMLLP = null;
			objBatchProcessor = null;
			objAuxAgent = null;
			objHL7Message = null;
			objACKResponseCollection=null;

			objHL7BatchResponseCollection = null;
		}

		if(HL7Config.createInstance().isLoggingOn())
		{
			HL7Logger objHL7Logger = HL7Logger.getLogger();
			objHL7Logger.log(ResourceBundleWrapper.getValue("status","ConnectionClosed",new Locale(HL7Config.createInstance().getLocale())));
		}
		return true;
	}

	/**
	 * Returns setting for Socket read timeout of this session, 
	 *  0 returns implies that the option is disabled (i.e., timeout of infinity).
	 * @return the setting for socket read timeout.
	 */
	public int getTimeOut() 
	{
		return itimeout;
	}

	/**
	 *  sets  Socket read timeout of this session, in
	 *  milliseconds.  With this option set to a non-zero timeout,
	 *  a read() call on the InputStream associated with this Socket
	 *  will block for only this amount of time.If the timeout expires,
	 *  a session is closed,The timeout must be > 0. A timeout of zero
	 *  is interpreted as an infinite timeout. 
	 * @param timeout the specified timeout, in milliseconds.
	 */
	public void setTimeOut(int itimeout)
	{
		this.itimeout = itimeout;
	}

	/**
	 * This method returns the Collection of Message by MessageControlId. 
	 * @return ISingleCollection<Message> Collection of message.
	 */
	public ISingleCollection<Message> getRecievedMessagesCollection(String strMessageControlID)
	{
		if(objACKResponseCollection.contains(strMessageControlID))
			return objACKResponseCollection.get(strMessageControlID);
		return null;
	}
	/**
	 * This will set the Query Cancellation agent on ClientSideMessageAgent
	 * @param objClientSideQCNAgent object of Client Side QCN Agent.
	 */
	public void setClientSideQCNAgent(IClientQueryCancellation objClientSideQCNAgent)
	{
		this.objClientSideQCNAgent = objClientSideQCNAgent;
	}
	//This method will be used to decide that After sending any Cancellation query or 
	//Any subscription message or Subscription cancellation message no waiting is required.
	private boolean isWaitingRequired(Message messageToSend)
	{
		if(messageToSend.getMessageCode().equals(EnumMessageCode.QCN.getValue()))
			return true;
		else if(messageToSend.contains(EnumSegments.HL7_SEG_RCP))
		{
			ISingleCollection<ISegment>  objRCPSegmentCollection = messageToSend.getSegment(EnumSegments.HL7_SEG_RCP);
			if(null != objRCPSegmentCollection.elementAt(0) && ((RCP)objRCPSegmentCollection.elementAt(0)).getModifyIndicator() != null)
			{
				if(("N".equals(((RCP)objRCPSegmentCollection.elementAt(0)).getModifyIndicator().getValue()) || "M".equals(((RCP)objRCPSegmentCollection.elementAt(0)).getModifyIndicator().getValue())))
					return true;
			}				
		}
		else if(messageToSend.contains(EnumSegments.HL7_SEG_QID) && EnumMessageCode.QSX == StandardMethods.getEnumMessageCode((String)messageToSend.getHeader().getMessageType().getMessageCode().getValue()))
			return true;

		return false;
	}
	//This method will be used to check that sending message is of library if not message  
	//is registered with local extension agent.
	private boolean isMessageSupported(Message messageToSend)
	{
		Message objMessage = objMessageFactory.createSendSupportedMessage(messageToSend.getMessageCode().toString(), messageToSend.getTriggerEvent().toString());
		if(messageToSend.getClass().getName() == objMessage.getClass().getName())
			return true;
		else if(objMessageFactory.getLocalExtensionAgent()!=null)
		{
			String strMsgQualifiedName = objMessageFactory.getLocalExtensionAgent().getMsgQualifiedName(messageToSend.getMessageCode().toString(), messageToSend.getTriggerEvent().toString());
			if(strMsgQualifiedName!=null)
				return strMsgQualifiedName.equals(objMessage.getClass().getName());
		}

		return false;
	}
}

