/*******************************************************************************
  *  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.CIRWBDService.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.exceptions.ConditionFailedException;
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.cget.commons.CGetRQ;
import cdac.medinfo.sdk.dcm30_04.services.cget.scu.CGetSCU;
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.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.ResourceBundleWrapper;


/**
 * CIRWBD_SCU works as a Service Class User for Composite Instance Retrieve Without Bulk Data Class.
 * CIRWBD_SCU Service Class includes C-Get service as specified by DIMSE service group.
 * CIRWBD_SCU sends the DIMSE service request for supported C-Service and handles the response.
 */
public class CIRWBD_SCU extends SCU
{
	/** The i message id. */
	static short iMsgID = 1;
	
	/**
	 * 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 CIRWBD_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 CIRWBD_SCU(InetSocketAddress addr) throws IOException
	{
		super(addr);
	}

	/**
	 * Instantiates a new CIRWBD_SCU on the SCU session provided by user.
	 * @param objSCUSession the object of SCU session on which  CIRWBD_SCU is to be initiated.
	 */
	public CIRWBD_SCU(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.
	 * @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,EnumCommandField objEnumCommandField ,String strSOPInstanceUID, ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		EnumServiceType[] arrenumServiceType = new EnumServiceType[1];
		arrenumServiceType[0] = EnumServiceType.CGET_COMPOSITEINSTANCERETRIEVEWITHOUTBULKDATA;
		AssociateRQ objAssociateRQ = new AssociateRQ(objDataSetCollection,strCalledAETitle,strCallingAETitle);
		send(objAssociateRQ,objEnumCommandField, strSOPInstanceUID, 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 CGET 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,EnumCommandField objEnumCommandField,String strSOPInstanceUID,ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ( objAssociateRQ,objEnumCommandField,strSOPInstanceUID, objDataSetCollection);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ( objAssociateRQ,objEnumCommandField, strSOPInstanceUID, objDataSetCollection);

			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,EnumCommandField objEnumCommandField ,String strSOPInstanceUID,ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}
		System.out.println("in sendRQ of CIRWBD the value of StrSOPInstanceUID is"+strSOPInstanceUID);
		
		if(objEnumCommandField==EnumCommandField.C_GET_RQ)
		{
			ISingleCollection<CGetRQ> objCGetRQ = CGetRQ.getDefaultRQ(strSOPInstanceUID, objDataSetCollection);
			sendRequestsForCGet(objCGetRQ);
		}


		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}
	
	/**
	 * This method sends the Request for CIRWBD.
	 * @param objDicomMessage  The Dicom Message containing Request for CIRWBD.
	 * @return true, if send message
	 * @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);
			boolean isConnected = objSCUSession.connectionState();

			if(isConnected)
			{
				if(associateStatus)
				{
					EnumCommandField enumRequestType = objDicomMessage.getCommandType();
					String strAffectedSOPClassUID = objDicomMessage.getCommandSet().getValue(0x0000,0x0002).toString();
					IDataSet objIDataSet = objDicomMessage.getDataSet();
					switch(enumRequestType)
					{
					case C_GET_RQ :
					{
						if(validateCGETRQ(objIDataSet))
						{
							String SopClassID = objDicomMessage.getCommandSet().getValue(0x0000,0x0002).toString();
							if(DicomConstants.DCM_COMPOSITE_INSTANCE_RETRIEVE_WITHOUT_BULK_DATA_GET.equals(SopClassID))
							{
							CGetSCU objCGetSCU = new CGetSCU(objSCUSession);
							objCGetSCU.setDIMSEResponse(objDIMSERSPCollection);
							boolean status = objCGetSCU.sendMessage(objDicomMessage);
							return status;
							}
							else
							{
								if(DicomConfig.createInstance().isLoggingOn())
								{
									DicomLogger objDicomLogger = DicomLogger.getLogger();
									objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","InvalidCommandSet",new Locale(DicomConfig.createInstance().getLocale())));
								}
							}
						}
					}
					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);
		}
		return false;
	}

	
	/**
	 * Validates request for C_GetRQ 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 validateCGetRQ(IDataSet objIDataSet) throws InvalidDataSetException
	{
		try
		{
			if(objIDataSet.isPresent(0x0072,0x0104))
			{
				//if attribute is present then check for null value
				if(objIDataSet.getValue(0x0072,0x0104) == null)
					//if value is null then throw exception
					throw new ConditionFailedException(ResourceBundleWrapper.getValue("exceptions","NullValue",new Locale(DicomConfig.createInstance().getLocale()))+ 0x0008 + "," + 0x0005); 
			}
			if(objIDataSet.isPresent(0x0072,0x0106))
			{
				//if attribute is present then check for null value
				if(objIDataSet.getValue(0x0072,0x0106) == null)
					//if value is null then throw exception
					throw new ConditionFailedException(ResourceBundleWrapper.getValue("exceptions","NullValue",new Locale(DicomConfig.createInstance().getLocale()))+ 0x0008 + "," + 0x0005); 
			}
			
		}catch(MandatoryMissingException mme)
			{
				if(DicomConfig.createInstance().isLoggingOn())
					DicomLogger.getLogger().log(mme.getMessage());
				throw new InvalidDataSetException(this.getClass().getSimpleName(),mme);
			}
			return true;

		}
	
		/**
		 * Validates request for C_GET_RQ 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 validateCGETRQ(IDataSet objIDataSet) throws InvalidDataSetException
		{
			try
			{
				if(objIDataSet.isPresent(0x0008,0x006))
				{
					//if attribute is present then check for null value
					if(objIDataSet.getValue(0x0008,0x006) == null)
						//if value is null then throw exception
						throw new ConditionFailedException(ResourceBundleWrapper.getValue("exceptions","NullValue",new Locale(DicomConfig.createInstance().getLocale()))+ 0x0008 + "," + 0x0005); 
				}
				
			}
		catch(MandatoryMissingException mme)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(mme.getMessage());
			throw new InvalidDataSetException(this.getClass().getSimpleName(),mme);
		}
		return true;
	}
		
		/**
		 * This method sends multiple C_Get_RQ.
		 * @param objRQCollection Collection of CGet i.e. to be send.
		 * @throws IOException Signals that an I/O exception has occurred.
		 */
		public void sendRequestsForCGet(ISingleCollection<CGetRQ> objRQCollection) throws IOException
		{
			ICollectionIterator<CGetRQ> objRQIterator = objRQCollection.iterator();
			while(objRQIterator.hasNext())
			{
				sendMessage(objRQIterator.next());
			}
		}
}
