/*******************************************************************************
 *  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.tools.cgetscu;

import java.net.InetAddress;


import cdac.medinfo.sdk.dcm30_04.baselibs.DataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;

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.cget.commons.CGetRQ;
import cdac.medinfo.sdk.dcm30_04.services.cget.commons.CGetRSP;
import cdac.medinfo.sdk.dcm30_04.services.cget.scu.CGetSCU;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.DataTypeConvertor;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumServiceType;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IDataSink;
import cdac.medinfo.sdk.dcm30_04.services.javadbhelper.DataSinkImpl;
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.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;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

public class CGet_SCU 
{
	static int lengthOfOption = 18;
	static int lengthOfDetails = 80 - lengthOfOption;

	public static void main(String[] args)
	{
		EnumLoggingLevel enumLoggingLevel = EnumLoggingLevel.INFO;
		String strLoggingPath = null;
		
		String strCalledAETitle = null;
		String strCallingAETitle = "SDK";
		int iPort = 0;
		String strIP = null;
		CGet_SCU objCGet_SCU = new CGet_SCU();
		ISingleCollection<String> objGetCollection = new SingleCollection<String>();
		String affectedSOPClassUID = null;
		IPairedCollection<String, ISingleCollection<String>> objPairedCollection = new PairedCollection<String, ISingleCollection<String>>();
		ISingleCollection<RQPresContext> objRQPresContextCollection = new SingleCollection<RQPresContext>();
		String strDestinationPath = null;
		String strDBConnectionString = null;
		int iTimeout=0;

		/*
		 * If no parameters are provided then display the help.
		 */
		String strIniFile = null;
		String strKey = null;


		if(args == null || args.length == 0)
		{
			objCGet_SCU.displayHelp();
			System.exit(0);
		}

		/*
		 * This loop initializes the service parameters from the arguments provided.
		 */
		for(int i=0; i<args.length; i++)
		{
			//logging level
			if(args[i].equalsIgnoreCase("-l") && args.length > i+1)
			{
				String strLoggingLevel = args[i+1];
				if(strLoggingLevel.equalsIgnoreCase("i"))
				{
					enumLoggingLevel = EnumLoggingLevel.INFO;
				}
				else if(strLoggingLevel.equalsIgnoreCase("s"))
				{
					enumLoggingLevel = EnumLoggingLevel.SEVERE;
				}
				else
				{
					System.out.println("Not a Valid Command. The value of logging mode can be INFO(I) or SEVER(S)");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//logging path.
			else if(args[i].equalsIgnoreCase("-d") && args.length > i+1)
			{
				strLoggingPath = args[i+1];
				i++;
			}

			//Validation Mode
			else if(args[i].equalsIgnoreCase("-v") && args.length > i+1)
			{
				i++;
			}

			//calling AE Title
			else if(args[i].equalsIgnoreCase("-h") && args.length > i+1)
			{
				strCallingAETitle = args[i+1] ;
				if(strCallingAETitle == null)
				{
					System.out.println("Not a Valid Command. AE Title name is not defined.");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//called AE Title
			else if(args[i].equalsIgnoreCase("-r") && args.length > i+1)
			{
				strCalledAETitle = args[i+1] ;
				if(strCalledAETitle == null)
				{
					System.out.println("Not a valid command");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//Port
			else if(args[i].equalsIgnoreCase("-p") && args.length > i+1)
			{
				String strPort = args[i+1];
				iPort = Integer.parseInt(strPort);
				if(iPort == 0)
				{
					System.out.println("Not a valid command. Define proper port number");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//IP
			else if(args[i].equalsIgnoreCase("-i") && args.length > i+1)
			{
				strIP = args[i+1] ;
				i++;
			}

			//Query Info
			else if(args[i].equalsIgnoreCase("-k") && args.length > i+1)
			{
				String strQueryInfo = args[i+1] ;
				if(strQueryInfo.indexOf(",") == -1)
				{
					System.out.println("Not a valid command");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				if(strQueryInfo.indexOf(":") == -1)
				{
					System.out.println("Not a valid command");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				objGetCollection.add(strQueryInfo);
				i++;
			}

			//Affected SOP Class UID 
			else if(args[i].equalsIgnoreCase("-a") && args.length > i+1)
			{
				affectedSOPClassUID = args[i+1] ;
				if(affectedSOPClassUID == null)
				{
					System.out.println("Not a valid command. Affected SOP Class UID is not defined.");
					objCGet_SCU.displayHelp();
				}
				i++;
			}

			//Get Information
			else if(args[i].equalsIgnoreCase("-g") && args.length > i+1)
			{
				String strGetInfo= args[i+1];
				if(strGetInfo == null)
				{
					System.out.println("Not a valid command. Affected SOP Class UID is not defined.");
					objCGet_SCU.displayHelp();
				}
				else
				{
					if(strGetInfo.indexOf(":") == -1)
					{
						System.out.println("Not a valid command.");
						objCGet_SCU.displayHelp();
					}
					else
					{
						String strAffectedSOPClassUID = strGetInfo.substring(0,strGetInfo.indexOf(":"));
						String strTS = strGetInfo.substring(strGetInfo.indexOf(":") +1, strGetInfo.length());
						if(objPairedCollection.get(strAffectedSOPClassUID) != null )
						{
							ISingleCollection<String> strTSColl = objPairedCollection.get(strAffectedSOPClassUID);
							strTSColl.add(strTS);
							objPairedCollection.put(strAffectedSOPClassUID, strTSColl);
							RQPresContext objRQPresContext = new RQPresContext();
							objRQPresContext.setAbstractSyntaxName(strAffectedSOPClassUID);
							objRQPresContext.setTSCollection(strTSColl);
							objRQPresContextCollection.add(objRQPresContext);
						}
						else
						{
							ISingleCollection<String> strTSCollection = new SingleCollection<String>();
							strTSCollection.add(strTS);
							objPairedCollection.put(strAffectedSOPClassUID, strTSCollection);
							RQPresContext objRQPresContext = new RQPresContext();
							objRQPresContext.setAbstractSyntaxName(strAffectedSOPClassUID);
							objRQPresContext.setTSCollection(strTSCollection);
							objRQPresContextCollection.add(objRQPresContext);
						}
					}
				}
				i++;
			}

			//Destination Path
			else if(args[i].equalsIgnoreCase("-s") && args.length > i+1)
			{
				strDestinationPath = args[i+1] ;
				i++;
			}

			//DataBasePath
			else if(args[i].equalsIgnoreCase("-c") && args.length > i+1)
			{
				strDBConnectionString = args[i+1] ;
				i++;
			}

			//			Timeout
			else if(args[i].equalsIgnoreCase("-t") && args.length > i+1)
			{
				String strTimeout = args[i+1];
				iTimeout = Integer.parseInt(strTimeout);
				i++;
			}
			else if (args[i].trim().equalsIgnoreCase("-z") && args.length > i + 1)
			{
				String temp = args[i + 1].trim();
				String[] valueArray = temp.split("#");
				if (valueArray.length != 2)
				{
					System.out.println("Not a valid command");
					objCGet_SCU.displayHelp();
					System.exit(0);
				}
				strIniFile = valueArray[0];
				strKey = valueArray[1];
				i++;
			}
			else
			{
				System.out.println("Not a valid command");
				objCGet_SCU.displayHelp();
				System.exit(0);
			}
		}

		if(strCalledAETitle == null ||  iPort ==0 || objGetCollection.size() ==0 || affectedSOPClassUID == null || objPairedCollection.size() ==0 )
		{
			System.out.println("Not a valid command");
			objCGet_SCU.displayHelp();
			System.exit(0);
		}
		objCGet_SCU.doCGet(enumLoggingLevel, strLoggingPath, objGetCollection, strIP, iPort, strCallingAETitle, strCalledAETitle, affectedSOPClassUID,objRQPresContextCollection, strDestinationPath, strDBConnectionString,iTimeout,strIniFile, strKey);
	}

	void doCGet(EnumLoggingLevel enumLoggingLevel, String strLogPath,  ISingleCollection<String> objGetCollection, String strIP, int iPort, String strCallingAETitle, String strCalledAString, String strAffectedSOPClassUID, ISingleCollection<RQPresContext> objRQPresContextCollection, String strDestinationPath, String strDBConnectionString,int iTimeout,String strIniFile, String strKey)
	{
		try
		{
			if(strIP == null)
			{				
				strIP = InetAddress.getLocalHost().getHostAddress();
			}
			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'.
			//If no directory is defined by user to create logs then it is created in User Temp DIR.
			objDicomConfig.enableLogging(enumLoggingLevel,strLogPath);

			EnumServiceType[] arrenumServiceType = new EnumServiceType[2];
			arrenumServiceType[0] = EnumServiceType.CGET;
			DefaultAssociation objDefaultAssociation = new DefaultAssociation(strCallingAETitle);
			objDefaultAssociation.loadAssociation(arrenumServiceType);

			for(RQPresContext objRQPresContext : objRQPresContextCollection)
			{
				objDefaultAssociation.addPresContext(objRQPresContext);
			}

			SCUSession objSession = new SCUSession();

			//Create Association RQ
			AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAString);

			//Start SCU Session by providing the IP address and Port of SCP.
			if(iTimeout>0)
				objSession.setTimeOut(iTimeout);
			objSession.start(strIP, iPort);

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

			if(strDestinationPath != null)
				objCGetSCU.setDestinationPath(strDestinationPath);

			if(strDBConnectionString != null)
			{
				String driverString = "org.apache.derby.jdbc.ClientDriver";
				IDataSink objIDataSink = new DataSinkImpl(strDBConnectionString,driverString);
				objCGetSCU.setDataSink(objIDataSink);
			}

			System.out.println("Sending Association request");

			//			Send the Association Request
			if(objSession.sendAssociationMessage(objAssociateRQ))
			{	
				//When association is successfully established the "objSession.sendAssociationMessage()"
				//method returns true.

				System.out.println("Association established");

				//				To send multiple RQ use while loop
				//				int i = 4;
				//				while(i-- > 0)
				//				{

				//set the data set on C-Find RQ. The Data set contains the query-able attributes
				//and the key attribute.
				IDataSet objIDataSet = getCGetDatasets(objGetCollection);

				//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.
				CGetRQ objCGetRQ = CGetRQ.getDefaultRQ(strAffectedSOPClassUID, objIDataSet);

				System.out.println("CGet Request initialized");

				//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 returns 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.)
				//The matched attributes are sent in the pending response data set and then the success response is sent
				//No of pending response is the no of matches found.
				if(objCGetSCU.sendMessage(objCGetRQ))
				{
					System.out.println("CGet Response Received");

					ISingleCollection<DicomMessage> objSingleCollection = objCGetSCU.getDIMSEResponse();
					System.out.println("Size of Response collection :: " + objSingleCollection.size());

					/*
					 * This will get the pending response from the Dicom Message and displays it.
					 */
					for(DicomMessage objDicomMessage : objSingleCollection)
					{
						CGetRSP objCGetRSP = new CGetRSP(objDicomMessage);
						System.out.println("Response Status  :" + Integer.toHexString(objCGetRSP.getStatus()));
						IDataSet objIDataSet2 = objDicomMessage.getDataSet();
						if(objIDataSet2 != null)
							display(objIDataSet2);
					}
				}
				else
					System.out.println("C-Get operation failed.");

				//Create A-Release-RQ to release the association
				AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);

				//Send the A-Release-RQ
				if(objSession.sendAssociationMessage(objAReleaseRQorRP))
				{
					System.out.println("Association is released");
					//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-Find-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();
		}
	}

	private IDataSet getCGetDatasets(ISingleCollection<String> objMoveCollection)
	{
		IDataSet objIDataSet = new DataSet();
		for(String strValue : objMoveCollection)
		{
			String iGroupNumber = strValue.substring(0,strValue.indexOf(","));
			String iElementNumber = strValue.substring(strValue.indexOf(",")+1,strValue.indexOf(":"));
			String strFindValue = null;
			if(strValue.indexOf(":") == -1)
				strFindValue = null;
			else
				strFindValue = strValue.substring(strValue.indexOf(":")+1,strValue.length());

			if(strFindValue.trim().equalsIgnoreCase("null"))
			{
				strFindValue = null;
			}
			String strVR = objIDataSet.getDictionaryReader().getVR(Integer.parseInt(iGroupNumber, 16), Integer.parseInt(iElementNumber, 16));
			int iVR = StandardMethods.getiVRtype(strVR);
			IDataElement objIDataElement = DataTypeConvertor.stringtoDataElement(strFindValue, Integer.parseInt(iGroupNumber, 16), Integer.parseInt(iElementNumber, 16), iVR, objIDataSet.getTransferSyntax(), objIDataSet.getDictionaryReader());
			objIDataSet.addDataElement(objIDataElement);
		}
		return objIDataSet;
	}

	/*
	 * Display the pending data set.
	 */
	private void display(IDataSet objIDataSet)
	{
		for(IDataElement objIDataElement : objIDataSet)
		{
			if(objIDataElement != null)
			{
				if(objIDataElement.getTag() != null)
				{
					System.out.println("Group No " + objIDataElement.getTag().getGroupNumber());
					System.out.println("Element No " + objIDataElement.getTag().getElementNumber());
					if(objIDataElement.getDicomValue() != null)
					{
						if(objIDataElement.getDicomValue().getValue() != null)
						{
							System.out.println("Value is " +objIDataElement.getDicomValue().getValue().toString());
						}
						else
						{
							System.out.println("Value is " + null);
						}
					}
				}

			}
		}
	}

	/*
	 * This function displays the help.
	 */
	private void displayHelp()
	{
		//Description of command
		System.out.println("\nCGETSCU :: Sends C-Get request to DICOM SCP.");

		//Signature
		System.out.println("\n" + "CGETSCU [-i IP] -p Port [-d LogDir]  " +
				"[-l LogMode] \n -h AETitle -r AETitle -k GroupNo,ElementNo:Value " +
		"[-s StorageDir] \n -c DBConString -a SOPClassUID -g SOPClassUID:TS [-t TimeOut] " +
		"\n \n");

		//IP Address
		printToConsole("-i", "IP Address, default IP address is localhost.");

		//Port
		printToConsole("-p", "Port");

		//Log directory
		printToConsole("-d", "Log directory");
		printToConsole("", "User's temp directory is taken as default.");


		// Log Mode
		printToConsole("-l","Log mode");
		String [] arrLogmode = {"I INFO","S SEVER"};
		printToConsole("LogMode", arrLogmode);
		printToConsole("","Default Logging Mode is INFO.");

		//Calling(host) AE Title
		printToConsole("-h","Calling(host) AE Title");

		//Called AE Title
		printToConsole("-r","Called AE Title");

		//Key Attribute
		printToConsole("-k", "Key Attribute");
		printToConsole("", "For eg.- 0010,0020:abc");

		//Storage directory
		printToConsole("-s","Storage directory of DICOM files");
		printToConsole("", "User's temp directory is taken as default.");

		//Data base connection string
		printToConsole("-c","Data base connection string");

		//Affected SOP class UID
		printToConsole("-a", "Affected SOP class UID");

		//Get Info
		printToConsole("-g", "SOP Class UID and Transfer syntax of DICOM files to be retrieved.");
		printToConsole("", "For eg.- 1.2.840.10008.5.1.4.1.1.12.1:1.2.840.10008.1.2.4.50");

		//Session time out
		printToConsole("-t","Session time out in milliseconds");

        
        System.out.println("\nExample :");
        
		//Example
		System.out.println("\n1. CGETSCU -p 9000 -h ABC -r SDK -k 0008,0018:1.3.12.2.1107.5.4.3.284980.19951129.170916 -a 1.2.840.10008.5.1.4.1.2.1.3 -g 1.2.840.10008.5.1.4.1.1.12.1:1.2.840.10008.1.2.4.50\n" +
				"\nClient ABC, request the AE SDK running on localhost and listening on port" +
				" 9000, to retrieve DICOM file having attribute 0008, 0018 and " +
				"value \n1.3.12.2.1107.5.4.3.284980.19951129.170916, for C-Get SOP class UID \n1.2.840.10008.5.1.4.1.2.1.3. " +
				"Retrieve request for DICOM Files having SOP class \nUID " +
				"1.2.840.10008.5.1.4.1.1.12.1 and Transfer syntax 1.2.840.10008.1.2.4.50.\n");
     
	}

	/**
	 * Display Help of command on console in proper manner.
	 * @param strOption Option for command.
	 * @param strDetails Description of option.
	 */
	public static void printToConsole(String strOption, String strDetails)
	{
		String strBuffer = "";
		if (strOption != null)
			strBuffer = strBuffer + strOption ;
		while (lengthOfOption > strBuffer.length())
		{
			strBuffer = strBuffer + " ";
		}
		if (strDetails != null)
		{
			if (strDetails.length() < lengthOfDetails || strDetails.length() == lengthOfDetails)
			{
				strBuffer = strBuffer + strDetails;
				strDetails = null;
			}
			else
			{
				strBuffer = strBuffer + strDetails.substring(0, lengthOfDetails);
				strDetails = strDetails.substring(lengthOfDetails, (strDetails.length() ));
			}
		}

		System.out.println(strBuffer);
		if (strDetails == null)
			return;
		else
			printToConsole(null, strDetails);
	}

	/**
	 * Display Help of command on console in proper manner.
	 * @param strAttribute Attribute for option.
	 * @param strDesc Description of attributes.
	 */
	public static void printToConsole(String strAttribute, String[] strDesc)
	{
		if (strDesc.length > 1)
			printToConsole(strAttribute, strDesc[0]);
		for(int i = 1;i<strDesc.length;i++)
			printToConsole(null, strDesc[i]);
	}
}
