/*******************************************************************************
 *  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.withdb.testcases.customizedscp;

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


import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.exceptions.DicomException;
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.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.cecho.scp.CEchoSCP;
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.dicomnetcommons.EnumAbortReason;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumAbortSource;
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.EnumErrorCode;
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.javadbhelper.DataSinkImpl;
import cdac.medinfo.sdk.dcm30_04.services.javadbhelper.DataSourceImpl;
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.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;

/**
 * This is master test case for SCP which works as SCP for All C-Services.
 * This test code is Multi Threaded in nature.
 * Once the Association or Service request is arrived thread is started to process the 
 * request. In this way the ProcessHandler return each event call as soon as possible.
 */
public class AllCService_SCP_MultiThreaded_Cust_ProcHandler 
{
	public static void main(String[] agrs)
	{
		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.SEVERE, "c:/DCM_Logs");

			/*
			 * Creates Request handler.
			 * Set the parameters required to process Service requests on it.
			 */
			DIMSERQHandler scp = new DIMSERQHandler();

			/*
			 * Set the Storage path.
			 */
			scp.setStoragePath("C:/StoragePath");

			String dataBasePath = "D:/Database/SDKdb";
			String ConnectionString = "jdbc:derby://localhost:5050/" + dataBasePath;

			String driverString = "org.apache.derby.jdbc.ClientDriver";

			/*
			 * Create the DataBase connection and set the DataSink and Data Source on SCP .
			 */
			IDataSink objIDataSink = new DataSinkImpl(ConnectionString,driverString);
			scp.addDataSink(EnumDataSink.CSERVICES, objIDataSink);
			IDataSource objIDataSource = new DataSourceImpl(ConnectionString,driverString);
			scp.addDataSource(EnumDataSource.CSERVICES, objIDataSource);


			/*
			 * Add third party Supported AE Title for C-Move operation.
			 */
			scp.addSupportedAETitles("DCMRCV", "192.168.8.98", 9002);

			/*
			 * Created SCP session and set Default association and Process Handler on it.
			 */
			SCPSession session = new SCPSession();

			/*
			 * 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.
			 */ 
			//session.setTimeOut(5000);

			/*
			 * 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[5];
			arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
			arrenumServiceType[1] = EnumServiceType.CFIND_DEFAULT;
			arrenumServiceType[2] = EnumServiceType.CECHO;
			arrenumServiceType[3] = EnumServiceType.CMOVE;
			arrenumServiceType[4] = EnumServiceType.CGET;
			session.selectServices(arrenumServiceType);

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

			//Start the Session
			session.start("SDK",9000);
		}
		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 separate 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);
	}
	
	@Override
	public void onException(ClientSession objClientSession, DicomException ex,EnumErrorCode ErrorCode) 
	{
		// TODO Auto-generated method stub
		ExceptionHandler objServiceRQHandler = new ExceptionHandler(objClientSession, ex,ErrorCode);
		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;
		}
		}
	}
}


class ExceptionHandler implements Runnable
{


	ClientSession objClientSession = null;
	DicomException ex = null;
	EnumErrorCode errorCode;
	public ExceptionHandler(ClientSession objClientSession,DicomException ex,EnumErrorCode errorCode)
	{
		this.objClientSession = objClientSession;
		this.ex = ex;
		this.errorCode = errorCode;
	}

	public void run()
	{
		/**
		 * Gets automatically invoked when exception occur while processing  data received  
		 * @param objClientSession - object of Client Session.
		 * @param ex - the exception object
		 * @param ErrorCode - the reason of the DicomException
		 */
		System.out.println("In On Exception for : "+errorCode.toString());
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ex.getMessage() + " " +errorCode.toString());
			}
			switch(errorCode)
			{
			case REMOTE_CONNECTION_CLOSED:
			case PDUREADER_CLOSED:
			case SOCKET_TIMEOUT:
				objClientSession.close();
			case INVALID_COMMANDSET:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				break;
			case INVALID_PDUTYPE:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				break;
			case INVALID_ASSOCIATION_PRIMITIVE:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				break;
			case AGRRED_PDU_LENGTH_VIOLATED:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				break;
			case ASSOCIATION_NOT_EXISTS:
			break;
			case UNEXPECTED_DIMSE_ASSOCIATION:
				break;
			case UNKNOWN:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				break;
			case INVALID_DICOM_MESSAGE:
				if(objClientSession.getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					objClientSession.sendAssociationMessage(objAAbort);
				}
				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_ECHO_RQ:
		{
			System.out.println("C-Echo Request Received");
			/*
			 * Create CECHO_SCP get RQ from ClientSession call processRequest and pass C_ECHO_RQ on CECHO_SCP
			 * receive response from CECHO_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CEchoSCP objCEchoSCP = CEchoSCP.createInstance(objClientSession); 
			try
			{
				/*
				 * Process CEcho Request.
				 */
				objDResponse = objCEchoSCP.processRequest(objClientSession.getDicomMessage());

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

				System.out.println("C-Echo Response Sent");
			}
			catch(IOException ioe)
			{
				ioe.printStackTrace();
			}
			break;
		}

		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
			{
				IPairedCollection<String, String> objPairedCollection = objSupportedAETitle;
				String strSupportedAETitle = objPairedCollection.get(new CMoveRQ(objClientSession.getDicomMessage()).getMoveDestination());
				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();
			}
		}
		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();
			}
		}
		}
	}
}
