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

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.Message;
import cdac.medinfo.sdk.hl7282.baselibs.messages.MessageFactory;
import cdac.medinfo.sdk.hl7282.baselibs.parserserializer.HL7Serializer;
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.baselibs.segments.QID;
import cdac.medinfo.sdk.hl7282.baselibs.segments.QPD;
import cdac.medinfo.sdk.hl7282.baselibs.segments.RCP;
import cdac.medinfo.sdk.hl7282.baselibs.segments.SFT;
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.patientadministration.ADTRecepient;
import cdac.medinfo.sdk.hl7282.hl7model.messages.patientadministration.ADTSource;
import cdac.medinfo.sdk.hl7282.hl7model.messages.patientadministration.QBP_Q23;
import cdac.medinfo.sdk.hl7282.hl7model.messages.query.QCN_J01;
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.querycancel.ClientSideQCNAgent;
import cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.transactionhandler.TransactionHandler;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IClientQueryCancellation;
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.localextension.PopulateDataType;
import cdac.medinfo.sdk.hl7282.hl7net.localextension.Populate_Test;
import cdac.medinfo.sdk.hl7282.hl7net.upperlayer.IBatchReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.upperlayer.ICanceledResponseReceiver;
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.EnumTriggerEvent;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Constants;
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 which sends Different Query.(Specially meant for Cancellation query)
 * And receives the response related to it.
 */
public class QCNInitiatingSystem implements IExceptionListener,ICanceledResponseReceiver,IBatchReceiver
{
	UserSession objUserSession = null;
	int i = 1;
	int Count= 0,Response=0;
	String strcounter;
	int iMessageControlId =0;
	String strMessageControlID=null;
	IClientQueryCancellation objClientSideQCNAgent = null;
	//IBatchProcessor objIBatchProcessor = null;
	//MessageFactory objMessageFactory =null;
	private Populate_Test objPopulateTest = new Populate_Test();


	public static void main(String[] args) 
	{
		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:\\QCNLogs");

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

			//Initializing QCNInitiatingSystem 
			QCNInitiatingSystem objQCNClient = new QCNInitiatingSystem();
			objQCNClient.initiate(strIP,port);
		}
		catch(Exception ioe)
		{
			ioe.printStackTrace();
		}
	}
	/**
	 * Initiates this System as QCNInitiatingSystem.
	 * 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 
		{
			System.out.println("Creating user session for QCNInitiatingSystem");
			
			//Creating user session for QCNInitiatingSystem
			objUserSession  = new UserSession();

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

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


			//Patient Administration System 
			/*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);

			/*Instantiating Message Recipient for HL7System
			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 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);
			
			/*
			 * Instantiating IFragmentProcessor which handles the fragmentation and defragmention 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 IClientQueryCancellation which handles cancellation query.
			 * ClientSideQCNAgent is the default implementation provided by SDK for HL7.
			 * User can provide his own implementation for ClientSideQCNAgent by implementing IClientQueryCancellation interface.
			 */ 
			objClientSideQCNAgent = new ClientSideQCNAgent();

			//Setting Query Cancellation Agent on user session.
			objUserSession.setClientSideQCNAgent(objClientSideQCNAgent);
			
			/*
			 * 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);
			
			/*
			 * Setting the class implementing behavior of HL7System as listener in asynchronous mode,
			 * to process deferred queries.
			 * User can provide his own implementation by implementing interface IAsynReceiver
			 */
			objUserSession.addExceptionListener(this);

			//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 User Session should wait till it 
			//receives ACK/response for triggered event/Query.
			//if user not sets then by default set to 20 sec.
			objUserSession.setWaitPeriod(999999);

			//Setting time period for which User Session 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);

			//This will register the IInteractiveResponseReciever on user session.
			objUserSession.setICanceledResponseReceiver(this);

			//This will register the IBatchReceiver on user session.
			objUserSession.setIBatchReceiver(this);
			
			//Setting messageFactory on user session.
			objUserSession.setMessageFactory(objMessageFactory);

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

	
			/*
			 * Generation of cancellation query can be done by using agent in which it will take query id and
			 * return the segment set for canceling query by using this QCN_J10 can be made.
			 * ISegmentSet objISegmentSet = objClientSideQCNAgent.generateCancellationQuery("QBPQ23");
			 * This will generate the Cancellation query from the Segment set received from ClientSideQCNAgent.
			 * QCN_J01 objQCN_J01 = getCancellationQuery(objISegmentSet);
			 * 
			 */

			System.out.println("Creating and Sending First Query QBP^Q23.");
			objUserSession.sendMessage(getQBPQ23());
			Thread.sleep(6000);
			objUserSession.sendMessage(getCancelQBP_Q23());

			
		}
		catch (Exception e)
		{
			System.out.println("Error Ocured (Ending Client..)== > "+e.getMessage());
			e.printStackTrace();
		}
	}
	/**
	 * 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) 
	{
		synchronized (this)
		{
			this.notify();
		}
		System.out.println("In On Exception Receiced : Printing Stacktrace...");
		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 implementation of IProcesshandler interface.
	 * This method is invoked when SDK receives erroneous binary data.
	 * @param buffer Binary data for which exception is generated
	 * @param ex Type of Exception raised
	 */
	public void onException(Exception ex,ISegmentSet objISegmentSet) 
	{
		synchronized (this)
		{
			this.notify();
		}
		System.out.println("In On Exception Receiced : Printing Stacktrace...");
		System.out.println(ex.getMessage());
		System.out.println("End of Stacktrace....");
		try
		{
			ISingleCollection<ISegment>  objCollection = objISegmentSet.getList();
			for(ISegment objISegment : objCollection)
			{
				System.out.println(objISegment.getName());
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * This method is invoked when deferred response is received.
	 */
	public void onResponseReceive(Message deferredResponse)
	{

		System.out.println("Deferred Response Received for Query ............ !!!!!!!!!!!"+deferredResponse.getMessageStructure());

		String strFilePath = null;
		try
		{
			strFilePath = "E:\\Server" + i++ + ".hl7"; 
			
			HL7Serializer objHL7Serializer = new HL7Serializer();
			objHL7Serializer.serialize(deferredResponse.getSegmentSet(),strFilePath);
			System.out.print("Message serialized successfully\n");

			//After receiving deferredResponse user sends Acknowledgment in response to Deferred response.
			//generate Acknowledgment with respect to deferred response.
			ACK ackMessage = generateAcknowledgement(deferredResponse);

			//Send Acknowledgment for deferred response
			System.out.println("Send Acknowledgement for deferred response");
			objUserSession.sendMessage(ackMessage);

			synchronized (this) 
			{
				notify();	
			}
		}catch(IOException ioe)
		{
			ioe.printStackTrace();
		}
		catch (InvalidMessageException e) 
		{
			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().getValue().toString());
		objMSA.setExpectedSequenceNumber("1234567");
		ackMessage.setMessageAcknowledgmentSegment(objMSA);
		return ackMessage;
	}
	/**
	 * This method will invoked Automatically when canceled response is received.
	 */
	public void onResponseCancellationReceive(Message objMessage)
	{
		System.out.println("onResponseCancellationReceive recieved canceled response"+objMessage.getMessageStructure());
	}

	/**
	 * 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 method will return batch which is ACK batch for Response received. 
	 * @param objHL7Batch
	 */
	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.
			FHS objBatchFileHeader  = new FHS();
			objBatchFileHeader.setFieldSeparator('|');
			objBatchFileHeader.setEncodingChars('^','&','~','\\','#');
			objBatchFileHeader.setFileControlID(StandardMethods.generateUUID());

			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);


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

	/*
	 * This will generate the Query . 
	 * @return objQBP_Q23
	 */
	private QBP_Q23 getQBPQ23()
	{
		strcounter = "Q102"+Count++;
		strMessageControlID =  "MSGQ02"+iMessageControlId++;
		ADTSource objADTSource = new ADTSource();
		QBP_Q23 objQBP_Q23 =(QBP_Q23)objADTSource.createMessage(EnumMessageCode.QBP, EnumTriggerEvent.Q23);


		//MSH Segment
		MSH objMSH = objPopulateTest.populateMSH();
		objMSH.setMessageControlID(strMessageControlID);//ST
		objMSH.setMessageType("QBP","Q23","QBP_Q21");
		objQBP_Q23.setHeader(objMSH);

		QPD objQPD = objPopulateTest.populateQPD();
        objQPD.setQueryTag("Q1232");//ST
		objQBP_Q23.setQueryParameterDefinitionSegment(objQPD);

		//RCP segment
		RCP objRCP = objPopulateTest.populateRCP();
		objQBP_Q23.setResponseControlParametersSegment(objRCP);

		return objQBP_Q23;
	}

	/*
	 * This will generate the cancellation query for QBP_Q23.  
	 * @return objQCN_J01
	 */
	private QCN_J01 getCancelQBP_Q23()
	{
		strMessageControlID =  "MSGQ02"+iMessageControlId++;
		
		QrySource objQrySource = new QrySource();
		QCN_J01 objQCN_J01 = (QCN_J01)objQrySource.createMessage(EnumMessageCode.QCN, EnumTriggerEvent.J01);

		//Populate MSH Segment
		MSH objMSH = objPopulateTest.populateMSH();
		objMSH.setMessageType("QCN","J01","QCN_J01");
		objMSH.setMessageControlID(strMessageControlID);
		objQCN_J01.setHeader(objMSH);

		//Populate SFT segment
		SFT objSFT = objPopulateTest.populateSFT();
		objQCN_J01.addSoftwareSegment(objSFT);

		//Populate QID Segment
		QID objQID = objPopulateTest.populateQID();
		objQID.setQueryTag("Q1232");
		objQID.setMessageQueryName(PopulateDataType.populateCWE(HL7Constants.HL7_COMPONENT));
		objQCN_J01.setQueryIdentificationSegment(objQID);

		return objQCN_J01;
	}
}
