/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.dcm30_04.services.cservices.testcases.scu;


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


import cdac.medinfo.sdk.dcm30_04.baselibs.DataSet;
import cdac.medinfo.sdk.dcm30_04.baselibs.ImplicitDE;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDictionaryReader;
import cdac.medinfo.sdk.dcm30_04.dicommodel.xray.XRayAngiographicIOD;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.enums.EnumSOPClassUIDs;

import cdac.medinfo.sdk.dcm30_04.services.association.AAbort;
import cdac.medinfo.sdk.dcm30_04.services.association.AReleaseRQorRP;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociateRQ;
import cdac.medinfo.sdk.dcm30_04.services.association.DefaultAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.RQPresContext;
import cdac.medinfo.sdk.dcm30_04.services.cfind.commons.CFindRQ;
import cdac.medinfo.sdk.dcm30_04.services.cfind.commons.CFindRSP;
import cdac.medinfo.sdk.dcm30_04.services.cfind.scu.CFindSCU;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRQ;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRSP;
import cdac.medinfo.sdk.dcm30_04.services.cmove.scu.CMoveSCU;
import cdac.medinfo.sdk.dcm30_04.services.cstore.commons.CStoreRQ;
import cdac.medinfo.sdk.dcm30_04.services.cstore.commons.CStoreRSP;
import cdac.medinfo.sdk.dcm30_04.services.cstore.scu.CStoreSCU;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
import cdac.medinfo.sdk.dcm30_04.services.scu.commons.SCUSession;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;

import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;

/**
 * This class is implemented to test Multiple SCU.
 * In order to run the following class , please refer to this link($Link) to create the environment.
 * To implement an SCU of a particular we need to do the following things:
 * Create the SCUSession object which maintains the current session of the SCU. Its "start" method accepts
 * the SCP's IP Address and Port and tries to establish a connection with the SCP. 
 * 
 * Next step is to establish association .Some ways to create an Association Request: 
 * 1)Use Default Association class to Load the association for requested service. User can load the association 
 * by specifying the Service Type in loadAssociation method of Default association. For e.g. here EnumServiceType.CFind
 * should be specified to load Association for C-Find Services. The association request for C-Find
 * is made according to  jars in the class path.
 * 2) Manually create object of AssociationRQ class and set all presentation contexts collection on it. 
 * More details provided in the API DOCS of AssociationRQ class.
 * 
 * After successful association is established the SCUSession stores the status of the Association with it.
 *   
 * Create SCU object to send the Request to the respective SCP. Currently the "sendMessage" method 
 * of the SCU would wait after sending the RQ until a suitable DIMSE response is received. 
 * This operation is part of Synchronous mode request handling.  
 * Once the DIMSE response is received by the SCU it stores it into its collection
 * which then can be retrieved.
 * 
 * After this, create a A-Release-RQ in order to request for association release and send it using the
 * SCUSession object. This again is a blocking call and the SCUSession waits until a A-Release-RP is received.
 * At the end of an operation calling "close" on SCUSession would disconnect the connection made with the 
 * SCP and release all the acquired resources.
 * 
 * Instead of closing the session after a service operation is complete,we can create another association
 * and send more requests. 
 * Also it is possible to create multiple RQs and send them one by one on the same Association.
 */
public class MultipleServices_SCU
{
	/**
	 * The main method.
	 * @param args arguments.
	 * @throws IOException Signals if IO Exception occurs.
	 */
	public static void main(String[] args)
	{
		DicomConfig objDicomConfig = DicomConfig.createInstance();

		//enable logging,specify the path of the directory where log files are created.
		//User can specify any directory in place of 'c:\DCM_Logs'.
		objDicomConfig.enableLogging(EnumLoggingLevel.INFO, "C:\\DCM_Logs");
		/*
		 * Creates MultipleServices_SCU.
		 */
		MultipleServices_SCU objTestMultipleServicesSCU = new MultipleServices_SCU();
		objTestMultipleServicesSCU.start();
	}

	/**
	 *	Start Test Multiple Services SCU.
	 */
	public void start()
	{
		SCUSession objSession =null;

		try
		{
			//Enter the X-Ray Angiogram DICOM File with full path
			String strFilePath = null;
			System.out.println("Enter the X-Ray Angiogram Dicom File with complete path");
			BufferedReader b_reader =new BufferedReader( new InputStreamReader( System.in ) );
			strFilePath = b_reader.readLine();

			// Creates the object of corresponding IOD by calling its constructor which takes File Name as a parameter and give its DataSet.
			XRayAngiographicIOD objXRayAngiographicIOD = new XRayAngiographicIOD(strFilePath);
			IDataSet objIDataSet = objXRayAngiographicIOD.getDataSet();

			//creates SCU session.
			objSession = new SCUSession();
			
			/*
			 * Setting time out as 5000 milli second. It will wait 5000 mili seconds for read and write operation.
			 * If in the time specified (5000 ms) read or write operation is not performed then 
			 * session is time out.
			 */ 
			//objSession.setTimeOut(5000);

			//get RQ Presentation Context
			IPairedCollection<Byte,RQPresContext> objRQCollection = getPresentationContextRQ(); 

			RQPresContext objRQPresContextXRay = new RQPresContext();
			objRQPresContextXRay.setAbstractSyntaxName(EnumSOPClassUIDs.XRAYANGIOGRAPHICIOD.getValue());
			objRQPresContextXRay.addTransferSyntax(DicomConstants.TS_LOSSY_JPEG_8_BIT);

			RQPresContext objRQPresContextCFIND = new RQPresContext();
			objRQPresContextCFIND.setAbstractSyntaxName(DicomConstants.DCM_STUDY_ROOT_QUERY_RETRIEVE_INFORMATION_MODEL_FIND);
			objRQPresContextCFIND.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);

			RQPresContext objRQPresContextCMOVE = new RQPresContext();
			objRQPresContextCMOVE.setAbstractSyntaxName(DicomConstants.DCM_STUDY_ROOT_QUERY_OR_RETRIVE_INFORMATION_MODEL_MOVE);
			objRQPresContextCMOVE.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);

			//RQ Pres Context for CT
			RQPresContext objRQPresContextCT = new RQPresContext();
			objRQPresContextCT.setAbstractSyntaxName(EnumSOPClassUIDs.CTIOD.getValue());
			objRQPresContextCT.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);

			//RQ Pres Context for MR
			RQPresContext objRQPresContextMR = new RQPresContext();
			objRQPresContextMR.setAbstractSyntaxName(EnumSOPClassUIDs.MRIOD.getValue());
			objRQPresContextMR.addTransferSyntax(DicomConstants.TS_JPEG_LOSSLESS_NON_HIERARCHICAL_PROCESS_14);

			RQPresContext objRQPresContextCGET = new RQPresContext();
			objRQPresContextCGET.setAbstractSyntaxName(EnumSOPClassUIDs.PATIENTROOTQUERYGET.getValue());
			objRQPresContextCGET.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);

			ISingleCollection<RQPresContext> objPressConColl = new SingleCollection<RQPresContext>();
			objPressConColl.add(objRQPresContextXRay);
			objPressConColl.add(objRQPresContextCT);
			objPressConColl.add(objRQPresContextMR);
			objPressConColl.add(objRQPresContextCGET);
			objPressConColl.add(objRQPresContextCFIND);
			objPressConColl.add(objRQPresContextCMOVE);

			DefaultAssociation objDefaultAssociation = new DefaultAssociation("SDK");
			objDefaultAssociation.loadAssociation(objPressConColl);

			//get Association request
			AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ("SDK");

			//set Presentation Context Collection in AssociateRQ.
			objAssociateRQ.setRQPresContextCollection(objRQCollection);

			//Start SCU Session by providing the IP address and Port of SCP.
			objSession.start("192.168.8.94", 9000);

			//Send the Association Request
			if(objSession.sendAssociationMessage(objAssociateRQ))
			{	
				System.out.println("Association established");

				/*
				 * To send multiple requests for CStore use while loop
				 */
				//IDataSet objIDataSet = parse();

				//Create CStore SCU object using the current SCU Session.
				CStoreSCU objCStoreSCU = new CStoreSCU(objSession);

				//Create Default CStore RQ by giving the parsed DataSet. This method uses
				// default values for the attributes in the request. These attributes can be
				//modified once the request object is created, before sending.
				CStoreRQ objCStoreRQ = CStoreRQ.getDefaultRQ(objIDataSet);
				System.out.println("CStore Request initialized");

				//Send the C-Store-RQ and wait for a C-Store-RSP.
				//This method exits once the response is received. This response is 
				//added in the response collection maintained by the CStoreSCU which
				//can be retrieved. This method return true if request is successfully sent 
				//to the SCP and false if the message cannot be sent to the SCP( for e.g. if 
				//the association status is false or if SOP class UID is not agreed in agreed association.
				if(objCStoreSCU.sendMessage(objCStoreRQ))
				{
					System.out.println("CStore Operation performed");
					CStoreRSP objCStoreRSP = new CStoreRSP(objCStoreSCU.getDIMSEResponse().get(0));
					System.out.println("C-Store Response Status :" + objCStoreRSP.getStatus());
				}
				else
					System.out.println("CStore Operation failed");

				//Create CFind SCU object using the current SCU Session.
				CFindSCU objCFindSCU = new CFindSCU(objSession); 

				//get the DataSet.
				IDataSet objIDataSet1 = getDataSet1();

				//Create Default CFind RQ by giving the parsed DataSet. This method uses
				//default values for the attributes in the request. These attributes can be
				//modified once the request object is created, before sending.
				CFindRQ objCFindRQ1 = CFindRQ.getDefaultRQ(DicomConstants.DCM_STUDY_ROOT_QUERY_RETRIEVE_INFORMATION_MODEL_FIND, objIDataSet1);

				//get the DataSet.
				IDataSet objIDataSet2 = getDataSet2();

				System.out.println("C-Find Request Initialized");

				//Create Default CFind RQ by giving the parsed DataSet. This method uses
				//default values for the attributes in the request. These attributes can be
				//modified once the request object is created, before sending.
				CFindRQ objCFindRQ2 = CFindRQ.getDefaultRQ(EnumSOPClassUIDs.PATIENTROOTQUERYFIND.getValue(), objIDataSet2);

				//Send the C-Find-RQ and wait for a C-Find-RSP.
				//This method exits once the response is received. This response is 
				//added in the response collection maintained by the CFindSCU which
				//can be retrieved. This method return true if request is successfully sent 
				//to the SCP and false if the message cannot be sent to the SCP( for e.g. if 
				//the association status is false or if SOP class UID is not agreed in agreed association.
				if(objCFindSCU.sendMessage(objCFindRQ1))
				{
					System.out.println("CFind Operation performed");
					for(DicomMessage objDicomMessage : objCFindSCU.getDIMSEResponse())
					{
						CFindRSP objCFindRSP = new CFindRSP(objDicomMessage);
						System.out.println("C-Find Response Status :" + objCFindRSP.getStatus());
					}
				}
				else
					System.out.println("CFind Operation failed");

				System.out.println("C-Find Request Initialized");

				if(objCFindSCU.sendMessage(objCFindRQ2))
				{
					System.out.println("CFind Operation performed");
					for(DicomMessage objDicomMessage : objCFindSCU.getDIMSEResponse())
					{
						CFindRSP objCFindRSP = new CFindRSP(objDicomMessage);
						System.out.println("C-Find Response Status :" + objCFindRSP.getStatus());
					}
				}
				else
					System.out.println("CFind Operation failed");

				//Create CMove SCU object using the current SCU Session.
				CMoveSCU objCMoveSCU = new CMoveSCU(objSession);

				//Create DataSet for C-Move Operation
				objIDataSet = new DataSet();
				IDictionaryReader objIDictionaryReader = objIDataSet.getDictionaryReader();
				//Sending StudyInstanceUID for query
				IDataElement objIDataElement = new ImplicitDE(0x0020,0x000D,"1.3.12.2.1107.5.4.3.4975316777216.19951114.94101.16",objIDictionaryReader);//Abdo.dcm
				objIDataSet.addDataElement(objIDataElement);

				//Create Default CMove RQ by giving the parsed DataSet. This method uses
				//default values for the attributes in the request. These attributes can be
				//modified once the request object is created, before sending.
				CMoveRQ objCMoveRQ = CMoveRQ.getDefaultRQ(DicomConstants.DCM_STUDY_ROOT_QUERY_OR_RETRIVE_INFORMATION_MODEL_MOVE, "DCMRCV", objIDataSet);//Abdo.dcm
				System.out.println("CMove Request Initialized");


				//Send the C-Move-RQ and wait for a C-Move-RSP.
				//This method exits once the response is received. This response is 
				//added in the response collection maintained by the CFindSCU which
				//can be retrieved. This method return true if request is successfully sent 
				//to the SCP and false if the message cannot be sent to the SCP( for e.g. if 
				//the association status is false or if SOP class UID is not agreed in agreed association.
				if(objCMoveSCU.sendMessage(objCMoveRQ))
				{
					System.out.println("C-Move Operation performed");
					for(DicomMessage objDicomMessage : objCMoveSCU.getDIMSEResponse())
					{
						CMoveRSP objCMoveRSP = new CMoveRSP(objDicomMessage);
						System.out.println("C-Move Response Status :" + objCMoveRSP.getStatus());
					}
				}
				else
					System.out.println("C-Move operation failed.");

				//Create Release Request.
				AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);

				//Send Release Request.
				if(objSession.sendAssociationMessage(objAReleaseRQorRP))
				{
					//When successful A-Release-RP is received, close the Session.
					//Please note that instead of closing the session we can create 
					//another association and send more requests. Also it is possible
					//to create multiple C-Store-RQs and send them one by one.
					objSession.close();
				}

				//In case an ABORT is received from SCP during the C-Store operation
				if(objSession.getDIMSEAssociation() instanceof AAbort)
				{
					System.out.println("Abort arrives. Closing session.");
					objSession.close();
				}
			}
			else
			{
				System.out.println("Association cannot to extablished");
				objSession.close();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			objSession.close();
		}
	}


	/*
	 * This method gets the data set to be sent in C-Find Request.
	 * @return IDataSet Data Set to be set in C-Find Request.
	 */
	private IDataSet getDataSet1()
	{
		IDataSet objIDataSet = new DataSet();
		IDictionaryReader objIDictionaryReader = objIDataSet.getDictionaryReader();
		IDataElement objIDataElement = new ImplicitDE(0x0010,0x0020,"WITHHELD",objIDictionaryReader);//Abdo.dcm - study Date
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x1208,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0030,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0050,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0052,"PATIENT",objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x000d,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x0010,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x1206,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		return objIDataSet;
	}

	/**
	 * This method gets the data set to be sent in C-Find Request.
	 * @return IDataSet Data Set to be set in C-Find Request.
	 */
	private IDataSet getDataSet2()
	{
		IDataSet objIDataSet = new DataSet();
		IDictionaryReader objIDictionaryReader = objIDataSet.getDictionaryReader();
		IDataElement objIDataElement = new ImplicitDE(0x0008,0x0020,"19930325",objIDictionaryReader);//0003.dcm - Study Date
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x1208,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0030,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0050,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0008,0x0052,"STUDY",objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x000d,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x0010,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		objIDataElement = new ImplicitDE(0x0020,0x1206,null,objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
		return objIDataSet;
	}

	/**
	 * Creates RQ presentation context collection which will be send in association request.
	 * @return ISingleCollection<RQPresContext> collection of RQ Presentation context.
	 */
	private IPairedCollection<Byte,RQPresContext> getPresentationContextRQ()
	{
		IPairedCollection<Byte,RQPresContext> objRQPCollection = new PairedCollection<Byte, RQPresContext>();

		ISingleCollection<String> objITransferSyntax = new SingleCollection<String>();
		objITransferSyntax.add(DicomConstants.TS_LOSSY_JPEG_8_BIT);//0003.DCM
		objITransferSyntax.add(DicomConstants.TS_EXPLICIT_VR_LITTLE_ENDIAN);//Kidney1.dcm
		RQPresContext objRQContext = new RQPresContext(DicomConstants.DCM_X_RAY_ANGIOGRAPHIC_IMAGE_STORAGE,objITransferSyntax);
		objRQPCollection.put((byte)1, objRQContext);

		objITransferSyntax = new SingleCollection<String>();
		objITransferSyntax.add(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);//3210.dcm
		objRQContext = new RQPresContext(DicomConstants.DCM_SECONDARY_CAPTURE_IMAGE_STORAGE,objITransferSyntax);
		objRQPCollection.put((byte)3, objRQContext);

		objITransferSyntax = new SingleCollection<String>();
		objITransferSyntax.add(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);//CFind
		objRQContext = new RQPresContext(DicomConstants.DCM_STUDY_ROOT_QUERY_RETRIEVE_INFORMATION_MODEL_FIND,objITransferSyntax);
		objRQPCollection.put((byte)5, objRQContext);

		objITransferSyntax = new SingleCollection<String>();
		objITransferSyntax.add(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);//CFind
		objRQContext = new RQPresContext(EnumSOPClassUIDs.PATIENTROOTQUERYFIND.getValue(),objITransferSyntax);
		objRQPCollection.put((byte)7, objRQContext);

		objITransferSyntax = new SingleCollection<String>();
		objITransferSyntax.add(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);//CFind
		objRQContext = new RQPresContext(DicomConstants.DCM_STUDY_ROOT_QUERY_OR_RETRIVE_INFORMATION_MODEL_MOVE,objITransferSyntax);
		objRQPCollection.put((byte)9, objRQContext);

		return objRQPCollection;
	}
}
