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

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDataSetException;
import cdac.medinfo.sdk.dcm30_04.exceptions.MandatoryMissingException;
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.cstore.commons.CStoreRQ;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumPriority;
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.ICommandSet;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidDICOMMessageException;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.CommandSet;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
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.ICollectionIterator;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;
/**
 * 	SRS_SCU works as a Service Class User for Structured Reporting Storage Service Class.<br>
 * 	SRS Service Class includes C-STORE service as specified by DIMSE service group.
 * 	The Structured Reporting Storage SOP Classes extend the functionality of the Storage Service
 *	class to extend the SCP behavior and conformance requirements.
 * 	SRS_SCU sends the DIMSE service request for supported C-Service (C_StoreE) and handles the response.
 *
 */
public class SRS_SCU extends SCU
{
	/** The i msg id. */
	static short iMsgID = 0;
	@SuppressWarnings("unused")
	private  final String fSendMessage ="sendMessage";
	@SuppressWarnings("unused")
	private final String fDIMSEResponse = "setDIMSEResponse";
	@SuppressWarnings("unused")
	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 SRS_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 SRS_SCU(InetSocketAddress addr) throws IOException
	{
		super(addr);
	}

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

	/**
	 * Instantiate SCP.
	 * @param enumClassname the enum class name
	 * @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", "unused" })
	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 objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param strCallingAETitle  Calling AE Title.
	 * @param strCalledAETitle  Called AE Title.
	 * @param objDataSetCollection Object of IDataSet.
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(String strCallingAETitle,String strCalledAETitle,ISingleCollection<IDataSet> objDataSetCollection ) throws IOException
	{
		DefaultAssociation objDefaultAssociation = null;
		EnumServiceType[] arrenumServiceType = new EnumServiceType[1];
		
		arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
		
		objDefaultAssociation = new DefaultAssociation(strCallingAETitle);
		objDefaultAssociation.loadAssociation(arrenumServiceType);
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAETitle);
		send(objAssociateRQ,  objDataSetCollection);
	}
	
	/**
	 * 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 SRS_SCU.
	 * 
	 * @param objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param objEnumCommandField  Enum Represents Element Command Field.
	 * @param strSOPInstanceUID  SOP Instance UID.
	 * @param objIDataSet Object of IDataSet
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(AssociateRQ objAssociateRQ, ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ(objAssociateRQ, objDataSetCollection);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ(objAssociateRQ, objDataSetCollection);

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

	/**
	 *  Sends Association request, if the association is not already created.
	 *  Then sends the C-Store request.
	 *  If the association request is sent then it is release.
	 *  @param objAssociateRQ the object associate request.
	 *  @param objDataSetCollection the object of ISingleCollection.
	 *  @throws IOException Signals IOException if IO operation fails.
	 */
	private void sendRQ(AssociateRQ objAssociateRQ, ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}
		
		ISingleCollection<CStoreRQ> objCStoreRQCollection = getDefaultRQCollection(objDataSetCollection);
		
		sendRequests(objCStoreRQCollection);
		
		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}
	
	/**
	 * Gets the request collection from the data set collection.
	 */
	private ISingleCollection<CStoreRQ> getDefaultRQCollection(ISingleCollection<IDataSet> objDataSetCollection)
	{
		ISingleCollection<CStoreRQ> objCStoreRQCollection = new SingleCollection<CStoreRQ>();
		
		for(IDataSet objIDataSet: objDataSetCollection)
		{
			CStoreRQ objCStoreRQ = CStoreRQ.getDefaultRQ(objIDataSet);
			objCStoreRQCollection.add(objCStoreRQ);
		}
		return objCStoreRQCollection;
	}
	
	/**
	 * This method makes CStore RQ's according to the given DataSet Collection.
	 * 
	 * @param objIDataSet object of DataSet to be set in C-Store RQ.
	 * 
	 * @return ISingleCollection<CStoreRQ> Collection of CStore RQ
	 */
	public static CStoreRQ getDefaultRQ(IDataSet objIDataSet)
	{
		CStoreRQ objCStoreRQ = new CStoreRQ();
		String strSOPUID = objIDataSet.getDataForTag(0x0008,0x0016).getDicomValue().getValue().toString();
		String strSOPInstanceUID = objIDataSet.getDataForTag(0x0008,0x0018).getDicomValue().getValue().toString();
		ICommandSet objICommandSet = new CommandSet();
		objCStoreRQ.setCommandSet(objICommandSet);
		objCStoreRQ.setAffectedSOPClassUID(strSOPUID);
		objCStoreRQ.setAffectedSOPInstanceUID(strSOPInstanceUID);
		objCStoreRQ.setCommandField(EnumCommandField.C_STORE_RQ);
		objCStoreRQ.setPriority(EnumPriority.HIGH);
		objCStoreRQ.setCommandDataSetType(DicomConstants.DCM_DATASET_PRESENT);
		objCStoreRQ.setMessageID(iMsgID++);
		objCStoreRQ.setDataSet(objIDataSet);
		objCStoreRQ.setCommandGroupLength(objICommandSet.getCommandSetLength());

		return objCStoreRQ;
	}

	
	/**
	 * This method sends multiple Application Event Logging RQ.
	 * @param objRQCollection instance of RQ collection.
	 * @throws IOException Signals if an IOException occurs.
	 */
	public void sendRequests(ISingleCollection<CStoreRQ> objRQCollection) throws IOException
	{
		ICollectionIterator<CStoreRQ> objRQIterator = objRQCollection.iterator();
		while(objRQIterator.hasNext())
		{
			sendMessage(objRQIterator.next());
		}
	}
	
	/**
	 * This method sends the Request for SRS.
	 * @param objDicomMessage  The Dicom Message containing Request for .
	 * @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.
		 */
		try
		{
			associateStatus = objSCUSession.getAssociationStatus();
			objSCUSession.setMessageListener(this);

//			if(associateStatus)
//			{
//				IDataSet objIDataSet = objDicomMessage.getDataSet();
//				if(validateCStoreRQ(objIDataSet))
//				{
//					String strAffectedSOPClassUID = objDicomMessage.getCommandSet().getValue(0x0000,0x0003).toString();
//					if(!DicomConstants.DCM_PROCEDURE_EVENT_LOGGING_SOP_CLASS.equals(strAffectedSOPClassUID))
//						throw new InvalidDICOMMessageException(ResourceBundleWrapper.getValue("exceptions","InvalidCommandSet",new Locale(DicomConfig.createInstance().getLocale())));
//
//					SCU objCStoreSCU = instantiateSCU(EnumSCUType.C,objSCUSession);
//					Class cObjectClass = objNActionSCU.getClass();
//					
//					Class cSingleCollection = Class.forName(DICOM_SINGLE_COLLECTION_QUALIFIED_NAME);
//					Method mSetDimseResponse = cObjectClass.getMethod(fDIMSEResponse, cSingleCollection);
//					mSetDimseResponse.invoke(objNActionSCU, objDIMSERSPCollection);
//
//					Class cDicomMessage = Class.forName(DicomConstants.DICOM_MESSAGE_QUALIFIED_NAME);
//					Method mSendMessage = cObjectClass.getMethod(fSendMessage, cDicomMessage);
//					boolean status = (Boolean)mSendMessage.invoke(objNActionSCU, objDicomMessage);
//
//					return status;
//				}
//			}
		}
		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 request for N_ACTION for its Compulsory and 
	 * Conditional attributes specified by DICOM.
	 * @param objDicomMessage : The Request Message for validation.
	 * @return : true if validation is successful otherwise false if validation fails.
	 * @throws InvalidDataSetException : Exception is raised when the DataSet contents are not in proper order.
	 */
	public boolean validateCStoreRQ(IDataSet objIDataSet) throws InvalidDataSetException
	{
		try
		{
			
		}
		catch(MandatoryMissingException mme)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(mme.getMessage());
			throw new InvalidDataSetException(this.getClass().getSimpleName(),mme);
		}
		return true;
	} 
}
