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



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.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.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.localextension.Populate_Test;
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.HL7Logger;
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 act as Simple HL7 Client which sends message to Central Archive using usersession.
 */
public class InitiatorSystem implements IExceptionListener
{
	UserSession objUserSession = null;
	String strMessageControlID=null,strQueryId =null;
	int iMessageControlId =0;
	Populate_Test objPopulate_Test = null;

	/**
	 * @param args
	 */
	public static void main(String[] args) 
	{
		try
		{
			InitiatorSystem objInitiatorSystem = new InitiatorSystem();

			//Reading IP address for InitiatorSystem
			System.out.print("Enter IP Adderess of HL7System Publisher Server is running: ");
			InputStreamReader input = new InputStreamReader(System.in);
			BufferedReader reader = new BufferedReader(input);
			String strIP = reader.readLine();

			//Reading port for HL7System Publisher
			System.out.print("Enter Port on which HL7System Publisher is listening : ");
			String strPort = reader.readLine();
			int port = Integer.parseInt(strPort);

			//Initiating InitiatorSystem Publisher at specified ip and port.
			objInitiatorSystem.initiate(strIP,port);

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

	/**
	 * Initiates this System as Client.
	 * Connects 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:\\Client_Logs");

						
			objPopulate_Test = new Populate_Test();

			//Creating User Session for Initiator system.
			objUserSession  = new UserSession();

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


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


			/* 
			 * As per the given systems requirement Un-Comment required source and 
			 * Recipient for sending and receiving for supported messages.  

		    Application management System 
			//Initiating Message Recipient for Application management System.
			IMessageRecepient objAppMgtRecepient = new AppMgtRecepient();

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

			//Initiating Message Source for Application management System.
			IMessageSource objAppMgtSource  = new AppMgtSource();

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


			   Clinical Laboratory Automation System 
			//Initiating Message Recipient for Clinical Laboratory Automation System.
			IMessageRecepient objClinicalLabAutoRecepient = new ClinicalLabAutoRecepient();

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

			//Initiating Message Source for Clinical Laboratory Automation System.
			IMessageSource objClinicalLabAutoSource  = new ClinicalLabAutoSource();

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


			   Financial Management System 
			//Initiating Message Recipient for Financial Management System.
			IMessageRecepient objFinancialRecepient = new FinancialRecepient();

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

			//Initiating Message Source for Financial Management System.
			IMessageSource objFinancialSource  = new FinancialSource();

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


			   Master Files System 
			//Initiating Message Recipient for Master Files System.
			IMessageRecepient objMasterFileRecepient = new MasterFileRecepient();

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

			//Initiating Message Source for Master Files System.
			IMessageSource objMasterFileSource  = new MasterFileSource();

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


			   Medical Records System 
			//Initiating Message Recipient for Medical Records System.
			IMessageRecepient objMediRecRecepient = new MediRecRecepient();

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

			//Initiating Message Source for Medical Records System.
			IMessageSource objMediRecSource  = new MediRecSource();

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


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


			  Order Entry System 
			//Initiating Message Recipient for Order Entry System.
			IMessageRecepient objOrderEntryRecepient = new OrderEntryRecepient();

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

			//Initiating Message Source for Order Entry System.
			IMessageSource objOrderEntrySource  = new OrderEntrySource();

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


			  Patient Care System 
			//Initiating Message Recipient for Patient Care System.
			IMessageRecepient objPatientCareRecepient = new PatientCareRecepient();

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

			//Initiating Message Source for Patient Care System.
			IMessageSource objPatientCareSource  = new PatientCareSource();

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


			  Patient Referral System 
			//Initiating Message Recipient for Patient Referral System.
			IMessageRecepient objPatientRefRecepient = new PatientRefRecepient();

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

			//Initiating Message Source for Patient Referral System.
			IMessageSource objPatientRefSource  = new PatientRefSource();

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


			  Personnel Management System 
			//Initiating Message Recipient for Personnel Management System.
			IMessageRecepient objPersonalMgmtRecepient = new PersonalMgmtRecepient();

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

			//Initiating Message Source for Personnel Management System.
			IMessageSource objPersonalMgmtSource  = new PersonalMgmtSource();

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


			  Scheduling System 
			//Initiating Message Recipient for Scheduling System.
			IMessageRecepient objSchedulingRecepient = new SchedulingRecepient();

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

			//Initiating Message Source for Scheduling System.
			IMessageSource objSchedulingSource  = new SchedulingSource();

			//Add MessageRecipient to MessageFactory.
			objMessageFactory.addMessageSource(objSchedulingSource);*/


			/*
			 * Instantiating IFragmentProcessor which handles the fragmentaion 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 user 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);
			
			/*
			 * 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 deffered queries.
			 * User can provide his own implementation by implementing interface IAsynReceiver
			 */
			objUserSession.addExceptionListener((IExceptionListener)this);


			//Set time for which user Session should wait for ACK/Response to arrive from remote end.
			//If time frame is elapsed then user session resends Event/Query message.
			objUserSession.setWaitPeriod(60000);
			
			/*
			 * Setting MessageFactory on User Session.  
			 */
			objUserSession.setMessageFactory(objMessageFactory);


			//Connecting User Session to specified Ip and port. 
			objUserSession.connect(strIP, port);


			System.out.println("Sending Message to HL7System_Publisher ..");
			System.out.println("Send ORU_R01 message to HL7System_Publisher :");
			InputStreamReader input1 = new InputStreamReader(System.in);
			BufferedReader reader1 = new BufferedReader(input1);
			String strMessage = reader1.readLine();
			objUserSession.sendMessage(strMessage);//"/home/sayali/dump/Testing/ORU_R01_Lab.hl7"

			//Check For ORU_R01 ACK is Received with mentioned MessageControlId.
			ISingleCollection<Message> ackCollection = objUserSession.getRecievedMessagesCollection("MSG0001");
			if(ackCollection != null)
			{
				for(Message ackMessage : ackCollection)
				{
					if(ackMessage.getMessageStructure().equals(EnumMessageStructure.ACK.getValue()))
						System.out.println("ACK Received for event " + " :: " + ackMessage.getTriggerEvent());
				}
			}
			else
				System.out.println("ACK Collection is empty");

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

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

		System.out.println("Deferred Response Received............ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		try 
		{

			//Afrer receiving deferredResponse user sends Acknowledgement in response to Deferred response.
			//generate Acknowledgement with respet to deferred response.
			ACK ackMessage = generateAcknowledgement(deferredResponse);
			//Send Acknowledgement for deferred response
			objUserSession.sendMessage(ackMessage);

			synchronized (this) 
			{
				notify();	
			}
		}
		catch (InvalidMessageException e) 
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(e);
			}			
		} 
	}

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

		ACK ackMessage = new ACK(StandardMethods.getEnumTriggerEvent(objReceivedMessage.getTriggerEvent()), objMessageMap);
		MSH objMSH = objPopulate_Test.populateMSH();
		objMSH.setMessageType(EnumMessageCode.ACK.getValue(), objReceivedMessage.getTriggerEvent(),EnumMessageStructure.ACK.getValue());
		objMSH.setMessageControlID(strMessageControlID);
		ackMessage.setHeader(objMSH);
		
		//MSA Segment
		MSA objMSA = new MSA();
		objMSA.setAcknowledgmentCode("AA");
		objMSA.setMessageControlID(objReceivedMessage.getHeader().getMessageControlID().getValue().toString());
		//objMSA.setTextMessage("This is "+ objReceivedMessage.getMessageStructure() +" event.");
		objMSA.setExpectedSequenceNumber("1234567");
		ackMessage.setMessageAcknowledgmentSegment(objMSA);
		return ackMessage;
	}

	/**
	 * This method simulates user action to generate Acknowledgement from message received in the batch.
	 * This method is demonstrated for test purpose only.
	 * In the actual scenerion,it is the responsibility of user to generates acknowledgement & 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 method is invoked when deferred response received is in Batch.
	 */
	public void onResponseReceive(IHL7Batch objHL7Batch) 
	{
		System.out.println("Deferred Response Received is Batch............ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		try 
		{

			//Afrer receiving deferredResponse user sends Acknowledgement in response to Deferred response.
			//generate Acknowledgement with respet 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();


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

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

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

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

			ISingleCollection<IHL7Batch> objIHL7BatchCollection = new SingleCollection<IHL7Batch>();
			//objIHL7BatchCollection.add(getADTEventBatch());
			objIHL7BatchCollection.add(objBatch);
			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)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(e);
			}			
		} 

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