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

import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


import cdac.medinfo.sdk.dcm30_04.baselibs.DirectoryRecordSequence;
import cdac.medinfo.sdk.dcm30_04.baselibs.DirectoryRecordSequenceItem;
import cdac.medinfo.sdk.dcm30_04.baselibs.SQ;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IItem;
import cdac.medinfo.sdk.dcm30_04.dicommodel.basicdirectory.BasicDirectoryIOD;
import cdac.medinfo.sdk.dcm30_04.dicommodel.modules.DirectoryInformationModule;
import cdac.medinfo.sdk.dcm30_04.dictionaryreaderwriters.DictionaryReader;
import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;

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.AssociationNegotiator;
import cdac.medinfo.sdk.dcm30_04.services.association.DefaultAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.IDIMSEAssociation;
import cdac.medinfo.sdk.dcm30_04.services.cfind.scp.CFindSCP;
import cdac.medinfo.sdk.dcm30_04.services.cget.scp.CGetSCP;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRQ;
import cdac.medinfo.sdk.dcm30_04.services.cmove.scp.CMoveSCP;
import cdac.medinfo.sdk.dcm30_04.services.cstore.scp.CStoreSCP;
import cdac.medinfo.sdk.dcm30_04.services.dicomdir.DICOMDIR;
import cdac.medinfo.sdk.dcm30_04.services.dicomdir.DICOMDIRSinkImpl;
import cdac.medinfo.sdk.dcm30_04.services.dicomdir.DICOMDIRSourceImpl;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumDataSink;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumDataSource;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumServiceType;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DIMSEResponse;
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.dimsecommons.IDataSource;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ClientSession;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ProcessHandler;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.SCPSession;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator;
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;
/**
 * Utility class for Dicom file structural representation.
 */
public class DICOMDIRSCP 
{
	static int lengthOfOption = 18;
	static int lengthOfDetails = 79 - lengthOfOption;

	public static void main(String[] args)
	{
		EnumLoggingLevel enumLoggingLevel = EnumLoggingLevel.INFO;
		String strLoggingPath = null;
		
		String strAETitle = null;
		String strDestinationPath = null;
		int iPort = 0;
		DICOMDIRSCP objDICOMDIRTools = new DICOMDIRSCP();
		String strIP = null;//"192.168.8.101";
		ISingleCollection<String> objMoveInformation = new SingleCollection<String>();
		int iTimeout=0;
		String strDICOMDIRFilePath = null;

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


		if(args == null || args.length == 0)
		{
			objDICOMDIRTools.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)");
					objDICOMDIRTools.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)
			{
				strAETitle = args[i+1] ;
				if(strAETitle == null)
				{
					System.out.println("Not a Valid Command. AE Title name is not defined.");
					objDICOMDIRTools.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");
					objDICOMDIRTools.displayHelp();
					System.exit(0);
				}
				i++;
			}

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

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

			//Move TO information
			else if(args[i].equalsIgnoreCase("-m") && args.length > i+1)
			{
				String strMoveInformation = args[i+1] ;
				if(strMoveInformation.indexOf(":") == -1)
				{
					System.out.println("Not a valid command");
					objDICOMDIRTools.displayHelp();
					System.exit(0);
				}
				else
				{
					objMoveInformation.add(strMoveInformation);
				}
				i++;
			}
			// DICOMDIR File Path
			else if(args[i].equalsIgnoreCase("-o") && args.length > i+1)
			{
				strDICOMDIRFilePath = 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");
					objDICOMDIRTools.displayHelp();
					System.exit(0);
				}
				strIniFile = valueArray[0];
				strKey = valueArray[1];
				i++;				
			}

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

		if(strDestinationPath == null)
		{
			strDestinationPath = System.getProperty("java.io.tmpdir")+"/CDAC-SDK/DCM-J/DicomDump/";
		}

		if((strAETitle == null || iPort == 0) && strDICOMDIRFilePath == null)
		{
			System.out.println("Not a valid command");
			objDICOMDIRTools.displayHelp();
			System.exit(0);
		}

		if(strDICOMDIRFilePath != null)
		{
			openDICOMDIRFile(enumLoggingLevel, strLoggingPath, strDICOMDIRFilePath,strIniFile, strKey);
		}
		else
		{
			SCPHandler objSCPStater = new SCPHandler();
			objSCPStater.startSCP(strIP,enumLoggingLevel,strLoggingPath,strAETitle,iPort,strDestinationPath,objMoveInformation, iTimeout,strIniFile, strKey);
		}
	}

	private static void openDICOMDIRFile(EnumLoggingLevel enumLoggingLevel, String strLoggingPath,  String strDICOMDIRDirPath,String strIniFile, String strKey) 
	{
		try 
		{
			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, strLoggingPath);

			

			DICOMDIR objDicomdir = new DICOMDIR(strDICOMDIRDirPath);
			IDataSet objIDataSet = objDicomdir.read();

			DICOMDIRSCP objDICOMDIRTools = new DICOMDIRSCP();

			// Displace Previous Information about DICOMDIR File
			System.out.println();
			objDICOMDIRTools.displatPreviousInfo(objIDataSet);

			System.out.println();

			DirectoryInformationModule objDirectoryInformationModule = objDICOMDIRTools.retrieveDirectoryInformationModule(objIDataSet);

			// Offset Info
			int iOfSetOfFirstDirectory = 0;
			int iOfSetOfLastDirectory = 0;

			IDataElement deOfSetOfFirstDirectory = objIDataSet.getDataForTag(0x0004,0x1200);
			if(deOfSetOfFirstDirectory != null)
				iOfSetOfFirstDirectory = (Integer)deOfSetOfFirstDirectory.getValue();

			IDataElement deOfSetOfLastDirectory = objIDataSet.getDataForTag(0x0004,0x1202);
			if(deOfSetOfLastDirectory != null)
				iOfSetOfLastDirectory = (Integer)deOfSetOfLastDirectory.getValue();

			/*
			 * Paired collection that contains items and their offset.
			 */
			IPairedCollection<Integer, DirectoryRecordSequenceItem> objOffsetTable = new PairedCollection<Integer, DirectoryRecordSequenceItem>();

			objDICOMDIRTools.populateOffSetTable(objDirectoryInformationModule, objOffsetTable);

			objDICOMDIRTools.displayOffSetTable(objOffsetTable, iOfSetOfFirstDirectory,iOfSetOfLastDirectory);

			//objDICOMDIRTools.display(objIDataSet);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}

	private void displatPreviousInfo(IDataSet objIDataSet)
	{
		IDataElement objDataElement = null;
		if(objIDataSet.isPresent(0x0002,0x0002))
		{
			objDataElement = objIDataSet.getDataForTag(0x0002,0x0002);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0002,0x0003))
		{
			objDataElement = objIDataSet.getDataForTag(0x0002,0x0003);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0002,0x0010))
		{
			objDataElement = objIDataSet.getDataForTag(0x0002,0x0010);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0004,0x1130))
		{
			objDataElement = objIDataSet.getDataForTag(0x0004,0x1130);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0004,0x1200))
		{
			objDataElement = objIDataSet.getDataForTag(0x0004,0x1200);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0004,0x1202))
		{
			objDataElement = objIDataSet.getDataForTag(0x0004,0x1202);
			displayDataElement(objDataElement);
		}
		if(objIDataSet.isPresent(0x0004,0x1212))
		{
			objDataElement = objIDataSet.getDataForTag(0x0004,0x1212);
			displayDataElement(objDataElement);
		}

	}

	/*
	 * Retrieves Directory Information Module.
	 */
	private DirectoryInformationModule retrieveDirectoryInformationModule(IDataSet objIDataSet)
	{
		BasicDirectoryIOD objBasicDirectoryIOD = new BasicDirectoryIOD(objIDataSet);
		DirectoryInformationModule objDirectoryInformationModule = objBasicDirectoryIOD.getDirectoryInformationModule();
		return objDirectoryInformationModule;
	}

	/*
	 * Populates offset table. This offset table will contian the item and its offset.
	 */
	private void populateOffSetTable(DirectoryInformationModule objDirectoryInformationModule,IPairedCollection<Integer, DirectoryRecordSequenceItem> objOffsetTable)
	{
		int startingOffSet = 0;
		startingOffSet = objDirectoryInformationModule.getOffsetOfFirstDirectoryRecordOfRootDirectoryEntity();
		int nextOffset = startingOffSet;
		DirectoryRecordSequence objDirectoryRecordSequence = objDirectoryInformationModule.getDirectoryRecordSequence();
		for(IItem objDirectoryRecordSequenceItem : objDirectoryRecordSequence)
		{
			objOffsetTable.put(nextOffset, (DirectoryRecordSequenceItem)objDirectoryRecordSequenceItem);
			int iItemLength = objDirectoryRecordSequenceItem.getItemLength();
			nextOffset = nextOffset + iItemLength ;
		}
	}

	private void displayOffSetTable(IPairedCollection<Integer , DirectoryRecordSequenceItem> objOffsetTable, int strOfSetOfFirstDirectory, int strOfSetOfLastDirectory)
	{
		DirectoryRecordSequenceItem objItem = objOffsetTable.get(strOfSetOfFirstDirectory);

		String strDirectoryRecordType = null;
		int iOffsetOfNextRefLowerLevDirEntry = 0;
		int iOffsetOfNextDirRec = 0;

		if(objItem.contains(0x0004,0x1430))
			strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

		if(objItem.contains(0x0004,0x1420))
			iOffsetOfNextRefLowerLevDirEntry = (Integer)objItem.getValue(0x0004,0x1420);

		if(objItem.contains(0x0004,0x1400))
			iOffsetOfNextDirRec = (Integer)objItem.getValue(0x0004,0x1400);

		System.out.println(strOfSetOfFirstDirectory + ": " + strDirectoryRecordType);

		//Display data elements of Item
		displayItem(objItem);
		System.out.println();

		if(iOffsetOfNextRefLowerLevDirEntry != 0)
		{
			outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffsetOfNextDirRec, iOffsetOfNextDirRec);
		}
	}

	private void outPut(int iItemLowerRec, IPairedCollection<Integer , DirectoryRecordSequenceItem> objOffsetTable, int iNextDirRec, int iOffSetOfNextPatDir)
	{
		if(iItemLowerRec != 0)
		{
			DirectoryRecordSequenceItem objItem = objOffsetTable.get(iItemLowerRec);

			String strDirectoryRecordType = null;
			int iOffsetOfNextDirRec = 0;
			int iOffsetOfNextRefLowerLevDirEntry = 0;

			if(objItem.contains(0x0004,0x1430))
				strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

			if(objItem.contains(0x0004,0x1400))
				iOffsetOfNextDirRec = (Integer)objItem.getValue(0x0004,0x1400);

			if(objItem.contains(0x0004,0x1420))
				iOffsetOfNextRefLowerLevDirEntry = (Integer)objItem.getValue(0x0004,0x1420);

			System.out.println(iItemLowerRec + ": " + strDirectoryRecordType);

			//Display data elements of Item
			displayItem(objItem);
			System.out.println();

			if (iOffsetOfNextRefLowerLevDirEntry != 0 && iOffsetOfNextDirRec == 0) 
				outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iNextDirRec, iOffSetOfNextPatDir);

			else if (iOffsetOfNextRefLowerLevDirEntry == 0 && iOffsetOfNextDirRec != 0) 
				outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffsetOfNextDirRec, iOffSetOfNextPatDir);

			else if (iOffsetOfNextRefLowerLevDirEntry != 0 && iOffsetOfNextDirRec != 0) 
				outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffsetOfNextDirRec, iOffsetOfNextDirRec);

			else if(iOffsetOfNextRefLowerLevDirEntry == 0 && iOffsetOfNextDirRec == 0)
			{
				if(iOffSetOfNextPatDir > iItemLowerRec) // if patient dir is NOT printed already
					outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffSetOfNextPatDir, iOffSetOfNextPatDir);
			}
		}
		else
		{
			if(iNextDirRec != 0)
			{
				DirectoryRecordSequenceItem objItem = objOffsetTable.get(iNextDirRec);

				String strDirectoryRecordType = null;
				int iOffsetOfNextRefLowerLevDirEntry = 0;
				int iOffsetOfNextDirRec = 0;


				if(objItem.contains(0x0004,0x1430))
					strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

				if(objItem.contains(0x0004,0x1420))
					iOffsetOfNextRefLowerLevDirEntry = (Integer)objItem.getValue(0x0004,0x1420);

				if(objItem.contains(0x0004,0x1400))
					iOffsetOfNextDirRec = (Integer)objItem.getValue(0x0004,0x1400);

				System.out.println(iNextDirRec + ": " + strDirectoryRecordType);

				//Display data elements of Item
				displayItem(objItem);
				System.out.println();

				if(iOffsetOfNextRefLowerLevDirEntry == 0 && iOffsetOfNextDirRec != 0)
					outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffsetOfNextDirRec,iOffSetOfNextPatDir);

				if(iOffsetOfNextRefLowerLevDirEntry == 0 && iOffsetOfNextDirRec == 0)
				{
					outPut(iOffSetOfNextPatDir, objOffsetTable, iOffSetOfNextPatDir, iOffSetOfNextPatDir);
				}

				if(iOffsetOfNextRefLowerLevDirEntry != 0 && iOffsetOfNextDirRec == 0)
				{
					if(iNextDirRec == iOffSetOfNextPatDir) // if next pat dir comes then change passed next pat dir
					{
						iOffSetOfNextPatDir = iOffsetOfNextDirRec;
						outPut(iOffsetOfNextRefLowerLevDirEntry, objOffsetTable, iOffSetOfNextPatDir, iOffSetOfNextPatDir);
					}
				}
			}
		}
	}



	private void displayItem(IItem objIItem)
	{
		//Display data elements  
		ICollectionIterator<IDataElement> itr = objIItem.iterator();
		while(itr.hasNext())
		{
			IDataElement objDataElement = itr.next();
			displayDataElement(objDataElement);
		}
	}

	//	/*
	//	 * Display contents of DICOMDIR FIle.
	//	 */
	//	private void display(IDataSet objIDataSet)
	//	{
	//		for(IDataElement objIDataElement : objIDataSet)
	//		{
	//			if(objIDataElement != null && objIDataElement.getTag() != null)
	//			{
	//				String strOffsertOfFirstDicRecOFRootSir = objIDataSet.getDataForTag(0x0004,0x1200).getValue().toString();
	//
	//				if(!(objIDataElement.getDicomValue() instanceof SQ))
	//				{
	//					displayDataElement(objIDataElement);
	//				}
	//				else
	//				{
	//					// For Directory Record Sequence Item 
	//					ISingleCollection<IItem> objCollDirRecSQItems = (ISingleCollection<IItem>)objIDataElement.getDicomValue().getValue();
	//
	//					for(int i=0; i<objCollDirRecSQItems.size(); i++)
	//					{
	//						// To print Offset and DirectoryRecordType like  - PATIENT, STUDY, SERIES, IMAGE
	//						printOffset( objCollDirRecSQItems, i, strOffsertOfFirstDicRecOFRootSir);
	//
	//						IItem objDirRecSQItem = objCollDirRecSQItems.get(i);
	//						//Display data elements  
	//						ICollectionIterator<IDataElement> itr = objDirRecSQItem.iterator();
	//						while(itr.hasNext())
	//						{
	//							IDataElement objDataElement = itr.next();
	//							displayDataElement(objDataElement);
	//						}
	//					}
	//				}
	//
	//			}
	//		}
	//	}

	//	private void printOffset(ISingleCollection<IItem> objCollDirRecSQItems, int i,String strOffsertOfFirstDicRecOFRootSir)
	//	{
	//		// To print Offset and DirectoryRecordType like  - PATIENT, STUDY, SERIES, IMAGE
	//		String strDirectoryRecordType = null;
	//		IItem objDirRecSQItem = objCollDirRecSQItems.get(i);
	//
	//		if(objDirRecSQItem.contains(0x0004,0x1430))
	//			strDirectoryRecordType =objDirRecSQItem.getValue(0x0004,0x1430).toString();
	//
	//		String strOffsetofReferencedLowerLevelDirectoryEntity = null;
	//		if(i == 0)
	//		{
	//			if(objDirRecSQItem.contains(0x0004,0x1420))
	//			{
	//
	//				strOffsetofReferencedLowerLevelDirectoryEntity = strOffsertOfFirstDicRecOFRootSir;
	//				System.out.println("\n" + strOffsetofReferencedLowerLevelDirectoryEntity+ ": " + strDirectoryRecordType);
	//			}
	//		}
	//		else
	//		{
	//			IItem previousItem = objCollDirRecSQItems.get(i-1);
	//
	//			if(objDirRecSQItem.contains(0x0004,0x1420))
	//			{
	//				strDirectoryRecordType = objDirRecSQItem.getValue(0x0004,0x1430).toString();
	//				strOffsetofReferencedLowerLevelDirectoryEntity = previousItem.getValue(0x0004,0x1420).toString();
	//				System.out.println("\n" + strOffsetofReferencedLowerLevelDirectoryEntity+ ": " + strDirectoryRecordType);
	//			}
	//		}
	//	}


	@SuppressWarnings("unchecked")
	private void displayDataElement(IDataElement objIDataElement)
	{
		int iGrNum = objIDataElement.getTag().getGroupNumber();
		int iEleNum = objIDataElement.getTag().getElementNumber();

		DictionaryReader objDictionaryReader = DictionaryReader.createInstance();
		String strAttributeName = objDictionaryReader.getName(iGrNum, iEleNum);

		if(objIDataElement.getDicomValue() instanceof SQ)
		{
			ISingleCollection<IItem> objCollDirRecSQItems = (ISingleCollection<IItem>)objIDataElement.getDicomValue().getValue();
			int i = 1;
			for(IItem objItem : objCollDirRecSQItems)
			{

				//				System.out.println("<" + iGrNum + "," + iEleNum + ">" + "  " + "SQ" + "  [" + i + " ITEM " + "]  " + strAttributeName);
				//				System.out.println(">ITEM #" + i);

				String str1 = "<" + iGrNum + "," + iEleNum + ">";
				String str2 = "SQ" + "  [" + i + " ITEM " + "]  " + strAttributeName;
				printToConsole(str1, str2);
				printToConsole(">ITEM #" + i, "");

				ICollectionIterator<IDataElement> itr = objItem.iterator();
				while(itr.hasNext())
				{
					IDataElement objDataElement = itr.next();
					displayDataElement(objDataElement);
				}
				i++;
			}
		}
		else
		{
			if(objIDataElement.getDicomValue() != null)
			{
				if(objIDataElement.getDicomValue().getValue() != null)
				{
					int iVRType = objIDataElement.getDicomValue().getIVRType();
					String strVRType = StandardMethods.getStrVRtype(iVRType);

					String strVal = objIDataElement.getDicomValue().getValue().toString();

					//System.out.println("<" + iGrNum + "," + iEleNum + ">" + "  " + strVRType + "  [" + strVal + "]  " + strAttributeName);

					String str1 = "<" + iGrNum + "," + iEleNum + ">";
					String str2 = strVRType + "  [" + strVal + "]  " + strAttributeName;
					printToConsole(str1, str2);
				}
				else
				{
					//System.out.println("<" + iGrNum + "," + iEleNum + ">" + "  " + "" + "  [" + "" + "]  " + strAttributeName);

					String str1 = "<" + iGrNum + "," + iEleNum + ">";
					String str2 = " " + "  [" + " " + "]  " + strAttributeName;
					printToConsole(str1, str2);
				}
			}
		}
	}

	private void printLevel(int ilevel)
	{
		System.out.println();
		for(int i=1; i <= ilevel; i++)
		{
			System.out.print(">");
		}
	}

	/*
	 * This function displays the help.
	 */
	private void displayHelp()
	{
		//Description of command
		System.out.println("\nDICOMDIRSCP :: Process all C-Service request specified by DICOM using DICOMDIR.");

		//Signature
		System.out.println("\n" + "DICOMDIRSCP [-i IP] -p Port [-d LogDir] " +
				"[-l LogMode] \n [-s StorageDir] -h AETitle [{-m AETilte@IP:Port}] [-t TimeOut]"
				+ "\n -o DICOMDIRStorageDir \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.");

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

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

		//Move destination title
		printToConsole("-m","Move destination title.");
		printToConsole("","For eg. - SDK@localhost:9002");

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

		//To print DICOMDIR File COntents
		printToConsole("-o","Reads the DICOMDIR file and renders its contents on console.");
        
		//Example
		System.out.println("\nExample :");
		
		System.out.println("\nThese examples are demonstration of simple C-Store Operation.\n");
		
		System.out.println("\n1. DICOMDIRSCP -p 9000 -h SDK" +
				"\nStarts server at localhost listening on port 9000, with SDK as AE Title\n");

		System.out.println("\n2. DICOMDIRSCP -p 9000 -h SDK -m XYZ:9002" +
				"\nStarts server at localhost listening on port 9000, SDK as AE Title " +
				"Storage \nserver for move operation started at localhost listening on port 9002\n");

		System.out.println("\n3. DICOMDIRSCP -o c:\\StoragePath" +
				"\nReads the DICOMDIR file from c:\\StoragePath and prints its contents on console.\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]);
	}
}

class SCPHandler 
{
	public void startSCP(String strIP ,EnumLoggingLevel enumLoggingLevel, String strLoggingPath, String strAETitle, int iPort, String strDestinationPath, ISingleCollection<String> objMoveInformation,int iTimeout,String strIniFile, String strKey)
	{
		try
		{
			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, strLoggingPath);

						
			/*
			 * Creates CStoreSCPHadler.
			 */
			DIMSERQHandler scp = new DIMSERQHandler();
			scp.setStoragePath(strDestinationPath);

			// Add sink and Source Implementations.
			IDataSink objIDataSink = new DICOMDIRSinkImpl();
			scp.addDataSink(EnumDataSink.CSERVICES, objIDataSink);

			String strDicomDirPath = scp.getStoragePath();
			IDataSource objIDataSource = new DICOMDIRSourceImpl(strDicomDirPath);
			scp.addDataSource(EnumDataSource.CSERVICES,objIDataSource);


			if(objMoveInformation.size()!=0)
			{
				for(int i=0; i<objMoveInformation.size();i++)
				{
					String str = objMoveInformation.get(i);
					if(str.indexOf('@') == -1)
					{
						scp.addSupportedAETitles(str.substring(0,str.indexOf(":")), InetAddress.getLocalHost().getHostAddress(), Integer.parseInt(str.substring(str.indexOf(":")+1,str.length())));
					}
					else
						scp.addSupportedAETitles(str.substring(0,str.indexOf("@")), str.substring(str.indexOf("@")+1, str.indexOf(":")), Integer.parseInt(str.substring(str.indexOf(":")+1,str.length())));
				}
			}

			SCPSession session = new SCPSession();
			if(iTimeout > 0)
				session.setTimeOut(iTimeout);

			/*
			 * Initializes the service types and sets it on SCP Session
			 * EnumService Type configures SCP Session to load corresponding association context and start
			 * corresponding DIMSE Services.
			 */
			EnumServiceType []arrenumServiceType = new EnumServiceType[4];
			arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
			arrenumServiceType[1] = EnumServiceType.CFIND_DEFAULT;
			arrenumServiceType[2] = EnumServiceType.CMOVE;
			arrenumServiceType[3] = EnumServiceType.CGET;
			session.selectServices(arrenumServiceType);

			//Set Process Handler as the SCP TestCode
			session.setIProcessHandler(scp);

			if(strIP == null)
				//Start the Session
				session.start(strAETitle,iPort);
			else
				session.start(strAETitle, strIP, iPort);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

class DIMSERQHandler extends ProcessHandler
{
	// create ExecutorService to manage threads                        
	ExecutorService threadExecutor = Executors.newCachedThreadPool();

	/**
	 * This method notifies when a new client connection is requested. A decision is to be made whether to
	 * accept or reject the connection.  
	 * @param objClientSession  object of client session.
	 */
	public void onConnect(ClientSession objClientSession)
	{
		objClientSession.acceptConnection(true);
	}


	/**
	 * When an Association Request, Associate Release Request or Abort is received from one of the 
	 * SCUs, this method creates new instance of AssociateRQHandler and executes it in seperate thread.
	 * Once the current received request is processed and a suitable response is sent to the SCU, the
	 * thread executed for AssociateRQHandler ends.
	 */
	public void onAssociateReceive(ClientSession objClientSession) 
	{
		AssociateRQHandler objAssociateRQHandler = new AssociateRQHandler(objClientSession);
		threadExecutor.execute(objAssociateRQHandler);
	}

	/**
	 * When a Service Request is received from one of the SCUs, this method creates new instance of
	 * ServiceRQHandler and executes it in separate thread there by relieving the DIMSERQHandler to receive another service request.
	 * Once the current received request is processed and a suitable response is sent to the SCU, the thread executed for ServiceRQHandler ends.
	 */
	public void onServiceRequest(ClientSession objClientSession)
	{
		ServiceRQHandler objServiceRQHandler = new ServiceRQHandler(objClientSession, getDataSink(EnumDataSink.CSERVICES),getDataSource(EnumDataSource.CSERVICES),getStoragePath(),getSupportedAETitles());
		threadExecutor.execute(objServiceRQHandler);
	}
}
/**
 * This class processes Association Request, Associate Release Request or Abort which is received from one of the 
 * SCUs. 
 * If Association Request is received,
 * 		Create Association Negotiator which negotiates the request with the existing default association and
 * 		creates an AgreedAssociation which contains the negotiated association values. AssociationNegotiator
 * 		returns the Association Response as AssociateAC when request is accepted or returns AssociateRJ is 
 * 		none of the values in Association Request are acceptable.
 * 	If Associate Release Request is received,
 * 		Create a Associate Release Response and send the response to the SCU using the respective ClientSession.
 *  If Abort is received,
 *  	An Abort response must be generated and sent using ClientSession
 *    
 * @param objClientSession instance of Client Session.
 */

class AssociateRQHandler implements Runnable
{


	ClientSession objClientSession = null;
	public AssociateRQHandler(ClientSession objClientSession)
	{
		this.objClientSession = objClientSession;
	}

	public void run()
	{
		//Get Association RQ
		IDIMSEAssociation objIDIMSEAssociation = objClientSession.getDIMSEAssociation();
		EnumPDUType enumPDUType = objIDIMSEAssociation.getPDUType();
		IDIMSEAssociation objAssociationRSP = null;

		//For Association Request negotiate with Default Association and generate Response
		switch(enumPDUType)
		{
		case ASSOCIATE_RQ :
		{
			AssociateRQ objAssociateRQ = (AssociateRQ)objIDIMSEAssociation;

			//get the default association from Client Session.
			DefaultAssociation objDefaultAssociation = objClientSession.getDefaultAssociation();

			//Negotiate and get Response
			AssociationNegotiator objAssNegotiator = new AssociationNegotiator();
			objAssociationRSP = objAssNegotiator.negotiate(objDefaultAssociation, objAssociateRQ);

			//set agreed association in client Session.
			objClientSession.setAgreedAssociation(objAssNegotiator.getAgreedAssociation());

			//Send Response
			objClientSession.sendAssociationMessage(objAssociationRSP);
			break;
		}
		case RELEASE_RQ :
		{

			//Create Release Response.
			objAssociationRSP = new AReleaseRQorRP(EnumPDUType.RELEASE_RP);

			//Send Release Response.
			objClientSession.sendAssociationMessage(objAssociationRSP);

			//Close Client Session
			objClientSession.close();
			break;
		}
		case ABORT :
		{
			System.out.println("Abort has arrived Stop Porcessing -- Association is no longer available");
			break;
		}
		}
	}
}

/**
 * This class processed any service related request received from a SCU.
 * For Example,
 * If C-Store-RQ is received,
 * 		Create CStoreSCP using the ClientSession.
 * 		Process the request using the CStoreSCP which actually serializes the DataSet on a Stream. 
 * 		Stores Data Set in the DataBase and returns
 * 		a CStoreRSP containing the response.
 * 		Send this response using the ClientSession.
 * @see objClientSession Object of Client Session.
 */
class ServiceRQHandler implements Runnable
{
	ClientSession objClientSession = null;
	IDataSink objIDataSink = null;
	IDataSource objIDataSource = null;
	String strStoragePath = null;
	IPairedCollection<String, String> objSupportedAETitle = null;
	public ServiceRQHandler(ClientSession objClientSession, IDataSink objIDataSink, IDataSource objIDataSource, String strStoragePath, IPairedCollection<String, String> strSupportedAETitle)
	{
		this.objClientSession = objClientSession;
		this.objIDataSink = objIDataSink;
		this.objIDataSource = objIDataSource;
		this.strStoragePath = strStoragePath;
		this.objSupportedAETitle = strSupportedAETitle;
	}


	public void run()
	{
		DicomMessage objDicomMessage = objClientSession.getDicomMessage();
		EnumCommandField enumCommandField = objDicomMessage.getCommandType();

		switch(enumCommandField)
		{
		case C_STORE_RQ:
		{

			System.out.println("C-Store Request Received");

			/*
			 * Create CSTORE_SCP get RQ from ClientSession call processRequest and pass C_STORE_RQ on CSTORE_SCP
			 * receive response from CSTORE_SCP sendResponse using ClientSession
			 */
			CStoreSCP objCStoreSCP = CStoreSCP.createInstance(objClientSession); 
			try
			{
				/*
				 * Process CStore Request. The DataSet is serialized at the Storage Path provided on C-Store SCP.
				 * If No Storage Path is provide on C-Store SCP then File is serialized at "%temp%/CDAC-SDK/DCM-J/DicomDump"
				 * in User Temp Folder.
				 */
				objCStoreSCP.setStoragePath(strStoragePath);

				//Set the DataSink for C-Store Service to C-Store SCP for processing of the request.
				IPairedCollection<EnumDataSink,IDataSink> objPairedCollection = new PairedCollection<EnumDataSink, IDataSink>();
				objPairedCollection.put(EnumDataSink.CSERVICES, objIDataSink);
				objCStoreSCP.setDataSinkList(objPairedCollection);

				//Provide the request to C-Store SCP for processing.  processRequest function will process the
				//request and returns the response
				DIMSEResponse objDResponse = objCStoreSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);

				System.out.println("C-Store Response Sent");

			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}

		case C_FIND_RQ:
		{
			System.out.println("C-Find Request Received");
			/*
			 * Create CFIND_SCP get RQ from ClientSession call processRequest and pass C_FIND_RQ on CFIND_SCP
			 * receive response from CFIND_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CFindSCP objCFindSCP = CFindSCP.createInstance(objClientSession); 
			try
			{

				/*
				 * Process CFind Request using the CFindSCP which actually searches the key attribute 
				 * in the data set and sends a CFindRSP pending response containing the dataset and returns final success response.
				 */
				/*
				 * Set Data Source on C-Find SCP.
				 */
				IPairedCollection<EnumDataSource,IDataSource> objPairedCollection = new PairedCollection<EnumDataSource, IDataSource>();
				objPairedCollection.put(EnumDataSource.CSERVICES, objIDataSource);
				objCFindSCP.setDataSourceList(objPairedCollection);
				objDResponse = objCFindSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);
			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}
		case C_MOVE_RQ:
		{
			System.out.println("C-Move Request Received");

			DIMSEResponse objDResponse = null;
			CMoveSCP objCMoveSCP = CMoveSCP.createInstance(objClientSession); 

			try
			{
				if(objSupportedAETitle != null)
				{
					IPairedCollection<String, String> objPairedCollection = objSupportedAETitle;
					String strSupportedAETitle = objPairedCollection.get(new CMoveRQ(objClientSession.getDicomMessage()).getMoveDestination());
					if(strSupportedAETitle != null)
					{
						String[] strArray = strSupportedAETitle.split(":");
						String strIP = strArray[0];
						int iPort = Integer.parseInt(strArray[1]);

						/*
						 * Set C-Store SCP on C-Move SCP.
						 */
						objCMoveSCP.addAETitleInfo(new CMoveRQ(objClientSession.getDicomMessage()).getMoveDestination(), strIP, iPort);
					}
				}

				/*
				 * Set DataSource onC-Move SCP.
				 * Process CMove Request using the CMoveSCP which searches for the key attribute
				 * then act as C-Store SCU , establishes association with C-Store Server , performs 
				 * C-Store and then release the association.
				 * For each C-Store sends the pending response to C-move SCU
				 * After all C-Store request send final response to the C-move SCU.
				 */
				objCMoveSCP.setDataSource(objIDataSource);
				objDResponse = objCMoveSCP.processRequest(objClientSession.getDicomMessage());
				System.out.println("C-Move RSP sent");

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);
				break;
			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}
		case C_GET_RQ:
		{
			System.out.println("C-Get request received");

			/*
			 * Create CGET_SCP and get RQ from ClientSession 
			 * Call processRequest and pass CGET_SCP on CGET_SCP
			 * Receive response from CGET_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CGetSCP objCGetSCP = CGetSCP.createInstance(objClientSession); 

			try
			{

				/*
				 * Set Data Source in C-Get SCP.
				 */
				objCGetSCP.setDataSource(objIDataSource);

				/*
				 * Provide the request to C-Get SCP for processing.  processRequest function will process the request and returns the response
				 */
				objDResponse = objCGetSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);

				System.out.println("C-Get response sent");

			}catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
		}
		default:
			System.out.println("Request not supported");
		}
	}
}
