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

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

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomValue;
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.cget.commons.CGetRQ;
import cdac.medinfo.sdk.dcm30_04.services.cstore.commons.CStoreRQ;
import cdac.medinfo.sdk.dcm30_04.services.cstore.commons.CStoreRSP;
import cdac.medinfo.sdk.dcm30_04.services.cstore.scp.CStoreSCP;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCGetStatus;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumDataSink;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumServiceType;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ICommandElement;
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.IDataSink;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.ServiceMessageEvent;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ClientSession;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ServerSideAssociationAgent;
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.ICollectionIterator;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;

/**
 * The C-GET service is used by a DIMSE-service-user to match and retrieve a set of Attributes
 * against the Attributes of a set of composite SOP Instances maintained by a peer DIMSE-service-user.
 * This class implements functioning of CGet Service User.
 */
public class CGetSCU extends SCU  
{
	

	private String strDestinationPath = null;
	private IDataSink objIDataSink = null;

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

	/**
	 * Parameterized constructor initializes CGet SCP with given session and registers itself to SCUSession to work as ResponseListener.
	 * @param objSCUSession instance of SCU Session.
	 */
	public CGetSCU(SCUSession objSCUSession)
	{
		super(objSCUSession);
	}

	/**
	 * Sets the destination path to store the file.
	 * @return String value of Storage Destination Path
	 */
	public String getDestinationPath()
	{
		return strDestinationPath;
	}

	/**
	 * Retrieves the destination path to store the file.
	 * @param strStorageDestPath the strStorageDestPath to set
	 */
	public void setDestinationPath(String strStorageDestPath) 
	{
		this.strDestinationPath = strStorageDestPath;
	}

	/**
	 * Retrieves the DataSink
	 * @return the instance of DataSink
	 */
	public IDataSink getDataSink() 
	{
		return objIDataSink;
	}

	/**
	 * Sets the Data Sink.
	 * @param objIDataSink object of Data Sink.
	 */
	public void setDataSink(IDataSink objIDataSink) 
	{
		this.objIDataSink = objIDataSink;
	}

	/**
	 * This method sends multiple CStore RQ.
	 * @param objRQCollection Collection of CStoreRQ i.e. to be send.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void sendRequests(ISingleCollection<CGetRQ> objRQCollection) throws IOException
	{
		ICollectionIterator<CGetRQ> objRQIterator = objRQCollection.iterator();
		while(objRQIterator.hasNext())
		{
			sendMessage(objRQIterator.next());
		}
	}

	/**
	 * This method creates association request according to the DataSet collection provided by the user and the Default association.
	 * Then it will perform the C-Move operation on the data set provided by the user, release the association and close the connection.
	 * @param strcallingAETitle calling AE title for this service provider.
	 * @param strCalledAETitle called AE title for user .
	 * @param strAffectedSOPClassUID String value of Affected SOP Class UID.
	 * @param objDataSetCollection Collection of Data Set containing identifier on which C-Move operation has to be performed.
	 * @throws IOException Signals IOException of IO operation fails.
	 */
	public void send(String strCalledAETitle,String strCallingAETitle,String strAffectedSOPClassUID,ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		EnumServiceType[] arrenumServiceType = new EnumServiceType[6];
		arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
		arrenumServiceType[1] = EnumServiceType.CGET;
		arrenumServiceType[2] = EnumServiceType.CGET_HANGINGPROTOCOL;
		arrenumServiceType[3] = EnumServiceType.CGET_IMPLANTTEMPLATE;
		arrenumServiceType[4] = EnumServiceType.CGET_COLORPALETTE;
		arrenumServiceType[5] = EnumServiceType.CGET_IFLR;
		DefaultAssociation objDefaultAssociation = new DefaultAssociation(strCallingAETitle);
		objDefaultAssociation.loadAssociation(arrenumServiceType);
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(strCalledAETitle);
		send(strAffectedSOPClassUID, objAssociateRQ,  objDataSetCollection);
	}

	/**
	 * 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 strAffectedSOPClassUID String value of Affected SOP Class UID.
	 * @param objAssociateRQ The association request i.e. to be sent to SCP.
	 * @param objDataSetCollection Collection of Data Set on which C-Store operation has to be performed.
	 * 
	 * @throws IOException Signals IOException if IO operation fails.
	 */
	public void send(String strAffectedSOPClassUID, AssociateRQ objAssociateRQ, ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		if(objSCUSession != null)
		{
			sendRQ(strAffectedSOPClassUID, objAssociateRQ, objDataSetCollection);
		}
		else
		{
			objSCUSession = new SCUSession();
			objSCUSession.start(objInetSocketAddress);

			objSCUSession.setMessageListener(this);

			sendRQ(strAffectedSOPClassUID, objAssociateRQ,objDataSetCollection);

			boolean isSCUSessionClosed = objSCUSession.close();
			if(isSCUSessionClosed)
				objSCUSession = null;
		}
	}

	//this method will send the association request and CStore request to the SCP on the session established.
	/**
	 * Send Request.
	 * 
	 * @param strAffectedSOPClassUID the string affected SOP Class UID.
	 * @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(String strAffectedSOPClassUID, AssociateRQ objAssociateRQ, ISingleCollection<IDataSet> objDataSetCollection) throws IOException
	{
		boolean associationStatusForSession = objSCUSession.getAssociationStatus();
		if(!associationStatusForSession)
		{
			objSCUSession.associate(objAssociateRQ);
		}
		ISingleCollection<CGetRQ> objCGetRQCollection = CGetRQ.getDefaultRQ(strAffectedSOPClassUID,objDataSetCollection);
		sendRequests(objCGetRQCollection);
		if(!associationStatusForSession)
		{
			objSCUSession.releaseAssociation();
		}
	}

	/**
	 * 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
	{
		try
		{
			DicomMessage objDicomMessage = objServiceMessageEvent.getDicomMessge();
			objDicomMessage.getDataSet();
			//			objSCUSession.getServiceAgent().dispose();

			if(objDicomMessage.getCommandType() == EnumCommandField.C_STORE_RQ)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SubOpeartionCStoreRequestReceived",new Locale(DicomConfig.createInstance().getLocale())) + objDicomMessage.getCommandType().name());
				}

				//process cStore and generate C-store response
				ClientSession objClientSession = new ClientSession(objDicomMessage);

				objClientSession.setPduReader(objSCUSession.getPduReader());
				objClientSession.setPduWriter(objSCUSession.getPduWriter());

				ServerSideAssociationAgent objServerSideAssociationAgent = new ServerSideAssociationAgent(objClientSession.getPduWriter(),objSCUSession.getPduReader());
				objServerSideAssociationAgent.addDataNotificationListener(objClientSession);
				objServerSideAssociationAgent.addAssociationStatusListener(objSCUSession.getServiceAgent());
				objClientSession.setServerSideAssociationAgent(objServerSideAssociationAgent);

				objClientSession.setServiceAgent(objSCUSession.getServiceAgent());	
				objClientSession.setAgreedAssociation(objSCUSession.getAgreedAssociation());


				objClientSession.getServiceAgent().addDataNotificationListener(objClientSession);
				objClientSession.setAssociationStatus(objSCUSession.getAssociationStatus());

				objSCUSession.getPduReader().addAssociatePDUReceiver(objServerSideAssociationAgent);
				objSCUSession.getPduReader().addServicePDUReceiver(objSCUSession.getServiceAgent());


				CStoreSCP objCStoreSCP = CStoreSCP.createInstance(objClientSession);
				objCStoreSCP.setStoragePath(strDestinationPath);//Setting storage path for CStore sub operations.
			//	setDestinationPath(strDestinationPath+objIDataSet.getValue(0x0008, 0x0018));//Setting storage path for CStore sub operations.

				// C-Get is used for CServices only there for we need to add
				IPairedCollection<EnumDataSink, IDataSink> objCollDataSink = new PairedCollection<EnumDataSink,IDataSink>();
				objCollDataSink.put(EnumDataSink.CSERVICES, objIDataSink);
				objCStoreSCP.setDataSinkList(objCollDataSink);//Setting DataSink for CStore sub operations.
				{
					// System.out.println("CStore RQ in CGET SCU received");
				}
				CStoreRSP objCStoreRSP = objCStoreSCP.processRequest(objDicomMessage);
				objClientSession.sendServiceMessage(objCStoreRSP);
				objSCUSession.getPduReader().addAssociatePDUReceiver(objSCUSession.getClientSideAssociationAgent());
				objSCUSession.getServiceAgent().addDataNotificationListener(objSCUSession);
				
				CStoreRQ objCStoreRQ = new CStoreRQ(objDicomMessage);
				IDataSet objIDataSet1 = objCStoreRQ.getDataSet();
				setDestinationPath(strDestinationPath+objIDataSet1.getValue(0x0008, 0x0018));


			}
			else
			{
				// If DICOM message is not with C_STORE_RQ command field and also it is no with
				// command field C_GET_RSP then message will be logged.
				//This will log message, if in case Dicom Message with unexpected Command field is received.
				if(objDicomMessage.getCommandType() != EnumCommandField.C_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.C_STORE_RQ.name() + " or " + EnumCommandField.C_GET_RSP.name());
					}
				}
				objDIMSERSPCollection.add(objDicomMessage);

				short shStatus = 0;
				IDicomValue objIDicomValue = null;
				ICommandSet objICommandSet = objDicomMessage.getCommandSet();

				if(objICommandSet != null)
				{
					ICommandElement objICommandElement = objICommandSet.getDataForTag(0x0000, 0x0900);
					if(objICommandElement != null)
						objIDicomValue = objICommandElement.getDicomValue();
					if(objIDicomValue != null && objIDicomValue.getValue() != null)
						shStatus = Short.parseShort(objIDicomValue.getValue().toString());
				}

				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "ResponseReceived",new Locale(DicomConfig.createInstance().getLocale())) + objDicomMessage.getCommandType().name());
					objDicomLogger.log(objDicomMessage.getCommandType().name()+" "+ResourceBundleWrapper.getValue("status", "StatusCode", new Locale(DicomConfig.createInstance().getLocale()))+ Integer.toHexString(shStatus));
				}

				//	Short.parseShort(objDicomMessage.getCommandSet().getDataForTag(0x0000, 0x0900).getDicomValue().getValue().toString());
				if(shStatus != EnumCGetStatus.PENDING_SUBOPERATIONS_CONTINUING.getValue())
				{
					synchronized(this)
					{
						isResponseReceived = true;
						{
							// System.out.println("Waiting Finished***************Success CGet RSP Received");
						}
						this.notify();
					}
				}
				else
				{
					// System.out.println("CGet pending RSP received");
				}
			}
		}
		catch(IOException e)
		{
			System.out.println("Inside CGetSCU exception  "+e);
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(e.getMessage());
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","FileAccessDenied",new Locale(DicomConfig.createInstance().getLocale())));
			}
		}
	}

	/**
	 * Sends the DicomMessage to SCUSession for further processing and waits untill final CGetRSP is arrived.
	 * @param objDicomMessage instance of DicomMessage.
	 * @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.C_GET_RQ || objDicomMessage.getCommandType() == EnumCommandField.C_CANCEL_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())
							{							
								if(!objDicomMessage.getCommandType().equals(EnumCommandField.C_CANCEL_RQ))
								   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;
	}
}
