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

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import cdac.medinfo.sdk.hl7282.baselibs.messagedictionary.MessageMapReader;
import cdac.medinfo.sdk.hl7282.baselibs.messages.ACK;
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.BHS;
import cdac.medinfo.sdk.hl7282.baselibs.segments.BTS;
import cdac.medinfo.sdk.hl7282.baselibs.segments.FHS;
import cdac.medinfo.sdk.hl7282.baselibs.segments.FTS;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSA;
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.InvalidMessageException;
import cdac.medinfo.sdk.hl7282.hl7model.messages.observationreporting.ObsvRepRecepient;
import cdac.medinfo.sdk.hl7282.hl7model.messages.observationreporting.ObsvRepSource;
import cdac.medinfo.sdk.hl7282.hl7model.messages.patientadministration.ADTRecepient;
import cdac.medinfo.sdk.hl7282.hl7model.messages.patientadministration.ADTSource;
import cdac.medinfo.sdk.hl7282.hl7model.messages.query.QryRecepient;
import cdac.medinfo.sdk.hl7282.hl7model.messages.query.QrySource;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.batch.BatchAgent;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.batch.HL7Batch;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.batch.HL7BatchFile;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.fragment.FragmentAgent;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.transactionhandler.TransactionHandler;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
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.ITransactionHandler;
import cdac.medinfo.sdk.hl7282.hl7net.upperlayer.IBatchReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.upperlayer.IExceptionListener;
import cdac.medinfo.sdk.hl7282.hl7net.upperlayer.UserSession;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageCode;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageStructure;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.IMap;
import cdac.medinfo.sdk.hl7282.utils.IMessageMapReader;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;

/**
 * This class acts as initiating system for sending locally extended message.
 */
public class HL7System_LocalExtensionClient implements IExceptionListener,IBatchReceiver
{
	UserSession objUserSession = null;
	int i = 1;
	String strMessageControlID=null;
	int iMessageControlId =0;
	MessageFactory objMessageFactory =null;
	private Populate_Test objPopulateTest = null;

	public static void main(String[] args) 
	{
		try
		{

			//Reading IP address for HL7System LocalExtensionClient
			System.out.print("Enter IP Adderess of HL7System_LocalExtensionClient: ");
			InputStreamReader input = new InputStreamReader(System.in);
			BufferedReader reader = new BufferedReader(input);
			String strIP = reader.readLine();
			//Reading port for HL7System LocalExtensionClient
			System.out.print("Enter Port on which HL7System_LocalExtensionClient is listening : ");
			String strPort = reader.readLine();
			int port = Integer.parseInt(strPort);

			//Initializing HL7System LocalExtensionClient 
			HL7System_LocalExtensionClient objQueryQIC_QCNInitiator = new HL7System_LocalExtensionClient();
			objQueryQIC_QCNInitiator.initiate(strIP,port);
		}
		catch(Exception ioe)
		{
			ioe.printStackTrace();
		}
	}

	/**
	 * Initiates this System as HL7System LocalExtensionClient.
	 * Connects the Client to Server on specific IP Address & port on which Server System is listening.
	 * @param strIP  IP Address specified
	 * @param port port specified 
	 */
	public void initiate(String strIP,int port)
	{
		try 
		{
			/*
			 * Specifying logging directory.
			 * User can specify any directory where log files will be created.
			 * If no path is specified then logs will be generated in user's temp directory. 
			 */
			HL7Config.createInstance().enableLogging("E:\\LocalExtensionLogs");

			
			objPopulateTest = new Populate_Test();

			//Creating UserSession for HL7System LocalExtensionClient.
			objUserSession = new UserSession();

			//Create MessageFactory to provide support for creation and population of messages.
			objMessageFactory = MessageFactory.createInstance();


			/*   Patient Administration System */
			/*
			 * Instantiating Message Recipient for HL7System LocalExtensionClient
			 * User can provide his own implementation of Message Recipient by implementing IMessageReceipient interface. 
			 */
			IMessageRecepient objADTRecepient = new ADTRecepient();

			/*
			 * Adding Patient Admin Recipient on Message Factory.
			 * By setting this recipient client becomes capable to create messages and send Events of Patient Admin System
			 * Similarly user can set recipients for different systems.
			 * sdk for HL7 provides sources for all systems specified by HL7.
			 */ 
			objMessageFactory.addMessageRecepient(objADTRecepient);

			/*
			 * Instantiating Message Source for HL7System
			 * User can provide his own implementation of Message Source by implementing IMessageSource interface. 
			 */
			IMessageSource objADTSource  = new ADTSource();

			/*
			 * Adding Patient Admin Source and Recipient on on Message Factory.
			 * By setting this source client becomes capable to create messages and receive responses of Patient Admin System
			 * Similarly user can set sources for different systems.
			 * sdk for HL7 provides sources for all systems specified by HL7.
			 */ 
			objMessageFactory.addMessageSource(objADTSource);


			/* Observation Reporting System */ 
			//Initiating Message Recipient for Observation Reporting System.
			IMessageRecepient objObsvRepRecepient = new ObsvRepRecepient();

			//Add the Message Recipient in MessageFactory.
			objMessageFactory.addMessageRecepient(objObsvRepRecepient);

			//Initiating Message Source for Observation Reporting System.
			IMessageSource objObsvRepSource  = new ObsvRepSource();

			//Add MessageRecipient to MessageFactory.
			objMessageFactory.addMessageSource(objObsvRepSource);

			/*   Query System */
			//Initiating Message Recipient for Query System.
			IMessageRecepient objQryRecepient = new QryRecepient();

			//Add the Message Recipient in MessageFactory.
			objMessageFactory.addMessageRecepient(objQryRecepient);

			//Initiating Message Source for Query System.
			IMessageSource objQrySource  = new QrySource();

			//Add MessageRecipient to MessageFactory.
			objMessageFactory.addMessageSource(objQrySource);


			/*
			 * Instantiating IFragmentProcessor which handles the fragmentation and defragmentation for larger messages.
			 * FragmentAgent is the default implementation provided by sdk for HL7.
			 * User can provide his own implementation for FragmentProcessor by implementing IFragmentProcessor interface.
			 */ 
			IFragmentProcessor objIFragmentProcessor = new FragmentAgent();

			//Setting Fragment processor on server session.
			objUserSession.setFragmentProcessor(objIFragmentProcessor);

			/*
			 * Instantiating IBatchProcessor which handles HL7Batch and HL7BatchFiles.
			 * BatchAgent is the default implementation provided by SDK for HL7.
			 * User can provide his own implementation for BatchProcessor by implementing IBatchProcessor interface.
			 */
			IBatchProcessor objIBatchProcessor = new BatchAgent();

			//Setting Batch Processor on user session
			objUserSession.setBatchProcessor(objIBatchProcessor);
			
			objUserSession.addExceptionListener((IExceptionListener)this);
			
			/*
			 * Instantiating TransactionHandler which keeps track for the sending and receiving message 
			 * and does verification of each message during transaction.
			 * User can provide his own implementation for ClientSideTransactionHandler by implementing ITransactionHandler interface.
			 * if user wants verification for each message within transaction then user has to set it else not.
			 */
			ITransactionHandler objClientSideTransactionHandler = new TransactionHandler();
			
			//Setting this transaction handler on User session.
			objUserSession.setClientSideTransactionHandler(objClientSideTransactionHandler);

			/*
			 * Instantiating LocalExtensionAgent which handles LocalExtensionAgent 
			 * works as a factory for locally extended messages and segments.
			 */ 
			LocalExtensionAgent objLocalExtensionAgent = new LocalExtensionAgent();

			/*
			 * Register the ADT_A99 locally extended message by providing Message code,trigger event and qualified name.
			 */
			objLocalExtensionAgent.registerMessage("ADT","A99","cdac.medinfo.sdk.hl7282.hl7net.testnetwork.localmessages.ADT_A99");

			/*
			 * Register the ZL7 Segment.
			 */
			objLocalExtensionAgent.registerSegment("ZL7","cdac.medinfo.sdk.hl7282.hl7net.testnetwork.localmessages.ZL7");

			/*
			 * Setting this local extension agent on MessageFacotry.  
			 */
			objMessageFactory.setLocalExtensionAgent(objLocalExtensionAgent);

			//Set the reading and writing max length for this client which will be used to decide whether to fragment or not.
			objUserSession.setReadWriteLength(1024,1024);

			//Sets the wait period for which each UserSession should wait till it 
			//receives ACK/response for triggered event/Query.
			//if user not sets then by default set to 20 sec.
			objUserSession.setWaitPeriod(6000);

			//Setting time period for which UserSession will read on socket 
			//for given time period in milliseconds. 
			//The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. 
			objUserSession.setTimeOut(0);

			//Connecting this client with HL7System LocalExtensionClient
			objUserSession.connect(strIP, port);

			InputStreamReader input = new InputStreamReader(System.in);
			BufferedReader reader = new BufferedReader(input);
			
			System.out.print("\n\n\nEnter File having locally extended message to send :");
			String strfilepath = reader.readLine();
			/*
			 * Sending locally extended message like ADT_A99.
			 */
			
			objUserSession.sendMessage(strfilepath);

			ISingleCollection<Message> objCollection = objUserSession.getRecievedMessagesCollection("MSG0001");
			if(objCollection!=null)
			{
				for(Message  objMessage :objCollection )
				{
					System.out.println("ACK REceived for Event : "+objMessage.getTriggerEvent());
				}
			}

		}
		catch (Exception e)
		{
			System.out.println("Error Ocured (Ending Client..)== > "+e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * This method simulates user action to generate Acknowledgment from message received.
	 * This method is demonstrated for test purpose only.
	 * In the actual scenario,it is the responsibility of user to generates acknowledgment & sends it on remote end. 
	 * @param objReceivedMessage specified Message object
	 * @return ACK message object
	 */
	public ACK generateAcknowledgement(Message objReceivedMessage)
	{
		IMessageMapReader objMessageMapReader = MessageMapReader.createInstance();
		IMap objMessageMap = objMessageMapReader.getMessageMap(EnumMessageCode.ACK, null);

		ACK ackMessage = new ACK(StandardMethods.getEnumTriggerEvent(objReceivedMessage.getTriggerEvent()), objMessageMap);
		MSH objMSH = objPopulateTest.populateMSH();
		objMSH.setMessageType(EnumMessageCode.ACK.getValue(), objReceivedMessage.getTriggerEvent(),EnumMessageStructure.ACK.getValue());
		objMSH.setMessageControlID(objReceivedMessage.getHeader().getMessageControlID());
		ackMessage.setHeader(objMSH);
		
		//MSA Segment
		MSA objMSA = new MSA();
		objMSA.setAcknowledgmentCode("AA");
		objMSA.setMessageControlID(objReceivedMessage.getHeader().getMessageControlID());
		objMSA.setExpectedSequenceNumber("1234567");
		ackMessage.setMessageAcknowledgmentSegment(objMSA);
		return ackMessage;
	}
	/**
	 * This method is invoked when exception is fetched from remote end.
	 * @ex Exception fetched
	 * @buffer for which exception is received</param>
	 */
	public void onException(Exception ex,byte[] buffer) 
	{
		System.out.println("In On Exception Receiced : Printing Stacktrace...");
		//ex.printStackTrace();
		System.out.println(ex.getMessage());
		System.out.println("End of Stacktrace....");
		try
		{
			System.out.println("Byte Buffer is : ");
			ByteArrayOutputStream objByteArrayOutputStream = new ByteArrayOutputStream();
			objByteArrayOutputStream.write(buffer);

			objByteArrayOutputStream.writeTo(System.out);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	/**
	 * This method is invoked when exception is fetched from remote end.
	 * @ex Exception fetched
	 * @objISegmentSet for which Segment Set is Received.
	 */
	public void onException(Exception ex, ISegmentSet objISegmentSet)
	{
		System.out.println("In On Exception Receiced : Printing Stacktrace...");
		System.out.println(ex.getMessage());
		System.out.println("End of Stacktrace....");
		try
		{
			System.out.println("Segment Set is : ");
			ISingleCollection<ISegment> objISegmentSetCollection = objISegmentSet.getList();
			if(objISegmentSetCollection!=null)
			{
				System.out.println("Received Segment Set Contains following Segments:");
				for(ISegment objISegment : objISegmentSetCollection)
				{
					System.out.println(objISegment.getName());
				}
			}

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * This method is invoked when deferred response received is in Batch.
	 */
	public void onBatchReceive(IHL7BatchFile objBatchFile) 
	{
		ISingleCollection<IHL7Batch> objBatchCollection = objBatchFile.getHL7BatchCollection();
		for(IHL7Batch objBatch : objBatchCollection)
		{
			if(objBatch.isResponseBatch()  && objBatch.isDeferred())
			{
				acknowledgeResponseBatch(objBatch);
			}
			else
				System.out.println("ACK received for BatchID  " + objBatch.getBatchControlID());
		}
	}

	//This will generate the Batch which will acknowledge the Response received. 
	private void acknowledgeResponseBatch(IHL7Batch objHL7Batch)
	{
		System.out.println("Deferred Response Received is Batch............ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		try 
		{

			//After receiving deferredResponse user sends Acknowledgment in response to Deferred response.
			//generate Acknowledgment with respect to deferred response.

			BHS objBatchHeader  = new BHS();
			objBatchHeader.setBatchFieldSeparator('|');
			objBatchHeader.setBatchEncodingChars('^','&','~','\\','#');
			objBatchHeader.setBatchControlID(objHL7Batch.getBatchControlID());//Generate it using UUID jar.

			ISingleCollection<Message> objMessageCollection = generateAcknowledgement(objHL7Batch.getMessageCollection());

			int iMessageCount = 0;
			if(objMessageCollection != null )
				iMessageCount = objMessageCollection.size();

			BTS objBatchTrailer = new BTS();
			objBatchTrailer.addBatchTotals(""+iMessageCount);

			IHL7Batch objBatch = new HL7Batch(objBatchHeader,objBatchTrailer,objMessageCollection);

			ISingleCollection<IHL7Batch> objIHL7BatchCollection = new SingleCollection<IHL7Batch>();
			objIHL7BatchCollection.add(objBatch);

			FHS objBatchFileHeader  = new FHS();
			objBatchFileHeader.setFieldSeparator('|');
			objBatchFileHeader.setEncodingChars('^','&','~','\\','#');
			objBatchFileHeader.setFileControlID(StandardMethods.generateUUID());

			FTS objBatchFileTrailer = new FTS();
			objBatchFileTrailer.setFileBatchCount("1");

			IHL7BatchFile objBatchFile = new HL7BatchFile(objBatchFileHeader,objBatchFileTrailer,objIHL7BatchCollection);

			//Send Acknowledgment for deferred response
			System.out.println("Sending ACK for Deferred Response Batch..");

			objUserSession.sendBatchFile(objBatchFile);

			System.out.println("ACK for Deferred Response Batch is Sent ..");
			synchronized (this) 
			{
				notify();	
			}
		}
		catch (InvalidMessageException e) 
		{
			e.printStackTrace();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * This method simulates user action to generate Acknowledgment from message received in the batch.
	 * This method is demonstrated for test purpose only.
	 * In the actual scenario,it is the responsibility of user to generates acknowledgment & sends it on remote end. 
	 * @param objMessageCollection specified Message collection from 
	 * @return objACKCollection - collection of ACK messages.
	 */
	public ISingleCollection<Message> generateAcknowledgement(ISingleCollection<Message> objMessageCollection)
	{
		ISingleCollection<Message> objACKCollection = new SingleCollection<Message>();
		for(Message objMessage :objMessageCollection)
		{
			objACKCollection.add(generateAcknowledgement(objMessage));
		}
		return objACKCollection;
	}
}
