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

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


import cdac.medinfo.sdk.dcm30_04.commoninterface.ITag;
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.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.nget.commons.NGetRQ;
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.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;

/**
 * NGetSCU works as a DIMSE Service User for NGet Service.
 * The N-GET service is used by a DIMSE-service-user to retrieve Attribute values from a peer DIMSEservice-user. 
 * It is a confirmed service.
 * It sends the NGet Request and receives response.
 */
public class NGetSCU extends SCU
{
	

	/** The i msg id. */
	static short shMsgID = 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 NGetSCU(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 NGetSCU(InetSocketAddress addr) throws IOException
	{
		super(addr);
	}

	/**
	 * Instantiates a new NGet SCU on the SCU session provided by user.
	 * @param objSCUSession the object of SCU session on which  NGet SCU is to be initiated.
	 */
	public NGetSCU(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.
	 * @param objITagCollection list of requested attributes
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(String strcallingAETitle,String strCalledAETitle,String strSOPClasUID,String strSOPInstanceUID, ISingleCollection<ITag> objITagCollection) throws IOException
	{
		DefaultAssociation objDefaultAssociation = null;

		EnumServiceType[] arrenumServiceType = new EnumServiceType[20];
		arrenumServiceType[0] = EnumServiceType.NGET_GPPPS;
		arrenumServiceType[1] = EnumServiceType.NGET_MPPS;
		arrenumServiceType[2] = EnumServiceType.NGET_BASIC_COLOR_PRINT_MANAGEMENT;
		arrenumServiceType[3] = EnumServiceType.NGET_BASIC_GRAYSCALE_PRINT_MANAGEMENT;
		arrenumServiceType[4] = EnumServiceType.NGET_PRINT_JOB;
		arrenumServiceType[5] = EnumServiceType.NGET_PRINTER_CONFIGURATION_RETRIEVAL;
		arrenumServiceType[6] = EnumServiceType.NGET_PULLPRINT;
		arrenumServiceType[7] = EnumServiceType.NGET_PRINT_QUEUE_MANAGEMENT;
		arrenumServiceType[8] = EnumServiceType.NGET_DETACHED_RESULT_MANAGEMENT;
		arrenumServiceType[9] = EnumServiceType.NGET_DETACHED_INTERPRETATION_MANAGEMENT;
		arrenumServiceType[10] = EnumServiceType.NGET_DETACHED_RESULT_MANAGEMENT_META_SOP;
		arrenumServiceType[11] = EnumServiceType.NGET_MEDIA_CREATION;
		arrenumServiceType[12] = EnumServiceType.NGET_DETACHED_PATIENT_MANAGEMENT;
		arrenumServiceType[13] = EnumServiceType.NGET_DETACHED_VISIT_MANAGEMENT;
		arrenumServiceType[14] = EnumServiceType.NGET_DETACHED_PATIENT_MANAGEMENT_META_SOP;
		arrenumServiceType[15] = EnumServiceType.NGET_STUDY_MANAGEMENT_META;
		arrenumServiceType[16] = EnumServiceType.NGET_DISPLAY_SYSTEM_MANAGEMENT;
		arrenumServiceType[17] = EnumServiceType.NGET_RT_CONVENTIONAL_MACHINE_VERIFICATION;
		arrenumServiceType[18] = EnumServiceType.NGET_RT_ION_MACHINE_VERIFICATION;
		arrenumServiceType[19] = EnumServiceType.NGET_UPS;
		objDefaultAssociation = new DefaultAssociation(strcallingAETitle);
		objDefaultAssociation.loadAssociation(arrenumServiceType);
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAETitle);
		send(objAssociateRQ,strSOPClasUID,strSOPInstanceUID,objITagCollection);
	}

	/**
	 * 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 CSTORE SCU.
	 * 
	 * @param objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @param objITagCollection list of requested attributes
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(AssociateRQ objAssociateRQ,String strSOPClasUID,String strSOPInstanceUID,ISingleCollection<ITag> objITagCollection) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ( objAssociateRQ, strSOPClasUID, strSOPInstanceUID, objITagCollection);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ( objAssociateRQ, strSOPClasUID, strSOPInstanceUID,objITagCollection);

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


	/**
	 * Send Request.
	 * @param objAssociateRQ the object associate request
	 * @param strSOPClasUID  SOP Class UID
	 * @param strSOPInstanceUID SOP Instance UID.
	 * @param objITagCollection list of requested attributes
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void sendRQ(AssociateRQ objAssociateRQ,String strSOPClasUID,String strSOPInstanceUID,ISingleCollection<ITag> objITagCollection) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}
		NGetRQ objNGetRQ = NGetRQ.getDefaultRQ(strSOPClasUID, strSOPInstanceUID,objITagCollection);
		sendMessage(objNGetRQ);
		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}

	/**
	 * This method sends the NGet Request.
	 * @param objDicomMessage  The Dicom Message containing NGet 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_GET_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();

				if(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.getMessage());
						}						
					}
					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_GET_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_GET_RSP.name());
			}
		}
		
		super.receiveMessage(objServiceMessageEvent);
	}
}
