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

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.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.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.scu.commons.SCUSession;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

/**
 * This class acts as main class for C-Move Utility tools of DICOM.
 * One the desired parameters are provided it performs C-Move Operation.
 */
public class CMove_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;
		CMove_SCU objCMove_SCU = new CMove_SCU();
		ISingleCollection<String> objMoveCollection = new SingleCollection<String>();
		String affectedSOPClassUID = null;
		String strMoveDestination = 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)
		{
			objCMove_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)");
					objCMove_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.");
					objCMove_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");
					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");
					objCMove_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");
					objCMove_SCU.displayHelp();
					System.exit(0);
				}
				objMoveCollection.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.");
					objCMove_SCU.displayHelp();
				}
				i++;
			}

			//Move Destination 
			else if(args[i].equalsIgnoreCase("-o") && args.length > i+1)
			{
				strMoveDestination = args[i+1] ;
				if(strMoveDestination == null)
				{
					System.out.println("Not a valid command. Move Destiantion is not defined.");
					objCMove_SCU.displayHelp();
				}
				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");
					objCMove_SCU.displayHelp();
					System.exit(0);
				}
				strIniFile = valueArray[0];
				strKey = valueArray[1];
				i++;
			}

			else
			{
				System.out.println("Not a valid command");
				objCMove_SCU.displayHelp();
				System.exit(0);
			}
		}

		if(strCalledAETitle == null ||  iPort ==0 || objMoveCollection.size() ==0 || affectedSOPClassUID == null || strMoveDestination == null)
		{
			System.out.println("Not a valid command");
			objCMove_SCU.displayHelp();
			System.exit(0);
		}
		objCMove_SCU.doCMove(enumLoggingLevel, strLoggingPath, objMoveCollection, strIP, iPort, strCallingAETitle, strCalledAETitle, affectedSOPClassUID, strMoveDestination,iTimeout,strIniFile, strKey);
	}

	void doCMove(EnumLoggingLevel enumLoggingLevel, String strLogPath, ISingleCollection<String> objMoveCollection, String strIP, int iPort, String strCallingAETitle, String strCalledAString, String strAffectedSOPClassUID, String strMoveDestination,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[1];
			arrenumServiceType[0] = EnumServiceType.CMOVE;
			DefaultAssociation objDefaultAssociation = new DefaultAssociation(strCallingAETitle);
			objDefaultAssociation.loadAssociation(arrenumServiceType);

			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.
			CMoveSCU objCMoveSCU = new CMoveSCU(objSession);

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

			//			Send the Association Request
			if(objSession.sendAssociationMessage(objAssociateRQ))
			{	
				//When associaton 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 queryable attributes
				//and the key attribute.
				IDataSet objIDataSet = getCMoveDatasets(objMoveCollection);

				//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.
				CMoveRQ objCMoveRQ = CMoveRQ.getDefaultRQ(strAffectedSOPClassUID,strMoveDestination,objIDataSet);

				System.out.println("CMove 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(objCMoveSCU.sendMessage(objCMoveRQ))
				{
					System.out.println("CMove Response Received");

					ISingleCollection<DicomMessage> objSingleCollection = objCMoveSCU.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)
					{
						CMoveRSP objCMoveRSP = new CMoveRSP(objDicomMessage);
						System.out.println("Response Status  :" + Integer.toHexString(objCMoveRSP.getStatus()));
						IDataSet objIDataSet2 = objDicomMessage.getDataSet();
						if(objIDataSet2 != null)
							display(objIDataSet2);
					}
				}
				else
					System.out.println("C-Move 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 getCMoveDatasets(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());

			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()
	{
		//Discription of command
		System.out.println("\nCMOVESCU :: Sends Move request to DICOM SCP.");

		//Signature
		System.out.println("\n" + "CMOVESCU [-i IP] -p Port [-d LogDir]  " +
				"[-l LogMode] \n -h AETitle -r AETitle -k GroupNo,ElementNo:Value " +
				" -o MoveDest -a SOPClassUID /n [-t TimeOut] " + "\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");

		//Move Destination 
		printToConsole("-o","Move destination for DICOM files");

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

		//Session time out
		printToConsole("-t","Session time out in milliseconds");
        
        System.out.println("\nExample :");

		//Example
		System.out.println("\n1. CMOVESCU -p 9000 -h ABC -r SDK -k 0008,0018:1.3.12.2.1107.5.4.3.284980.19951129.170916.9 -a 1.2.840.10008.5.1.4.1.2.1.2 -o XYZ\n" +
				"\nClient ABC, request AE SDK running on localhost listening on " +
				"port 9000, to move the DICOM Object having attribute 0008, 0018 and value " +
				"1.3.12.2.1107.5.4.3.284980.19951129.170916.9, on third party move destination server named XYZ for C-Move SOP " +
				"class UID 1.2.840.10008.5.1.4.1.2.1.2.\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]);
	}
}
