/*******************************************************************************
 *  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.mediacreation.testcases.defaultscp;
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;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;
import cdac.medinfo.sdk.dcm30_04.baselibs.DataSet;
import cdac.medinfo.sdk.dcm30_04.baselibs.ExplicitDE;
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.commoninterface.IItem;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITransferSyntax;
import cdac.medinfo.sdk.dcm30_04.enums.EnumExecutionStatus;
import cdac.medinfo.sdk.dcm30_04.enums.EnumExecutionStatusInfo;
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.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.scu.CGetSCU;
import cdac.medinfo.sdk.dcm30_04.services.dicomdir.DICOMDIRSinkImpl;
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.IDataSink;
import cdac.medinfo.sdk.dcm30_04.services.mediacreation.dbhelper.MediaCreation_DBAgent;
import cdac.medinfo.sdk.dcm30_04.services.mediacreation.mediacreationsinkimpl.MediaCreation_SinkImpl;
import cdac.medinfo.sdk.dcm30_04.services.mediacreation.mediacreationsourceimpl.IMediaHandler;
import cdac.medinfo.sdk.dcm30_04.services.mediacreation.mediacreationsourceimpl.MediaCreation_SourceImpl;
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.services.scu.commons.SCUSession;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import java.util.Date;

/**
 * This sample code defines the customized implementation of SCP for Media Creation Management Service Class.
 * This code process the request for all services belonging to Media Creation Management service group.
 * Supported services are :
 * 1. N-Create
 * 2. N-Action
 * 3. N-Get
 * This implements all required methods of IProcessHandler for in order to process the request.
 * Connection establishment process : onConnect()
 * Association establishment process : onAssociateReceive()
 * Service Request process : onServiceRequest()
 */
public class Media_Creation_SCP_Def_ProcHandler 
{
	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'.
		//If no directory is defined by user to create logs then it is created in User Temp DIR.
		objDicomConfig.enableLogging(EnumLoggingLevel.INFO, "C:\\DCM_Logs");

		/*
		 * Creates MediaCreation_SCP.
		 */
		MediaCreation_SCP objMediaCreation_SCP = new MediaCreation_SCP();

		/*
		 * Start the MediaCreationSCP.
		 */
		objMediaCreation_SCP.startProcess();
	}
}

/**
 * This class inherits all the lifecycle methods involved in a communication with client.
 * This class is extened from the ProcessHandler class of the SDK for DICOM.
 * ProcessHandler class is the default processor of SDK for DICOM which is capable
 * of handling connection , association and serive requests. 
 * It process the request ,generates response and send it back to SCU.
 * Once the required parameters are set on ProcessHandler class of SDK for DICOM
 * then it will handle all the Association and Service requests.
 */
class MediaCreation_SCP extends ProcessHandler implements IMediaHandler,Runnable
{
	/**
	 * Starts MediaCreation SCP at iPort and local host.
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	Thread MediaWriterThread;
	public MediaCreation_SCP()
	{
		MediaWriterThread = new Thread((Runnable) this);
		MediaWriterThread.start();
	}
	IPairedCollection<String,IDataSet> objDataSetQueue = new PairedCollection<String,IDataSet>();
	boolean stopProcess = false;
	MediaCreation_DBAgent objMediaCreation_DBAgent = null;
	
	public void startProcess()
	{
		try
		{
			/*
			 * Create SCP session.
			 */
			SCPSession objSCPSession = new SCPSession();
			
			/*
			 * Create Sink and Source Impl with ConnectionString and driver String.
			 * MediaCreationDataSourceImpl and MediaCreationDataSinkImpl are default implementation of library which uses JavaDB. 
			 * Sink and Source Impl process the storage and retrieval process for all MediaCreation supported services. 
			 * The user has to start javaDB server before connection creation if Client Server model in java DB is used.
			 * The SQL Script needed to create database is provided at %InstallDir%/SQLScripts/NServiceDB.sql
			 * User has to first create the Database then it should be set in the MediaCreation SCP.
			 * This DataSink and DataSource should be set on process handler class working as MediaCreation SCP.
			 * User can also provide its own implementation of IDataSink and IDataSource interface for MediaCreation.
			 */
			String dataBasePath = "D:/MIG/DICOM/db/MediaCreationSDKdb";
			String ConnectionString = "jdbc:derby://localhost:5050/" + dataBasePath;
			String driverString = "org.apache.derby.jdbc.ClientDriver";

			IDataSink objMediaCreationSink = new MediaCreation_SinkImpl(ConnectionString,driverString);
			MediaCreation_SourceImpl objMediaCreationSource = new MediaCreation_SourceImpl(ConnectionString,driverString);
			objMediaCreationSource.setMediaHandler(this);
			/*
			 * Add Datasink and DataSource on Process Handler.
			 * Here MediaCreationServices_SCP is acting as the Process handler,hence adding Data Sink and Source to Process Handler.
			 */
			addDataSink(EnumDataSink.MEDIA_CREATION,objMediaCreationSink);
			addDataSource(EnumDataSource.MEDIA_CREATION,objMediaCreationSource);

			/*
			 * Initializes the supporting 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[3];
			arrenumServiceType[0] = EnumServiceType.NCREATE_MEDIA_CREATION;
			arrenumServiceType[1] = EnumServiceType.NGET_MEDIA_CREATION;
			arrenumServiceType[2] = EnumServiceType.NACTION_MEDIA_CREATION;
			objSCPSession.selectServices(arrenumServiceType);

			//Set MediaCreationServices_SCP as ProcessHandler.
			objSCPSession.setIProcessHandler(this);
			
			/*
			 * 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.
			 */ 
			//objSCPSession.setTimeOut(5000);

			//Start the Session for MediaCreation SCP
			objSCPSession.start("SDK","192.168.8.93",9000);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	/*
	 * provide implementation to dumps the requested images in DICOM DIR format
	 */
	public void process(String strSOPInstanceUID, IDataSet objIDataSet) {
		System.out.println("Processing of media creation started");
		objDataSetQueue.put(strSOPInstanceUID,objIDataSet);
		synchronized (this)
		{
			notify();	
		}
	}


	/**
	 * This method will be notified by MediaHandlerSourceImpl when a cancellation request is received.
	 * On notification it stops the media creation process.
	 * @param strSOPInstanceUID value of instance uid for which process need to be stopped
	 */
	public void stopProcess(String strSOPInstance)
	{
		stopProcess = true;
	}
	
	/*
	 * Writing files on Disk in DICOM DIR format.
	 * This method internally works as service class user for NGet service and retrieves the files from a NGet SCP.
	 * On NGet SCU DICOM DIR impl is set which dumps the files on disk in DICOM DIR format.
	 */

	public void WriteToCD(String strSOPInstanceUID,String strStoragePath)
	{

		try
		{
			//Creating connection to NGet SCP
			SCUSession objSession = new SCUSession();

			objSession.start("localhost", 7000);

			//Populating Association RQ and establishing association
			EnumServiceType[] arrenumServiceType = new EnumServiceType[1];
			arrenumServiceType[0] = EnumServiceType.CGET;

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

			/*
			 * Populating association request for file 0003.DCM because 
			 * SCU has been implemented to send request for this file only.
			 * User can modify it for other files also.
			 */
			ISingleCollection<String> objTSList = new SingleCollection<String>();
			objTSList.add("1.2.840.10008.1.2.4.50");
			RQPresContext objRQPresContext = new RQPresContext();
			objRQPresContext.setAbstractSyntaxName("1.2.840.10008.5.1.4.1.1.12.1");
			objRQPresContext.setTSCollection(objTSList);

			objDefaultAssociation.addPresContext(objRQPresContext);


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

			if(objSession.sendAssociationMessage(objAssociateRQ))
			{
				IDataSink objIDataSink = new DICOMDIRSinkImpl();
				CGetSCU objCGetSCU = new CGetSCU(objSession); 
				objCGetSCU.setDataSink(objIDataSink);
				objCGetSCU.setDestinationPath(strStoragePath);
				IDataSet objQryDataSet = new DataSet();
				IDictionaryReader objIDictionaryReader = objQryDataSet.getDictionaryReader();
				IDataElement objIDataElement = new ImplicitDE(0x0008,0x0018,strSOPInstanceUID,objIDictionaryReader);
				objQryDataSet.addDataElement(objIDataElement);	

				CGetRQ objCGetRQ = CGetRQ.getDefaultRQ(DicomConstants.DCM_PATIENT_ROOT_QUERY_OR_RETRIVE_INFORMATION_MODEL_GET,objQryDataSet);

				//Sending retrieval request to SCP
				if(objCGetSCU.sendMessage(objCGetRQ))
				{
					System.out.println("Media written on drive");
				}

				AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);
				if(objSession.sendAssociationMessage(objAReleaseRQorRP))
				{
					objSession.close();
				}
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	@Override
	public void run() {

		try
		{
			/*
			 * This thread continuously waiting for media creation process.
			 * It will be notified when a media creation initiation signal is triggered
			 */
			ISingleCollection<String> objSOPInstanceList = null;
			String strMediaCreationInstanceUID = null;
			while(true)
			{
				synchronized(this)
				{
					try
					{
						wait();
					}
					catch(InterruptedException ioe)
					{
						ioe.printStackTrace();
					}
				}

				short i = 0;
				while(!stopProcess)
				{
					if(objDataSetQueue != null && objDataSetQueue.size() != 0)
					{
						/*
						 * Retrieving MediaCreation Dataset using SOP Instance UID.
						 * It retrieves sop instance uid of all images which are requested for media creation 
						 * and populated the list of SOPInstances which will be used for retrieval.
						 */
						Object []arrObject = objDataSetQueue.getKeys();
						strMediaCreationInstanceUID = arrObject[0].toString();
						IDataSet objIDataset = objDataSetQueue.get(strMediaCreationInstanceUID);
						System.out.println("Dataset Procesed");
						objDataSetQueue.remove(strMediaCreationInstanceUID);
						if(objIDataset.isPresent(0x0008,0x1199))
						{
							if(objSOPInstanceList == null)
								objSOPInstanceList = new SingleCollection<String>();
							ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataset.getValue(0x0008,0x1199);
							for(IItem objItem : objItemList)
							{
								String strSOPInstance = objItem.getValue(0x0008,0x1155).toString();
								objSOPInstanceList.add(strSOPInstance);
							}
						}
					}
					if(objSOPInstanceList != null && objSOPInstanceList.size() != 0)
					{
						/*
						 * Retrieving images from NGet SCP and dumping on disk in DICOM DIR format
						 */
						String strStoragePath = "D://testing/" + ((new Date()).getTime());
						WriteToCD(objSOPInstanceList.elementAt(0),strStoragePath);
						objSOPInstanceList.remove(0);
						i++;
						updateScheduledInstanceInfo(strMediaCreationInstanceUID,i,EnumExecutionStatus.CREATING.getValue(),EnumExecutionStatusInfo.EXECUTION_STATUS_DONE_OR_PRINTING.getValue());
						System.out.println("Media Written :: " + i);
					}
					else
					{
						break;
					}

				}
				if(objSOPInstanceList.size() == 0)
				{
					/*
					 * If all requested images has been processed then completion information will be updated in database
					 */
					updateScheduledInstanceInfo(strMediaCreationInstanceUID,i,EnumExecutionStatus.DONE.getValue(),EnumExecutionStatusInfo.EXECUTION_STATUS_DONE_OR_PRINTING.getValue());
					objSOPInstanceList = null;
					stopProcess = false;
				}
				else if(stopProcess && objSOPInstanceList.size() != 0)
				{
					/*
					 * If cancel operation has been triggered than failure information will be updated in database
					 */
					updateScheduledInstanceInfo(strMediaCreationInstanceUID,i,EnumExecutionStatus.FAILURE.getValue(),EnumExecutionStatusInfo.EXECUTION_STATUS_DONE_OR_PRINTING.getValue());
					objSOPInstanceList = null;
					stopProcess = false;
				}
			}

		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	
	}
	
	public void updateScheduledInstanceInfo(String strSOPInstanceUID,short noOfCompletedMedia,String strExecutionStatus,String strExecutionInfo)
	{
		try
		{
			/*
			 * Creating dataset using provided information and sending it for updation using DBAgent.
			 */
			IDataSet objSchedulingDataset = new DataSet();
			ITransferSyntax objITransferSyntax = objSchedulingDataset.getTransferSyntax();
			IDictionaryReader objIDictionaryReader = objSchedulingDataset.getDictionaryReader();

			objSchedulingDataset.addDataElement(getDataElement(0x2100,0x0020, strExecutionStatus, objITransferSyntax, objIDictionaryReader));
			objSchedulingDataset.addDataElement(getDataElement(0x2100,0x0030, strExecutionInfo, objITransferSyntax, objIDictionaryReader));
			objSchedulingDataset.addDataElement(getDataElement(0x2200,0x000B, noOfCompletedMedia, objITransferSyntax, objIDictionaryReader));

			if(objMediaCreation_DBAgent == null)
			{
				String dataBasePath = "D:/DataBase/MediaCreationSDKdb";
				String ConnectionString = "jdbc:derby://localhost:5050/" + dataBasePath;
				String driverString = "org.apache.derby.jdbc.ClientDriver";
				objMediaCreation_DBAgent = MediaCreation_DBAgent.createInstance(ConnectionString,driverString);
			}

			objMediaCreation_DBAgent.updateScheduledInstanceInfo(objSchedulingDataset,strSOPInstanceUID);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	//Creating DataElement using provided information
		private IDataElement getDataElement(int GroupNo,int ElementNo,Object objVal,ITransferSyntax objITransferSyntax,IDictionaryReader objDictionaryReader)
		{
			IDataElement objIDataElement = null;
			int iVRType = 0;
			if(objITransferSyntax.getVRTransferSyntax() == DicomConstants.DCM_VR_IMPLICIT)
				objIDataElement = new ImplicitDE(GroupNo,ElementNo,objVal,objITransferSyntax,objDictionaryReader);
			else
			{
				iVRType = StandardMethods.getiVRtype(objDictionaryReader.getVR(GroupNo, ElementNo));
				objIDataElement = new ExplicitDE(GroupNo,ElementNo,iVRType,objVal,objITransferSyntax,objDictionaryReader);
			}

			return objIDataElement;
		}
}
