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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import cdac.medinfo.sdk.hl7282.hl7net.commons.IBufferReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBufferSender;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IDataProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IExceptionReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IFragmentProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IMLLP;
import cdac.medinfo.sdk.hl7282.hl7net.commons.INetworkAgent;
import cdac.medinfo.sdk.hl7282.baselibs.messages.LocalExtensionAgent;
import cdac.medinfo.sdk.hl7282.baselibs.parserserializer.HL7Parser;
import cdac.medinfo.sdk.hl7282.baselibs.parserserializer.HL7Serializer;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSH;
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.FragmentException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidMessageException;
import cdac.medinfo.sdk.hl7282.exceptions.MessageLengthLimitException;
import cdac.medinfo.sdk.hl7282.exceptions.ParseException;
import cdac.medinfo.sdk.hl7282.exceptions.ProcessFailedException;
import cdac.medinfo.sdk.hl7282.exceptions.SerializeException;
import cdac.medinfo.sdk.hl7282.hl7streams.HL7BufferStream;
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.SingleCollection;


/**
 * AuxAgent class deals with auxiliary protocols.
 * When no auxiliary protocol is supported 
 * then also it performs basic sending and receiving operation for messages.
 */
public class AuxAgent implements IBufferReceiver,IBufferSender   
{
	

	private IFragmentProcessor objIFragmentProcessor;

	private IMLLP objIMLLP;

	private MessageStreamSplitter objMessageStreamSplitter;

	private INetworkAgent objINetworkAgent;
	private IDataProcessor objIDataProcessor;

	private int iWriteMaxLength;

	private IExceptionReceiver objIExceptionReceiver;

	private HL7Parser objParser ;
	private HL7Serializer objSerializer;
	private LocalExtensionAgent objLocalExtensionAgent = null;

	/**
	 * Instantiates Aux agent
	 */
	public AuxAgent()
	{
		objMessageStreamSplitter = new MessageStreamSplitter();
		objParser = new HL7Parser();
		objSerializer = new HL7Serializer();
	}

	/**
	 * Sets LocalExtensionAgent to process locally extended segments
	 * @param objAgent instance of LocalExtensionAgent
	 */
	public void setLocalExtensionAgent(LocalExtensionAgent objAgent)
	{
		this.objLocalExtensionAgent = objAgent;
	}

	/**
	 * Retrieves instance of LocalExtensionAgent.
	 * @return LocalExtensionAgent - instance of LocalExtensionAgent
	 */
	public LocalExtensionAgent getLocalExtensionAgent()
	{
		return this.objLocalExtensionAgent;
	}

	/**
	 * Sets the IExceptionReceiver
	 * @param objIExceptionReceiver the objIExceptionReceiver to set
	 */
	public void setExceptionReceiver(IExceptionReceiver objIExceptionReceiver) 
	{
		this.objIExceptionReceiver = objIExceptionReceiver;
	}


	/**
	 * Retrieves the Fragment Processor
	 * @return IFragmentProcessor-the object of IFragmentProcessor
	 */
	public IFragmentProcessor getFragmentProcessor() 
	{
		return objIFragmentProcessor;
	}

	/**
	 * Sets the Fragment Processor
	 * @param objIFragmentProcessor the objIFragmentProcessor to set
	 */
	public void setFragmentProcessor(IFragmentProcessor objIFragmentProcessor) 
	{
		this.objIFragmentProcessor = objIFragmentProcessor;
	}

	/**
	 * Sets the MLLP Processor.
	 * @param objIMLLP the MLLP processor to set.
	 */
	public void setMLLPProcessor(IMLLP objIMLLP)
	{
		this.objIMLLP = objIMLLP;
	}

	/**
	 * Sets NetworkAgent on INetworkAgent.
	 * @param objINetworkAgent-object of INetworkAgent
	 */
	public void setNetworkAgent(INetworkAgent objINetworkAgent)
	{
		this.objINetworkAgent = objINetworkAgent;
	}

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

	/**
	 * Sets IDataProcessor on MessageAgenet.
	 * @param objIDataProcessor - object of IDataProcessor
	 */
	public void setDataProcessor(IDataProcessor objIDataProcessor)
	{
		this.objIDataProcessor = objIDataProcessor;
	}


	/**
	 * Gets automatically invoked when Message is received from remote system.
	 * @param buffer message stream received in form of byte array
	 * @return boolean - status indicating reception of Message buffer from remote end.
	 * @throws IOException  Signals that an IO Exception has occurred.  
	 */
	public boolean onMessageBufferReceived(byte[] buffer) throws IOException
	{
		try
		{
			if(objIMLLP != null)
			{
				buffer = objIMLLP.readMLLPComponent(buffer);
			}

			HL7BufferStream objHL7BufferStream = new HL7BufferStream();
			ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
			objHL7BufferStream.setInputStream(bis);
			HL7BufferStream objDefragmentStream = null;
			
			//Check weather HL7 Header is present or not.
			if(!checkHeader(buffer))
				return false;

			/*
			 * If FragmentProcessor is available then performing process of defragmentation. 
			 */
			if(objIFragmentProcessor != null)
				objDefragmentStream = objIFragmentProcessor.defragment(objHL7BufferStream);
			else
				objDefragmentStream = objHL7BufferStream;

			if(objLocalExtensionAgent != null)
				objParser.setLocalExtensionAgent(objLocalExtensionAgent);

			ISegmentSet objReceivedSegmentSet = objParser.parse(objDefragmentStream);
			ISingleCollection<ISegmentSet> objSegmentSetCollection = objMessageStreamSplitter.splitMessageBuffer(objReceivedSegmentSet);
			for(ISegmentSet objISegmentSet : objSegmentSetCollection)
			{
				try
				{
					objIDataProcessor.onDataReceived(objISegmentSet);
				}
				catch(ProcessFailedException ex)
				{
					if(HL7Config.createInstance().isLoggingOn())
					{
						HL7Logger objhl7Logger = HL7Logger.getLogger();
						objhl7Logger.log(ex.getMessage());
					}
					objIExceptionReceiver.onException(ex,objISegmentSet);
				}
				catch(BatchException ex)
				{
					if(HL7Config.createInstance().isLoggingOn())
					{
						HL7Logger objhl7Logger = HL7Logger.getLogger();
						objhl7Logger.log(ex.getMessage());
					}
					objIExceptionReceiver.onException(ex,objISegmentSet);
				}
			}
		}
		catch(FragmentException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(ex.getMessage());
			}
			objIExceptionReceiver.onException(ex, buffer);
		}
		catch(ParseException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(ex.getMessage());
			}
			objIExceptionReceiver.onException(ex, buffer);
		}
		return  true;
	}
	
	/**
	 * Checks whether buffer carries Message or Batch header or not.
	 * @param buffer message stream received in form of byte array
	 * @return boolean - status indicating reception of Message buffer from remote end.
	 */
	private boolean checkHeader(byte[] buffer)
	{
		HL7BufferStream objHL7Stream = new HL7BufferStream();
		ByteArrayInputStream tempInputStream = new ByteArrayInputStream(buffer);
		objHL7Stream.setInputStream(tempInputStream);
		
		StringBuffer objStringBuffer = new StringBuffer(3);
		objHL7Stream.mark(3);
		objHL7Stream.readString(objStringBuffer, 3);
		
		if(!(EnumSegments.HL7_SEG_MSH.getValue().equalsIgnoreCase(objStringBuffer.toString()) || EnumSegments.HL7_SEG_BHS.getValue().equalsIgnoreCase(objStringBuffer.toString()) || EnumSegments.HL7_SEG_FHS.getValue().equalsIgnoreCase(objStringBuffer.toString())))
		{
			objIExceptionReceiver.onException(new InvalidMessageException(), buffer);
			return false;
		}
		else
			return true;
	}

	/**
	 * Gets automatically invoked when Message is to be sent to  remote system.
	 * @param messageToSend Specified message
	 * @throws IOException Thrown when IO operation fails.
	 * @throws SerializeException Thrown when serialize operation fails.
	 */
	public void sendData(ISegmentSet objSegmentSetToSend) throws IOException
	{
		try
		{
			ISegment objISegment = objSegmentSetToSend.getSegment(0);
			ISingleCollection<HL7BufferStream> objBufferStreamCollection = new SingleCollection<HL7BufferStream>();

			HL7BufferStream objBufferStream = new HL7BufferStream();
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			objBufferStream.setOutputStream(bout);

			objSerializer.serialize(objSegmentSetToSend, objBufferStream);

			if(EnumSegments.HL7_SEG_MSH.getValue().equals(objISegment.getName().getValue()))
			{
				if(iWriteMaxLength != 0)
				{
					if(objIFragmentProcessor != null)
					{
						objBufferStreamCollection = objIFragmentProcessor.fragment(objBufferStream);
						
						HL7BufferStream objTempBufferStream = new HL7BufferStream();
						ByteArrayOutputStream bTempOut = new ByteArrayOutputStream();
						objTempBufferStream.setOutputStream(bTempOut);

						for(HL7BufferStream objHL7BufferStream : objBufferStreamCollection)
						{
							if(objIMLLP != null)
							{
								byte[] barrayBufferStream = objIMLLP.writeMLLPComponent(objHL7BufferStream.toByteArray());
								objTempBufferStream.writeBuffer(barrayBufferStream, 0, barrayBufferStream.length);
							}
							else
								concatStreams(objTempBufferStream, objHL7BufferStream);
						}

						objINetworkAgent.write(objTempBufferStream.toByteArray());
					}
					else
					{
						if(objIMLLP != null)
						{
							byte[] barrayBufferStream = objIMLLP.writeMLLPComponent(bout.toByteArray());
							if(barrayBufferStream.length <= iWriteMaxLength + 3)
								objINetworkAgent.write(barrayBufferStream);
							else
								throw new MessageLengthLimitException((String)((MSH)objISegment).getMessageType().getTriggerEvent().getValue());
						}
						else if(bout.toByteArray().length <= iWriteMaxLength)
							objINetworkAgent.write(bout.toByteArray());
						else
							throw new MessageLengthLimitException((String)((MSH)objISegment).getMessageType().getTriggerEvent().getValue());
					}
				}
				else
					objINetworkAgent.write(objBufferStream.toByteArray());
			}
			else
			{
				if(objIMLLP != null)
				{
					byte[] barrayBufferStream = objIMLLP.writeMLLPComponent(objBufferStream.toByteArray());
					objINetworkAgent.write(barrayBufferStream);
				}
				else
					objINetworkAgent.write(objBufferStream.toByteArray());
			}
		}
		catch (SerializeException se)
		{
			throw new ProcessFailedException(se.getMessage(),se);
		}
		catch (FragmentException fe)
		{
			throw new ProcessFailedException(fe.getMessage(),fe);
		}
	}

	/**
	 * Concatenates the two HL7BufferStreams into the first stream.
	 * @param objHL7BufferStream1  - HL7BufferStream to which other stream will be concatenated. 
	 * @param objHL7BufferStream2  - HL7BufferStream which will be concatenated to other stream . 
	 */
	private void concatStreams(HL7BufferStream objHL7BufferStream1 ,HL7BufferStream objHL7BufferStream2)
	{
		ByteArrayInputStream objByteArrayInputStream1 = new ByteArrayInputStream(objHL7BufferStream1.toByteArray());
		ByteArrayInputStream objByteArrayInputStream2 = new ByteArrayInputStream(objHL7BufferStream2.toByteArray());
		objHL7BufferStream1.setInputStream(objByteArrayInputStream1);
		objHL7BufferStream2.setInputStream(objByteArrayInputStream2);

		byte []arrbyte = new byte[objHL7BufferStream2.available()];
		if(objHL7BufferStream2.available()!=0)
		{
			objHL7BufferStream2.readBuffer(arrbyte);
			objHL7BufferStream1.writeBuffer(arrbyte, 0, arrbyte.length);
		}
	}

	/**
	 * Closes the Auxiliary Protocol Agent
	 */
	public void close()
	{		
		objIFragmentProcessor = null ;
		objIMLLP = null ;
		objMessageStreamSplitter = null ;
		objINetworkAgent = null ;
		objIDataProcessor = null ;
	}

}
