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

import java.io.File;
import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cdac.medinfo.sdk.dcm30_04.baselibs.DataSet;
import cdac.medinfo.sdk.dcm30_04.baselibs.ImplicitDE;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDictionaryReader;
import cdac.medinfo.sdk.dcm30_04.dicommodel.xray.XRayAngiographicIOD;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.enums.EnumSOPClassUIDs;
import cdac.medinfo.sdk.dcm30_04.services.association.AAbort;
import cdac.medinfo.sdk.dcm30_04.services.association.AReleaseRQorRP;
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.association.RQPresContext;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRQ;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRSP;
import cdac.medinfo.sdk.dcm30_04.services.cmove.scu.CMoveSCU;
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.cstore.scu.CStoreSCU;
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.SinkException;
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.IQueryModel;
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.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;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

/**
 * This class is used to serve web request to DICOM Object,Following are the parameters to
 * be specified in the HTTP Request for retrieving the DICOM Object
 * 
 * 1) Request Type - Value should always be WADO.
 * 2) Study ID - Value should be a Study Instance UID of the Study existing on the C-MOVE SCP server.
 * 3) Series ID - Value should be a Series Instance UID of the Series that belongs to the above Study.
 * 4) Object ID - Value should be a SOP Instance UID of the SOP instance that belongs to the above Series.
 * 5) Content Type - Value should be the Content Type of the HTTP Response expected. Currently, application\dicom value is supported 
 * 					 as content type of the HTTP Response.
 * 
 * At least, one of the Study ID, Series ID, Object ID parameters should be specified in order to successfully retrieve the
 * DICOM image. All other parameter values can be specified as NULL. In case of some parameters being NULL, a single stream of multiple DICOM
 * images should be expected in the HTTP response.
 * 
 * Before requesting the DICOM Object user needs start the CStore SCP server
 * to serve the web request. 
 */
public class WADOServer implements IDataSink 
{
	
	private final String DCMJ_TMP_DIR = StandardMethods.getTempDirectoryStorage() + "\\C-DAC\\SDK\\DCM-J";;
	private WadoConfig objConfig;
	private HttpServletResponse httpResponse = null;
	private CStoreSCPHandler cStoreSCPHandler = null;

	
	/**
	 * Create new instance of WadoServer
	 */
    public WADOServer(WadoConfig objConfig)
    {
        this.objConfig = objConfig;          
    }
	

	/**
	 * Start the CStore SCP for receiving the CStore RQs as sub-operation of CMove Request
	 */
	public void startServer()
	{
		//Start the CStore SCP for receiving the CStore RQs as sub-operation of CMove Request
			startCStoreSCP(new Integer(objConfig.getCStoreSCPPort()).intValue());
	}


	/**
	 * Stops the CStore SCP Server.
	 */
	public void stopServer()
	{
		if(null != cStoreSCPHandler)
			cStoreSCPHandler.stopCStoreSCP();
	}


	/**
	 * Gets the HTTP Request for retrieving a DICOM image based on the parameters specified in the request.
	 * Creates a C-Move Request using the parameters specified in the HTTP request and sends the CMove Request
	 * to the specified C-Move SCP.
	 * 
	 */
	public void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse)  throws ServletException
	{
		try{

		if(null != cStoreSCPHandler)
		{
			String strRequestType = httpRequest.getParameter("requestType");
			String strStudyID = httpRequest.getParameter("studyUID");
			String strSeriesID = httpRequest.getParameter("seriesUID");
			String strObjectID = httpRequest.getParameter("objectUID");
			String strContentType = httpRequest.getParameter("contentType");
			String strButton= httpRequest.getParameter("btnSendRequest");
			String strImageLocation=httpRequest.getParameter("imagelocation");

			this.httpResponse = httpResponse;
			this.httpResponse.setContentType(strContentType);

			//Send C-Move Request
			if(strButton.equals("C-Move"))
			doCMove(strRequestType,strStudyID,strSeriesID,strObjectID,strContentType);
			//Send C-Store Request
			if(strButton.equals("Store"))
			doCStore(strImageLocation);
		}
		else
			throw new ServletException(ResourceBundleWrapper.getValue("exceptions","WADO.CStoreServernotRunning",new Locale(DicomConfig.createInstance().getLocale())));
				
		}
		catch (Exception e) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","WADO.OperationFialed",new Locale(DicomConfig.createInstance().getLocale())));
				DicomLogger.getLogger().log(e);
			}			

			throw new ServletException(ResourceBundleWrapper.getValue("exceptions","WADO.OperationFialed",new Locale(DicomConfig.createInstance().getLocale())),e);
		}
	}

	/**
	 * Gets the HTTP Request for retrieving a DICOM image based on the parameters specified in the request.
	 * Creates a C-Move Request using the parameters specified in the HTTP request and sends the CMove Request
	 * to the specified C-Move SCP.
	 * @param strRequestType
	 * @param strStudyID
	 * @param strSeriesID
	 * @param strObjectID
	 * @param strContentType
	 * @throws IOException
	 * @throws ServletException
	 */
	private void doCMove(String strRequestType,String strStudyID,String strSeriesID,String strObjectID,String strContentType) throws IOException, ServletException
	{
		DicomConfig objDicomConfig = DicomConfig.createInstance();
		//enable logging,specify the path of the directory where log files are created.
		//User can specify any directory in place of 'c:\DCM_Logs'.
		//If no directory is defined by user to create logs then it is created in User Temp DIR.

		File objLogDir = new File(DCMJ_TMP_DIR + "\\DCM_Logs");
		if( false == objLogDir.exists())
		{
			objLogDir.mkdirs();
		}

		objDicomConfig.enableLogging(EnumLoggingLevel.INFO,DCMJ_TMP_DIR + "\\DCM_Logs");

		//SCU Session.
		SCUSession objSession = null;

		//Create SCUSession object
		objSession = new SCUSession();

		DefaultAssociation objDefaultAssociation = new DefaultAssociation(objConfig.getCMoveAETitle()); //initDefaultAssociation();
		EnumServiceType[] arrenumServiceType = new EnumServiceType[1];
		arrenumServiceType[0] = EnumServiceType.CMOVE;
		objDefaultAssociation.loadAssociation(arrenumServiceType);

		//Create Association RQ
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(objConfig.getCMoveAETitle());

		//Start SCU Session by providing the IP address and Port of SCP.
		objSession.start(objConfig.getCMoveSCPIP(), new Integer(objConfig.getCMoveSCPPort()).intValue());

		//Create CStore SCU object using the current SCU Session.
		CMoveSCU objCMoveSCU = new CMoveSCU(objSession);

		//Send the Association Request
		if(objSession.sendAssociationMessage(objAssociateRQ))
		{

			//When association is successfully established the "objSession.sendAssociationMessage()"
			//method returns true.

			/*
			 * Creating C-Move Request for specified DICOM image.
			 */

			CMoveRQ objCMoveRQ = createCMoveRequest(strStudyID, strSeriesID, strObjectID);

			//Send the C-Move-RQ and wait for a C-Move-RSP with status not pending.
			//This method exits once the response is received. This response is 
			//added in the response collection maintained by the CStoreSCU which
			//can be retrieved. This method return true if request is successfully sent 
			//to the SCP and false if the message cannot be sent to the SCP( for e.g. if 
			//the association status is false or if SOP class UID is not agreed in agreed association.

			if(objCMoveSCU.sendMessage(objCMoveRQ))
			{
				ISingleCollection<DicomMessage> objRspCollection = objCMoveSCU.getDIMSEResponse();
				CMoveRSP objMoveRSP = new CMoveRSP(objRspCollection.get(0));
				if(objMoveRSP.getStatus() != 0 && objMoveRSP.getStatus() != -256 )
				{
					DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())) + objMoveRSP.getStatus());
					throw new ServletException(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())) + objMoveRSP.getStatus());

				}
			}
			else
			{
				throw new ServletException(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())));
			}

			//Create A-Release-RQ to release the association
			AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);

			//Send the A-Release-RQ
			if(objSession.sendAssociationMessage(objAReleaseRQorRP))
			{
				//When successful A-Release-RP is received, close the Session.Please note that instead of closing the session we can create 
				//another association and send more requests. Also it is possible
				//to create multiple C-Move-RQs and send them one by one.
				objSession.close();
			}

			//In case an ABORT is received from SCP during the C-Store operation
			if(objSession.getDIMSEAssociation() instanceof AAbort)
			{				
				objSession.close();
			}
		}
		else
		{			
			objSession.close();
		}

	}
	/**
	 * Gets the HTTP Request for storing the Image in the location specified.
	 * @param strImageLocation
	 * @throws IOException
	 * @throws ServletException
	 */
	private void doCStore(String strImageLocation) throws IOException, ServletException
	{
		DicomConfig objDicomConfig = DicomConfig.createInstance();
		//enable logging,specify the path of the directory where log files are created.
		//User can specify any directory in place of 'c:\DCM_Logs'.
		//If no directory is defined by user to create logs then it is created in User Temp DIR.

		File objLogDir = new File(DCMJ_TMP_DIR + "\\DCM_Logs");
		if( false == objLogDir.exists())
		{
			objLogDir.mkdirs();
		}

		objDicomConfig.enableLogging(EnumLoggingLevel.INFO,DCMJ_TMP_DIR + "\\DCM_Logs");
		
		XRayAngiographicIOD objXRayAngiographicIOD = new XRayAngiographicIOD(strImageLocation);

		IDataSet objIDataSet = objXRayAngiographicIOD.getDataSet();

		//SCU Session.
		SCUSession objSession = null;

		//Create SCUSession object
		objSession = new SCUSession();
		ISingleCollection<RQPresContext> objRQPresContextCol = new SingleCollection<RQPresContext>();
		
		//RQ Pres Context for XA
		RQPresContext objRQPresContextXRay = new RQPresContext();
		objRQPresContextXRay.setAbstractSyntaxName(EnumSOPClassUIDs.XRAYANGIOGRAPHICIOD.getValue());
		objRQPresContextXRay.addTransferSyntax(DicomConstants.TS_LOSSY_JPEG_8_BIT);
		objRQPresContextCol.add(objRQPresContextXRay);
		
		//RQ Pres Context for CT
		RQPresContext objRQPresContextCT = new RQPresContext();
		objRQPresContextCT.setAbstractSyntaxName(EnumSOPClassUIDs.CTIOD.getValue());
		objRQPresContextCT.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);
		objRQPresContextCol.add(objRQPresContextCT);

		//RQ Pres Context for NM
		RQPresContext objRQPresContextNM = new RQPresContext();
		objRQPresContextNM.setAbstractSyntaxName(EnumSOPClassUIDs.NMIOD.getValue());
		objRQPresContextNM.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);
		objRQPresContextCol.add(objRQPresContextNM);

		//RQ Pres Context for SC
		RQPresContext objRQPresContextSC = new RQPresContext();
		objRQPresContextSC.setAbstractSyntaxName(EnumSOPClassUIDs.SCIOD.getValue());
		objRQPresContextSC.addTransferSyntax(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN);
		objRQPresContextCol.add(objRQPresContextSC);

		DefaultAssociation objDefaultAssociation = new DefaultAssociation(objConfig.getCMoveAETitle()); //initDefaultAssociation();
		objDefaultAssociation.loadAssociation(objRQPresContextCol);

		//Create Association RQ
		AssociateRQ objAssociateRQ = objDefaultAssociation.getAssociationRQ(objConfig.getCMoveAETitle());

		//Start SCU Session by providing the IP address and Port of SCP.
		objSession.start(objConfig.getCStoreSCPIP(), new Integer(objConfig.getCStoreSCPPort()).intValue());

		//Create CStore SCU object using the current SCU Session.
		CStoreSCU objCStoreSCU= new CStoreSCU(objSession);

		//Send the Association Request
		if(objSession.sendAssociationMessage(objAssociateRQ))
		{

			//When association is successfully established the "objSession.sendAssociationMessage()"
			//method returns true.

			CStoreRQ objCStoreRQ = CStoreRQ.getDefaultRQ(objIDataSet);

			if(objCStoreSCU.sendMessage(objCStoreRQ))
			{
				ISingleCollection<DicomMessage> objRspCollection = objCStoreSCU.getDIMSEResponse();
				CStoreRSP objCStoreRSP = new CStoreRSP(objRspCollection.get(0));
				if(objCStoreRSP.getStatus() != 0 && objCStoreRSP.getStatus() != -256 )
				{
					DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())) + objCStoreRSP.getStatus());
					throw new ServletException(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())) + objCStoreRSP.getStatus());

				}
			}
			else
			{
				throw new ServletException(ResourceBundleWrapper.getValue("exceptions", "WADO.OperationFialed", new Locale(DicomConfig.createInstance().getLocale())));
			}

			//Create A-Release-RQ to release the association
			AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);

			//Send the A-Release-RQ
			if(objSession.sendAssociationMessage(objAReleaseRQorRP))
			{
				//When successful A-Release-RP is received, close the Session.
				//Please note that instead of closing the session we can create 
				//another association and send more requests. Also it is possible
				//to create multiple C-Move-RQs and send them one by one.
				objSession.close();
			}

			//In case an ABORT is received from SCP during the C-Store operation
			if(objSession.getDIMSEAssociation() instanceof AAbort)
			{				
				objSession.close();
			}
		}
		else
		{			
			objSession.close();
		}

	}
	private CMoveRQ createCMoveRequest(String strStudyID,String strSeriesID,String strObjectID)
	{
		/*
		 * Creating DataSet for C-Move Request.
		 */
		IDataSet objIDataSet = new DataSet();
		IDictionaryReader objIDictionaryReader = objIDataSet.getDictionaryReader();

		if(strStudyID != null)
		{
			IDataElement objIDataElement = new ImplicitDE(0x0020,0x000D,strStudyID,objIDictionaryReader);
			objIDataSet.addDataElement(objIDataElement);	
		}

		if(strSeriesID != null)
		{
			IDataElement objIDataElement = new ImplicitDE(0x0020,0x000E,strSeriesID,objIDictionaryReader);
			objIDataSet.addDataElement(objIDataElement);	
		}

		if(strObjectID != null)
		{
			IDataElement objIDataElement = new ImplicitDE(0x0008,0x0018,strObjectID,objIDictionaryReader);
			objIDataSet.addDataElement(objIDataElement);	
		}

		//Create Default CMoveRQ by giving the DataSet. This method uses default values for the attributes in the request. These attributes can be
		//modified once the request object is created, before sending.
		CMoveRQ objCCMoveRQ = CMoveRQ.getDefaultRQ(DicomConstants.DCM_STUDY_ROOT_QUERY_OR_RETRIVE_INFORMATION_MODEL_MOVE,objConfig.getAETitle(),objIDataSet);

		return objCCMoveRQ;
	}


	/**
	 *  Empty overridden method. This method is not used in this implementation.
	 */
	public short store(DicomMessage arg0, String arg1) throws SinkException 
	{
		return 0;

	}

	/**
	 * Empty overridden method. This method is not used in this implementation. 
	 */
	public short store(IQueryModel arg0, String arg2) throws SinkException 
	{
		return 0;
	}

	private void startCStoreSCP(int iPort)
	{
			cStoreSCPHandler = new CStoreSCPHandler(iPort, this);
			cStoreSCPHandler.start();	
	}

	class CStoreSCPHandler extends Thread
	{
		private CStoreSCP objCStoreSCP;
		private int iPort;
		private IDataSink objIDataSink;

		public CStoreSCPHandler(int iPort, IDataSink objIDataSink)
		{
			this.iPort = iPort;
			this.objIDataSink = objIDataSink;
		}

		public void run()
		{
			try {
				IPairedCollection<EnumDataSink, IDataSink> objSinkCollection = new PairedCollection<EnumDataSink, IDataSink>();
				objSinkCollection.put(EnumDataSink.CSERVICES, objIDataSink);

				objCStoreSCP = CStoreSCP.createInstance();
				objCStoreSCP.setDataSinkList(objSinkCollection);

				objCStoreSCP.start(objConfig.getAETitle(), iPort);
			} 
			catch (IOException e) 
			{			
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(e);
				}
			}
		}

		public void stopCStoreSCP()
		{
			if(null != objCStoreSCP)
				objCStoreSCP.stopListening();
			Thread.currentThread().interrupt();

		}
	}
}
