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

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.enums.EnumSOPClassUIDs;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDataSetException;
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.EnumSCUType;
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.ncreate.commons.NCreateRQ;
import cdac.medinfo.sdk.dcm30_04.services.nget.scu.NGetSCU;
import cdac.medinfo.sdk.dcm30_04.services.nset.scu.NSetSCU;
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.DicomConstants;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;

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

/**
 * UPS_SCU works as a Service Class User for Unified Procedure Step Service and SOP Classes.<br>
 * UPS_SCU sends the DIMSE service request for supported N-Service and handles the response.
 */

public class UPS_SCU extends SCU
{

	/** The i msg id. */
	static short iMsgID = 0;
	private  final String fSendMessage ="sendMessage";
	private final String fDIMSEResponse = "setDIMSEResponse";
	private final static String DICOM_SINGLE_COLLECTION_QUALIFIED_NAME = "cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection";

	/**
	 * 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 UPS_SCU(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 UPS_SCU(InetSocketAddress addr) throws IOException
	{
		super(addr);
	}

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

	/**
	 * Instantiate scp.
	 * 
	 * @param enumClassname the enum classname
	 * @param objSession instance of SCUSession
	 * @return SCU - instance of SCU
	 * @throws ClassNotFoundException the class not found exception
	 * @throws InvocationTargetException the invocation target exception
	 * @throws IllegalAccessException the illegal access exception
	 * @throws NoSuchMethodException the no such method exception
	 */
	@SuppressWarnings("unchecked")
	private SCU instantiateSCU(EnumSCUType enumClassname, SCUSession objSession)throws ClassNotFoundException,NoSuchMethodException,InvocationTargetException,InstantiationException,IllegalAccessException
	{
		Class fSCU = Class.forName(enumClassname.getValue());
		Class[] fSCUSession = new Class[]{SCUSession.class};
		Object[] objSCUSession = new Object[]{objSession};
		Constructor objArgsConstructor = fSCU.getConstructor(fSCUSession);	
		SCU objSCU = (SCU)objArgsConstructor.newInstance(objSCUSession);
		return objSCU;
	}

	/**
	 * 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.
	 * @param strcallingAETitle value of Calling AE Title
	 * @param strCalledAETitle value of Called AE Title
	 * @param objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @param objIDataSet Object of IDataSet
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(String strcallingAETitle,String strCalledAETitle,String strSOPInstanceUID, IDataSet objIDataSet ) throws IOException
	{
		DefaultAssociation objDefaultAssociation = null;
		EnumServiceType[] arrenumServiceType = new EnumServiceType[1];
		arrenumServiceType[0] = EnumServiceType.NCREATE_UPS;

		objDefaultAssociation = new DefaultAssociation(strcallingAETitle);
		objDefaultAssociation.loadAssociation(arrenumServiceType);
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAETitle);
		send(objAssociateRQ,strSOPInstanceUID,objIDataSet);
	}

	/**
	 * Sends the available data sets to the SCP. While sending the data sets, 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 data sets.
	 * 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 objIDataSet Object of IDataSet
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(AssociateRQ objAssociateRQ,String strSOPInstanceUID,IDataSet objIDataSet) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ( objAssociateRQ,strSOPInstanceUID, objIDataSet);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ( objAssociateRQ, strSOPInstanceUID, objIDataSet);

			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 objDataSetCollection the object data set collection
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void sendRQ(AssociateRQ objAssociateRQ,String strSOPInstanceUID,IDataSet objIDataSet) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}

		NCreateRQ objNCreateRQ = NCreateRQ.getDefaultRQ(DicomConstants.DCM_UPS_CREATE, strSOPInstanceUID, objIDataSet);
		sendMessage(objNCreateRQ);

		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}

	/**
	 * This method sends the Request for UPS.
	 * @param objDicomMessage  The Dicom Message containing Request for UPS.
	 * @return true, if send message
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	@SuppressWarnings("unchecked")
	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.
		 */
		try
		{
			associateStatus = objSCUSession.getAssociationStatus();
			objSCUSession.setMessageListener(this);
			boolean isConnected = objSCUSession.connectionState();

			if(isConnected)
			{
				if(associateStatus)
				{
					EnumCommandField enumRequestType = objDicomMessage.getCommandType();
					switch(enumRequestType)
					{
					case N_CREATE_RQ :
					{
						/*
						 * In this code it referring NCreate SCP through reflection
						 * so that if our NCreate SCU is not present then it can throw exception.
						 */
						String strAffectedSOPClassUID = objDicomMessage.getCommandSet().getValue(0x0000,0x0002).toString();
						if(!DicomConstants.DCM_UPS_CREATE.equals(strAffectedSOPClassUID))
							throw new InvalidDICOMMessageException(ResourceBundleWrapper.getValue("exceptions","InvalidCommandSet",new Locale(DicomConfig.createInstance().getLocale())));

						//Instantiating SCU through reflection
						SCU objNCreateSCU = instantiateSCU(EnumSCUType.NCREATE_SCU,objSCUSession);
						Class cObjectClass = objNCreateSCU.getClass();

						//search for setDimseResponse method in NCreateSCU and invoke it.
						//Setting response collection of UPS_SCU to NCreate SCU
						Class cSingleCollection = Class.forName(DICOM_SINGLE_COLLECTION_QUALIFIED_NAME);
						Method mSetDimseResponse = cObjectClass.getMethod(fDIMSEResponse, cSingleCollection);
						mSetDimseResponse.invoke(objNCreateSCU, objDIMSERSPCollection);

						//search for sendMessage() method in NCreateSCU and invoke it.
						Class cDicomMessage = Class.forName(DicomConstants.DICOM_MESSAGE_QUALIFIED_NAME);
						Method mSendMessage = cObjectClass.getMethod(fSendMessage, cDicomMessage);
						boolean status = (Boolean)mSendMessage.invoke(objNCreateSCU, objDicomMessage);

						return status;
					}
					case N_ACTION_RQ :
					{
						/*
						 * In this code it referring NCreate SCP through reflection
						 * so that if our NCreate SCU is not present then it can throw exception.
						 */
						String strAffectedSOPClassUID = objDicomMessage.getCommandSet().getValue(0x0000,0x0003).toString();
						if(!(DicomConstants.DCM_UPS_CREATE.equals(strAffectedSOPClassUID)||DicomConstants.DCM_UPS_PULL.equals(strAffectedSOPClassUID)))
							throw new InvalidDICOMMessageException(ResourceBundleWrapper.getValue("exceptions","InvalidCommandSet",new Locale(DicomConfig.createInstance().getLocale())));

						//Instantiating SCU through reflection
						SCU objNActionSCU = instantiateSCU(EnumSCUType.NACTION_SCU,objSCUSession);
						Class cObjectClass = objNActionSCU.getClass();

						//search for setDimseResponse method in NCreateSCU and invoke it.
						//Setting response collection of UPS_SCU to NCreate SCU
						Class cSingleCollection = Class.forName(DICOM_SINGLE_COLLECTION_QUALIFIED_NAME);
						Method mSetDimseResponse = cObjectClass.getMethod(fDIMSEResponse, cSingleCollection);
						mSetDimseResponse.invoke(objNActionSCU, objDIMSERSPCollection);

						//search for sendMessage() method in NCreateSCU and invoke it.
						Class cDicomMessage = Class.forName(DicomConstants.DICOM_MESSAGE_QUALIFIED_NAME);
						Method mSendMessage = cObjectClass.getMethod(fSendMessage, cDicomMessage);
						boolean status = (Boolean)mSendMessage.invoke(objNActionSCU, objDicomMessage);

						return status;
					}
					case N_SET_RQ :
					{
						
						NSetSCU objNSetSCU = new NSetSCU(objSCUSession);
						objNSetSCU.setDIMSEResponse(objDIMSERSPCollection);
						boolean status = objNSetSCU.sendMessage(objDicomMessage);
						return status;
						//	
					}
					case N_GET_RQ :
					{
						NGetSCU objNGetSCU = new NGetSCU(objSCUSession);
						objNGetSCU.setDIMSEResponse(objDIMSERSPCollection);
						boolean status = objNGetSCU.sendMessage(objDicomMessage);
						return status;
					}
					default :
						if(DicomConfig.createInstance().isLoggingOn())
						{
							DicomLogger objDicomLogger = DicomLogger.getLogger();
							objDicomLogger.log(ResourceBundleWrapper.getValue("warning","ServiceRequestNotSupported",new Locale(DicomConfig.createInstance().getLocale()))+ enumRequestType.toString());
						}
					}
				}
				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())));
				}
			}
		}
		catch(InvalidDataSetException ide)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(ide);
			throw new InvalidDICOMMessageException(ide.getMessage(),ide);
		}
		catch(ClassNotFoundException cnfe)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(cnfe.getMessage());
			}
		}
		catch(NoSuchMethodException nsme)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(nsme.getMessage());
			}
		}
		catch(IllegalAccessException iae)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(iae.getMessage());
			}
		}
		catch(InvocationTargetException iae)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(iae.getMessage());
			}
		}
		catch(InstantiationException iae)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(iae.getMessage());
			}
		}
		return false;
	}

	/**
	 * Validates the N-Set Data Set of the SOP Class UID specified.
	 * @param strAffectedSOPClassUID SOP class UID for which DataSet needs to be validated for N-Set RQ.
	 * @param objIDataSet instance of dataset which is to be validated.
	 * @return  boolean - true if the dataset is validated successfully. Else return false.
	 * @throws InvalidDataSetException Signals if dataset validation fails.
	 */
	public boolean validateNSetRQ(String strAffectedSOPClassUID) throws InvalidDataSetException
	{
		return true;
	}

}
