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

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Locale;


import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
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.dicomnetcommons.EnumServiceType;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ICommandSet;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidDICOMMessageException;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.ServiceMessageEvent;
import cdac.medinfo.sdk.dcm30_04.services.ndelete.commons.NDeleteRQ;
import cdac.medinfo.sdk.dcm30_04.services.scu.commons.SCU;
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.DicomLogger;

import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;

/**
 * NDeleteSCU works as a DIMSE Service User for NCreate Service.
 * The N-Delete service is used by a DIMSE-service-user to request a peer DIMSE-service-user to delete
 * a SOP Instance, complete with its identification and the values of its associated Attributes,
 * and simultaneously to unregister its identification.
 * It is a confirmed service.
 * It processes the arrived N-Delete Request and generates the response.
 * It sends the N-Delete Request and receives response.
 */
public class NDeleteSCU extends SCU 
{
	

	/** The i msg id. */
	static short iMsgID = 0;

	/**
	 * The IP and port to which user connects.
	 * @param strIP String value of IP to which user connects.
	 * @param port  Port to which user connects.
	 * @throws IOException Signals IO Exception if IO operation fails.
	 */
	public NDeleteSCU(String strIP, int port) throws IOException
	{
		super(new InetSocketAddress(strIP,port));
	}

	/**
	 * The InetAddress and port to which user connects.
	 * @param addr InetAddress to which user connects.
	 * @throws IOException Signals IO Exception if IO operation fails.
	 */
	public NDeleteSCU(InetSocketAddress addr) throws IOException
	{
		super(addr);
	}

	/**
	 * Instantiates a new NDelete SCU on the SCU session provided by user.
	 * @param objSCUSession the object of SCU session on which  NDelete SCU is to be initiated.
	 */
	public NDeleteSCU(SCUSession objSCUSession) 
	{
		super(objSCUSession);
	}

	/**
	 * This method creates association request according to the DataSet collection provided by the user and the Default association.
	 * Then it will perform the requested operation on the data set provided by the user, release the association and close the connection.
	 * If the session is already created by the user then session is not created nor it is released.
	 * Similarly if the association is established by the user then it is not released.
	 * @param strcallingAETitle value of Calling AE Title.
	 * @param strCalledAETitle value of Called AE Title.
	 * @param strSOPClasUID value of SOP Class UID.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(String strcallingAETitle,String strCalledAETitle,String strSOPClasUID,String strSOPInstanceUID) throws IOException
	{
		DefaultAssociation objDefaultAssociation = null;
		EnumServiceType[] arrenumServiceType = new EnumServiceType[7];
		arrenumServiceType[0] = EnumServiceType.NDELETE_BASIC_COLOR_PRINT_MANAGEMENT;
		arrenumServiceType[1] = EnumServiceType.NDELETE_BASIC_GRAYSCALE_PRINT_MANAGEMENT;
		arrenumServiceType[2] = EnumServiceType.NDELETE_BASIC_PRINT_IMAGE_OVERLAY;
		arrenumServiceType[3] = EnumServiceType.NDELETE_PRESENTATION_LUT;
		arrenumServiceType[4] = EnumServiceType.NDELETE_PULLPRINT;
		arrenumServiceType[5] = EnumServiceType.NDELETE_RT_CONVENTIONAL_MACHINE_VERIFICATION;
		arrenumServiceType[6] = EnumServiceType.NDELETE_RT_ION_MACHINE_VERIFICATION;
		
		objDefaultAssociation = new DefaultAssociation(strcallingAETitle);
		objDefaultAssociation.loadAssociation(arrenumServiceType);
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAETitle);
		send( objAssociateRQ, strSOPClasUID, strSOPInstanceUID);
	}

	/**
	 * Sends the available datasets to the SCP. While sending the datasets, if the connection is not made to the
	 * destination SCP, then the SCU first creates the connection. If the association is not created with the
	 * destination SCP, then the SCU first establishes the association and then sends the datasets.
	 * Note:
	 * The SCU will not close the connection, if already created connection is given to the SCU in the form of SCUSession.
	 * The SCU will neither release the association nor close the connection, if the association is already established
	 * with the destination SCP through SCUSession. User has to explicitly release the association and close the connection
	 * after completion of send process of NDELETE SCU.
	 * 
	 * @param objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @param strSOPClasUID value of SOP Class UID.
	 * @param objDataSetCollection Object of IDataSet Collection.
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(AssociateRQ objAssociateRQ,String strSOPClasUID,String strSOPInstanceUID) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ( objAssociateRQ, strSOPClasUID, strSOPInstanceUID);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ( objAssociateRQ, strSOPClasUID, strSOPInstanceUID);

			if(!objSCUSession.getAssociationStatus())
			{
				if(objSCUSession.close())
					objSCUSession = null;
			}
		}
	}

	/**
	 * Send request.
	 * @param objAssociateRQ the object associate request
	 * @param objDataSetCollection the object data set collection
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void sendRQ(AssociateRQ objAssociateRQ,String strSOPClasUID,String strSOPInstanceUID) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}
		NDeleteRQ objNDeleteRQ = NDeleteRQ.getDefaultRQ(strSOPClasUID, strSOPInstanceUID);
		sendMessage(objNDeleteRQ);
		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}

	/**
	 * This method sends the NDelete Request.
	 * @param objDicomMessage  The Dicom Message containing NDelete request.
	 * @return true, if  message  send
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public boolean sendMessage(DicomMessage objDicomMessage) throws IOException 
	{
		/* 
		 * At the time of sending message the SCU should register itself as message listener to SCU session.
		 * because if multiple request are send through the SCU session then when response is received which
		 * SCU should be notified. If Message listener is set at time of sending message then in case of sync mode 
		 * the response is received to the proper SCU.
		 */
		associateStatus = objSCUSession.getAssociationStatus();
		objSCUSession.setMessageListener(this);
		boolean isConnected = objSCUSession.connectionState();
		
		@SuppressWarnings("unused")
		String strSOPClassUID = null;
		
//		 check for null value of object of DicomMessage if DicomMessage is null then throw InvalidDICOMMessageException.
		if(objDicomMessage == null)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","InvalidDicomMessage",new Locale(DicomConfig.createInstance().getLocale())));
			}
			throw new InvalidDICOMMessageException(ResourceBundleWrapper.getValue("exceptions","InvalidCommandField",new Locale(DicomConfig.createInstance().getLocale())));
		}
		else
		{
//			 Checks for service type.
			if(objDicomMessage.getCommandType() != EnumCommandField.N_DELETE_RQ)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning","RQNotMatched",new Locale(DicomConfig.createInstance().getLocale()))+ objDicomMessage.getCommandType().name());
				}
				return false;
			}
		}

		if(isConnected)
		{
			if(associateStatus)
			{
				isResponseReceived = false;
				if(objAgreedAssociation == null)
					objAgreedAssociation = objSCUSession.getAgreedAssociation();

				/*
				 * This block of code is verifying that TS supported in Association and TS of DataSet is same or not.
				 * If TS are not same then Message will not be sent and fail success will be returned.
				 */ 
				IDataSet objIDataSet = objDicomMessage.getDataSet();
				ICommandSet objICommandSet = objDicomMessage.getCommandSet();
				if(objIDataSet != null)
				{
					String strTS = objIDataSet.getTransferSyntax().getTransferSyntaxUID();
					String strAffectedSOPClassUID = objICommandSet.getValue(0x0000, 0x0002).toString();

					if(strTS == null || strAffectedSOPClassUID == null || !(objAgreedAssociation.isSOPClassUIDSupported(strAffectedSOPClassUID, strTS)))
					{
						return false;
					}
				}

				objSCUSession.sendServiceMessage(objDicomMessage);
				try
				{
					synchronized(this)
					{
						if(associateStatus && !isResponseReceived && !objAgreedAssociation.isAsyncMode())
						{
							this.wait();
						}
					}
				}
				catch(InterruptedException ie)
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ie);
					}					
				}
				return associateStatus;
			}
			else
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","Association.AssociationDoesNotExist",new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
		}
		else
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
			}
		}
		return false;
	}
	
	/**
	 * It processes the received Dicom Message ,retrieved from event generated, according to it's Command Type.
	 * 
	 * @param objServiceMessageEvent instance of ServiceMessageEvent
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void receiveMessage(ServiceMessageEvent objServiceMessageEvent) throws IOException
	{
		DicomMessage objDicomMessage = objServiceMessageEvent.getDicomMessge();
		objDicomMessage.getDataSet();
		//objSCUSession.getServiceAgent().dispose();

		//This will log message, if in case Dicom Message with unexpected Command field is received.
		if(objDicomMessage.getCommandType() != EnumCommandField.N_DELETE_RSP)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "InvalidDicomMessage",new Locale(DicomConfig.createInstance().getLocale())));
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "UnexpectedCommandField",new Locale(DicomConfig.createInstance().getLocale())) + objDicomMessage.getCommandType().name());
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "ExpectedCommandField",new Locale(DicomConfig.createInstance().getLocale())) + EnumCommandField.N_DELETE_RSP.name());
			}
		}
			
		super.receiveMessage(objServiceMessageEvent);
	}
}
