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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;

import cdac.medinfo.sdk.dcm30_04.baselibs.TransferSyntax;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITransferSyntax;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomBufferStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomFileStream;
import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.exceptions.DicomException;
import cdac.medinfo.sdk.dcm30_04.services.association.AgreedAssociation;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumEarlyFailedStatus;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumErrorCode;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ICommandSet;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidCommandSetException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidDICOMMessageException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ServiceException;
import cdac.medinfo.sdk.dcm30_04.services.pdu.IServicePDUReceiver;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDUWriter;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDV;
import cdac.medinfo.sdk.dcm30_04.services.pdu.P_DATA_TF_PDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.P_DATA_ValueItem;
import cdac.medinfo.sdk.dcm30_04.services.pdu.ServiceEvent;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ServicePDUException;

//TODO: Auto-generated Javadoc
/**
 * The class ServiceAgent handles and validates the Service messages while sending and receiving.
 * At the time sending it generates the PDU from arrived Dicom Message.
 * At the time of receiving it collects all the PDUs and generates DiocmMessage.
 */
public class ServiceAgent implements IServicePDUReceiver,IAssociationStatusListener 
{


	/** The obj p_data_tf_pdu. */
	//private P_DATA_TF_PDU objP_data_tf_pdu;

	/** The pdu writer. */
	private PDUWriter pduWriter;

	/** The obj dicom message. */
	private DicomMessage objDicomMessge;

	/** The obj agreed association. */
	private AgreedAssociation objAgreedAssociation; 

	/** The obj i data notification receiver. */
	private IDataNotificationReceiver objIDataNotificationReceiver;

	/** The obj data sink. */
	private IDicomStream objDataSink;

	/** The command fragments. */
	//private byte[] commandFragments;

	/** The command set populated. */
	private boolean commandSetPopulated;

	/** The association status. */
	private boolean associationStatus ;

	/** The is early failed response. */
	private boolean isEarlyfailedRSP = false;

	/** The obj file. */
	private File objFile = null;

	/** The stop. */
	@SuppressWarnings("unused")
	private boolean flag,stop;

	/** The enum early failed status. */
	private EnumEarlyFailedStatus enumEarlyFailedStatus;

	private boolean isPDUProcessing;	
	
	//	private IExceptionListener objIExceptionListener;


	/**
	 * Instantiates a new Service Agent.
	 * @param pduWriter instance of PDUWriter
	 */
	public ServiceAgent(PDUWriter pduWriter)
	{
		this.pduWriter = pduWriter;
		objDataSink = new DicomBufferStream();
	}

	/**
	 * Retrieves the EarlyFailed Status for last received DicomMessage.
	 * 
	 * @return EnumEarlyFailedStatus - enum value of EarlyFailed Status
	 */
	public EnumEarlyFailedStatus getEarlyFailedStatus() 
	{
		return enumEarlyFailedStatus;
	}

	/**
	 * Sets the EarlyFailed Status for last received DicomMessage.
	 * 
	 * @param enumMsgStatus the enumMsgStatus to set
	 */
	public void setEarlyFailedStatus(EnumEarlyFailedStatus enumMsgStatus) 
	{
		this.enumEarlyFailedStatus = enumMsgStatus;
	}

	//	/**
	//	 * Sets the exception listener.
	//	 * The entity that registers itself as an exception listener is notified 
	//	 * if connection an exception occurs.
	//	 * @param objIConnectionListener object of Connection listener.
	//	 */
	//	public void setExceptionListener(IExceptionListener objIExceptionListener)
	//	{
	//		this.objIExceptionListener = objIExceptionListener;
	//	}


	//	/**
	//	* @return the bCancelRQSent
	//	*/
	//	public boolean isCancelRQSent() 
	//	{
	//	return bCancelRQSent;
	//	}

	//	/**
	//	* @param cancelRQSent the bCancelRQSent to set
	//	*/
	//	public void setCancelRQSent(boolean cancelRQSent)
	//	{
	//	bCancelRQSent = cancelRQSent;
	//	}

	/**
	 * It processes the arrived service message according to the association status.
	 * 
	 * @param objServiceEvent instance of ServiceEvent
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void servicePDUReceive(ServiceEvent objServiceEvent) throws IOException
	{
		/*
		 * If association does not exist then it logs the message.
		 */
		if(associationStatus)
		{
		
			isPDUProcessing = true;
			processPDU(objServiceEvent.getPDU());
		}
		else
		{
			ServicePDUException ex = new ServicePDUException();
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ex.getLocalizedMessage());
			}
			objIDataNotificationReceiver.onException(new ServiceException(ex),EnumErrorCode.ASSOCIATION_NOT_EXISTS);
		}
	}



	/** The nospdu. */
	int count=0,nospdu=0;//used for Display purpose

	/*
	 * Processes the arrived PDU ,collects all PDU and reassembles them in DicomMessage.
	 * @param objPDU instance of PDU
	 */
	/**
	 * Process pdu.
	 * 
	 * @param objPDU the obj pdu
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void processPDU(PDU objPDU) throws IOException
	{
		try
		{
			if(objPDU instanceof P_DATA_TF_PDU)
			{	
				P_DATA_TF_PDU objP_data_tf_pdu = (P_DATA_TF_PDU)objPDU;

				//checking that new RQ has starting or not.
				int iFragmentType = objP_data_tf_pdu.getFragmentType();
				switch(iFragmentType)
				{
				case 0: // Dataset
					if(commandSetPopulated == false )
						throw new InvalidDICOMMessageException("CommandSet not Populated");
					break;
				case 1: // Commandset
					if(commandSetPopulated == true )
						throw new InvalidDICOMMessageException("CommandSet is already Populated");
					break;
				case -1: // No value
					break;
				}

				if(commandSetPopulated == false )
				{
					objDataSink = new DicomBufferStream();
					((DicomBufferStream)objDataSink).setOutputStream(new ByteArrayOutputStream());

					byte[] commandFragments = objP_data_tf_pdu.getAllCommmandFragments();

					if(objP_data_tf_pdu.isLastCommandFragmentPresent())
					{
						writeBuffer(objDataSink,commandFragments);

						((DicomBufferStream)objDataSink).setInputStream(new ByteArrayInputStream(((DicomBufferStream)objDataSink).toByteArray()));

						ICommandSet objICommandSet = new CommandSet();
						objICommandSet.parse(objDataSink);
						commandSetPopulated = true;
						objDicomMessge = new DicomMessage();

						objDicomMessge.setCommandSet(objICommandSet);

						objDataSink = null;
						{
							// System.out.println("MESSAGE ========= Commandset parsed...");
						}
						isEarlyfailedRSP = false;
					}
					else
					{
						writeBuffer(objDataSink,commandFragments);
					}
				}

				/*
				 * if requested SOP class UID or affected SOP class UID present in Command Set is
				 * not same as any of the Abstract Syntaxes in AgreedAssociation, then EnumEarlyFailedStatus will be set to SOP Class is not supproted.
				 */
				ICommandSet objCommandSet = objDicomMessge.getCommandSet();
				String strSOPClassUID = null;
				if(objCommandSet.isPresent(0x0000,0x0002))  // AffectedSOPClassUID
				{
					strSOPClassUID = objCommandSet.getValue(0x0000,0x0002).toString();
				}
				else if(objCommandSet.isPresent(0x0000,0x0003))  // RequestedSOPClassUID
				{
					strSOPClassUID = objCommandSet.getValue(0x0000,0x0003).toString();
				}

				if(strSOPClassUID != null)
				{
					if(false == objAgreedAssociation.isSOPClassUIDPresent(strSOPClassUID))
					{
						setEarlyFailedStatus(EnumEarlyFailedStatus.SOP_CLASS_NOT_SUPPORTED);
						isEarlyfailedRSP = true;
						DataNotificationEvent objDataNotificationEvent = new DataNotificationEvent(this);
						objDataNotificationEvent.setEnumPDUType(EnumPDUType.P_DATA_TF);
						objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
					}
				}

				short shDataSetTye = Short.parseShort(objDicomMessge.getCommandSet().getDataForTag(0x0000, 0x0800).getDicomValue().getValue().toString());
				if(false == isEarlyfailedRSP)
				{
					if(shDataSetTye != 0x0101)
					{
						getStream();
						//String strSOPClassUID = null;
						EnumCommandField enumServiceType = StandardMethods.getCommandType((Short.parseShort(objDicomMessge.getCommandSet().getValue(0x0000,0x0100).toString())));
						if(enumServiceType == EnumCommandField.N_SET_RQ || enumServiceType == EnumCommandField.N_GET_RQ || enumServiceType == EnumCommandField.N_ACTION_RQ)
							strSOPClassUID = objDicomMessge.getCommandSet().getValue(0x0000,0x0003).toString();
						else
							strSOPClassUID = objDicomMessge.getCommandSet().getValue(0x0000,0x0002).toString();

						if(objAgreedAssociation.isSOPClassUIDPresent(strSOPClassUID))
						{
							byte bPresContextID = objP_data_tf_pdu.getPresContextID();
							String strTS = objAgreedAssociation.getSupportedTSyntax(bPresContextID);
							ITransferSyntax objITransferSyntax = new TransferSyntax(strTS);
							objDataSink.setEndianNess(objITransferSyntax.getEndianNess());
							byte[] dataFragments = null;
							dataFragments = objP_data_tf_pdu.getAllDataFragments();
							if(objP_data_tf_pdu.isLastDataFragmentPresent())	//Last DataSet fragment received
							{
								//Write the last fragment
								writeBuffer(objDataSink,dataFragments);
								objP_data_tf_pdu = null;
								getDataStream();
								objDicomMessge.setDataSet(objDataSink,objITransferSyntax);

								/*
								 * This block of code Setting Cancel RQ status true.
								 */
								//							if(objDicomMessge.getCommandType() == EnumCommandField.C_CANCEL_RQ)
								//							setCancelRQSent(true);

								//Create Event and notify the Client Session that a Request is received
								setEarlyFailedStatus(EnumEarlyFailedStatus.NO_EARLY_FAILED);
								DataNotificationEvent objDataNotificationEvent = new DataNotificationEvent(this);
								objDataNotificationEvent.setEnumPDUType(EnumPDUType.P_DATA_TF);
								objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
								commandSetPopulated = false;
								isPDUProcessing = false;
							}
							else
							{
								writeBuffer(objDataSink, dataFragments);
								objP_data_tf_pdu = null;
								count++;
							}
						}
						else 
						{
							/*
							 * EARLY FAILED RESPONSE should be sent in case the Request does not follow AgreedAssociation.
							 * Association should remain alive to receive next requests and should not ABORT.
							 */
							{
								// System.out.println("generating Early Failed Response");
							}
							setEarlyFailedStatus(EnumEarlyFailedStatus.SOP_CLASS_NOT_SUPPORTED);
							isEarlyfailedRSP = true;
							{
								// System.out.println("********** PDU  DISCARDED ********** " + objPDU.getLength());
							}
							objPDU = null;
							isPDUProcessing = false;
							DataNotificationEvent objDataNotificationEvent = new DataNotificationEvent(this);
							objDataNotificationEvent.setEnumPDUType(EnumPDUType.P_DATA_TF);

							objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
						}
					}
					else
					{
						setEarlyFailedStatus(EnumEarlyFailedStatus.NO_EARLY_FAILED);
						/*
						 * This block of code Setting Cancel RQ status true.
						 */
						//					if(objDicomMessge.getCommandType() == EnumCommandField.C_CANCEL_RQ)
						//					setCancelRQSent(true);

						DataNotificationEvent objDataNotificationEvent = new DataNotificationEvent(this);
						objDataNotificationEvent.setEnumPDUType(EnumPDUType.P_DATA_TF);
						objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
						isPDUProcessing = false;
						commandSetPopulated = false;
					}
				}
				else
				{
					objPDU = null;
				}
			}
		}
		catch(InvalidCommandSetException icse)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(icse.getLocalizedMessage());
			}
			objIDataNotificationReceiver.onException(new ServiceException(icse),EnumErrorCode.INVALID_COMMANDSET);
		}
		catch(InvalidDICOMMessageException idme)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(idme.getLocalizedMessage());
			}
			objIDataNotificationReceiver.onException(new ServiceException(idme),EnumErrorCode.INVALID_DICOM_MESSAGE);
		}


	}

	/**
	 * Gets automatically invoked when exception occur while processing  data received  
	 * @param objClientSession - object of Client Session.
	 * @param ex - the exception object
	 * @param ErrorCode - the reason of the DicomException
	 */
	public void onException(DicomException ex,EnumErrorCode ErrorCode)
	{
		if(DicomConfig.createInstance().isLoggingOn())
		{
			DicomLogger objDicomLogger = DicomLogger.getLogger();
			objDicomLogger.log(ex.getMessage());
		}
		objIDataNotificationReceiver.onException(new ServiceException(ex),ErrorCode);
	}


	/*
	 * Sets the buffer stream or file stream to write all data and command fragments until the last data or command fragment arrives. 
	 * If the Temp Storage is true buffer stream is used else file stream is used 
	 */
	/**
	 * Gets the stream.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void getStream() throws IOException
	{
		if(objDataSink == null)
		{
			if(DicomConfig.createInstance().isTempStorageInUse() == false)
			{
				objDataSink = new DicomBufferStream();
				((DicomBufferStream)objDataSink).setOutputStream(new ByteArrayOutputStream());
			}
			else
			{
				String strFilePath = null;
				if(DicomConfig.createInstance().getTempStorageFilePath().equals(""))
				{
					strFilePath = StandardMethods.getTempDirectoryStorage();
					strFilePath = strFilePath+"//DicomSDK";
				}
				else
					strFilePath = DicomConfig.createInstance().getTempStorageFilePath();

				File objDir = new File(strFilePath);
				if(!objDir.exists())
					objDir.mkdirs();
				objFile = new File(strFilePath + "//"+StandardMethods.generateUUID());
				objDataSink = new DicomFileStream();
				((DicomFileStream)objDataSink).setOutputStream(new FileOutputStream(objFile,true));
			}
		}
	}

	/*
	 * Sets Input stream in Data Sink.
	 * This input stream can be file stream or buffer stream depending on whether temp stream is in use or not.
	 */
	/**
	 * Gets the data stream.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void getDataStream() throws IOException
	{
		if(DicomConfig.createInstance().isTempStorageInUse() == false)
		{
			((DicomBufferStream)objDataSink).setInputStream(new ByteArrayInputStream(((DicomBufferStream)objDataSink).toByteArray()));
		}
		else
		{
			((DicomFileStream)objDataSink).setInputStream(new FileInputStream(objFile));
		}
	}

	/*
	 * Writes the buffer to given DicomStream.
	 */
	/**
	 * Write buffer.
	 * 
	 * @param objIDicomStreams the obj i dicom streams
	 * @param arrByte the arr byte
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void writeBuffer(IDicomStream objIDicomStreams, byte[] arrByte) throws IOException
	{
		objIDicomStreams.writeBuffer(arrByte, 0, arrByte.length);
	}

	/**
	 * Sets the Dicom Message.
	 * 
	 * @param objDicomMessge instance of Dicom Message
	 */
	public void setDicomMessage(DicomMessage objDicomMessge)
	{
		this.objDicomMessge = objDicomMessge;
	}

	/**
	 * Retrieves the dicom message.
	 * 
	 * @return DicomMessage - instance of Dicom Message
	 */
	public DicomMessage getDicomMessage()
	{
		return objDicomMessge;
	}

	/**
	 * Registeres the data notification listener.
	 * 
	 * @param recvr instance of IDataNotificationReceiver
	 */
	public void addDataNotificationListener(IDataNotificationReceiver recvr)
	{
		objIDataNotificationReceiver = recvr;
	}

	/**
	 * Sends Dicom Message in form of PDUs.
	 * 
	 * @param objDicomMessage instance of DicomMessage
	 * 
	 * @return boolean - shows status True if successfully sent else False.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public boolean sendServiceMessage(DicomMessage objDicomMessage) throws IOException
	{
		if(DicomConfig.createInstance().isLoggingOn())
		{
			DicomLogger objDicomLogger = DicomLogger.getLogger();
			objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SendingServiceMessage",new Locale(DicomConfig.createInstance().getLocale()))+objDicomMessage.getCommandType().name());
		}
		ICommandSet objICommandSet = objDicomMessage.getCommandSet();
		IDataSet objIDataSet = objDicomMessage.getDataSet();
		if(objIDataSet != null)
		{
			objIDataSet.iterator();
		}
		if(associationStatus)
			startSend(objICommandSet,objIDataSet);
//		if(objIDataSet != null)
//			objIDataSet.dispose();
		return true;
	}

	/*
	 * Start send.
	 * @param objICommandSet instance of CommandSet
	 * @param objIDataSet instance of DataSet
	 */
	/** The no of pdu. */
	int noOfPDU = 0;

	/**
	 * Start send.
	 * 
	 * @param objICommandSet the obj i command set
	 * @param objIDataSet the obj i data set
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void startSend(ICommandSet objICommandSet, IDataSet objIDataSet) throws IOException
	{
		//		long t1 = System.currentTimeMillis();
		//		long t2 = 0; 
		ISingleCollection<P_DATA_ValueItem> objP_Data_PDV_Item_Collection = null;

		//get max length from agreed association for negotiation
		int iNegotiationLength = objAgreedAssociation.getWriteMaxLength();

		byte[] arrByte = null;
		String strTS = null;
		byte bPresentationContextID = 0 ;		

		//get Presentation contextID from agreed association.
		String strPresID = null;
		EnumCommandField enumServiceType = StandardMethods.getCommandType((Short.parseShort(objICommandSet.getValue(0x0000,0x0100).toString())));

		if(!enumServiceType.equals(EnumCommandField.C_CANCEL_RQ))
		{
			if(enumServiceType == EnumCommandField.N_SET_RQ || enumServiceType == EnumCommandField.N_GET_RQ || enumServiceType == EnumCommandField.N_ACTION_RQ ||  enumServiceType == EnumCommandField.N_DELETE_RQ)
				strPresID = objICommandSet.getValue(0x0000,0x0003).toString();
			else
				strPresID = objICommandSet.getValue(0x0000,0x0002).toString();

			//		if(objICommandSet != null)
			//		{
			//		ICommandElement objICommandElement = objICommandSet.getDataForTag(0x0, 0x2);
			//		if(objICommandElement != null)
			//		{
			//		IDicomValue objIDicomValue = objICommandElement.getDicomValue();
			//		if(objIDicomValue != null && objIDicomValue.getValue() != null)
			//		strPresID = objIDicomValue.getValue().toString();
			//		}
			//		}


			if(objIDataSet != null)
			{
				strTS = objIDataSet.getTransferSyntax().getTransferSyntaxUID();
			}
			else
			{
				strTS = objAgreedAssociation.getSupportedTSCollection(strPresID).elementAt(0);
			}
			bPresentationContextID = objAgreedAssociation.getPresContextID(strPresID, strTS);
		}
		while(objICommandSet.isAvailable())
		{
			objP_Data_PDV_Item_Collection = new SingleCollection<P_DATA_ValueItem>();
			arrByte = objICommandSet.getBytes(iNegotiationLength-6);
			if(arrByte.length == (iNegotiationLength-6))
			{
				PDV objPdv = null;

				if(objICommandSet.isAvailable())
					objPdv = getPDV(1,false,arrByte);
				else
					objPdv = getPDV(1,true,arrByte);


				P_DATA_ValueItem objPValueItem = getP_DATA_ValueItem(bPresentationContextID, objPdv);
				objP_Data_PDV_Item_Collection.add(objPValueItem);

				P_DATA_TF_PDU objP_data_tf_pdu = getP_DATA_TF_PDU(objP_Data_PDV_Item_Collection);

				//send data.
				noOfPDU = noOfPDU +1;
				if(associationStatus)
					pduWriter.sendData(objP_data_tf_pdu);
				else
				{
					{
						// System.out.println("Association does not exist");
					}
					return;//Here exception needs to be thrown.
				}
				//t2 = System.currentTimeMillis();

				//				System.out.println("Time To create a Service PDU" + (t2-t1));
				//t1 = System.currentTimeMillis();
			}
		}

		//if command set is completed then make PDV item and add it in data set. set the flag as true to indicate more
		//pdv items can be added to our PDU.
		PDV objPdv = getPDV(1,true,arrByte);
		P_DATA_ValueItem objPValueItem = getP_DATA_ValueItem(bPresentationContextID, objPdv);
		objP_Data_PDV_Item_Collection.add(objPValueItem);

		int iBytesRemaining = iNegotiationLength-6-arrByte.length-6;
		flag = true;
		if(objIDataSet != null)
		{
			if(objIDataSet.isAvailable() == false)
			{
				P_DATA_TF_PDU objP_data_tf_pdu = getP_DATA_TF_PDU(objP_Data_PDV_Item_Collection);
				noOfPDU = noOfPDU +1;
				if(associationStatus)
					pduWriter.sendData(objP_data_tf_pdu);
				else
				{
					{
						// System.out.println("Association does not exist");
					}
					return;//Here exception needs to be thrown.
				}
				//t2 = System.currentTimeMillis();

				//				System.out.println("Time To create a Service PDU" + (t2-t1));
				//t1 = System.currentTimeMillis();
			}
			while(objIDataSet.isAvailable())
			{
				if(flag == true)
				{
					arrByte = objIDataSet.getBytes(iBytesRemaining);

					if(objIDataSet.isAvailable())
						objPdv = getPDV(0,false,arrByte);
					else
					{
						{
							// System.out.println("Last Data Fragment sent");
						}
						objPdv = getPDV(0,true,arrByte);
					}

					objPValueItem = getP_DATA_ValueItem(bPresentationContextID, objPdv);
					objP_Data_PDV_Item_Collection.add(objPValueItem);

					P_DATA_TF_PDU objP_data_tf_pdu = getP_DATA_TF_PDU(objP_Data_PDV_Item_Collection);
					noOfPDU = noOfPDU +1;
					if(associationStatus)
						pduWriter.sendData(objP_data_tf_pdu);
					else
					{
						{
							// System.out.println("Association does not exist");
						}
						return;//Here exception needs to be thrown.
					}
					//t2 = System.currentTimeMillis();

					//					System.out.println("Time To create a Service PDU" + (t2-t1));
					//t1 = System.currentTimeMillis();
					flag = false;
					iBytesRemaining = 0;
				}
				else
				{
					objP_Data_PDV_Item_Collection = new SingleCollection<P_DATA_ValueItem>();
					arrByte = objIDataSet.getBytes(iNegotiationLength-6);
					if(objIDataSet.isAvailable())
					{
						objPdv = getPDV(0,false,arrByte);
					}
					else
					{
						objPdv = getPDV(0,true,arrByte);
						{
							// System.out.println("last data fragment sent");
						}
					}
					objPValueItem = getP_DATA_ValueItem(bPresentationContextID, objPdv);
					objP_Data_PDV_Item_Collection.add(objPValueItem);

					P_DATA_TF_PDU objP_data_tf_pdu = getP_DATA_TF_PDU(objP_Data_PDV_Item_Collection);
					noOfPDU = noOfPDU +1;
					if(associationStatus)
						pduWriter.sendData(objP_data_tf_pdu);
					else
					{
						{
							// System.out.println("Association does not exist");
						}
						return;//Here exception needs to be thrown.
					}
					//t2 = System.currentTimeMillis();

					//					System.out.println("Time To create a Service PDU" + (t2-t1));
					//t1 = System.currentTimeMillis();
				}
			}
		}
		else
		{
			P_DATA_TF_PDU objP_data_tf_pdu = getP_DATA_TF_PDU(objP_Data_PDV_Item_Collection);
			noOfPDU = noOfPDU +1;
			if(associationStatus)
				pduWriter.sendData(objP_data_tf_pdu);
			else
			{
				{
					// System.out.println("Association does not exist");
				}
				return;//Here exception needs to be thrown.
			}
			//t2 = System.currentTimeMillis();
			{
				// System.out.println("Time To create a Service PDU" + (t2-t1));
			}
			//t1 = System.currentTimeMillis();
		}
	}


	/*
	 * Retrieves the PDV item.
	 * @param bPresentationContextID PresentationContextID to be set in PDV Item.
	 * @param objPDV instance of PDV
	 * @return P_DATA_ValueItem - instance of P_DATA_ValueItem
	 */
	/**
	 * Gets the p_ data_ value item.
	 * 
	 * @param bPresentationContextID the b presentation context id
	 * @param objPDV the obj pdv
	 * 
	 * @return the p_ data_ value item
	 */
	private P_DATA_ValueItem getP_DATA_ValueItem(byte bPresentationContextID, PDV objPDV)
	{
		P_DATA_ValueItem objPValueItem = new P_DATA_ValueItem();
		objPValueItem.setPresentationContextID(bPresentationContextID);
		objPValueItem.setPDV(objPDV);
		return objPValueItem;
	}

	/*
	 * Retrieves the P_DATA_TF PDU.
	 * @param objP_Data_PDV_Item_Collection collection of PDV Items.
	 * @return P_DATA_TF_PDU instance of P_DATA_TF_PDU
	 */
	/**
	 * Gets the p_ data_ t f_ pdu.
	 * 
	 * @param objP_Data_PDV_Item_Collection the obj p_ data_ pd v_ item_ collection
	 * 
	 * @return the p_ data_ t f_ pdu
	 */
	private P_DATA_TF_PDU getP_DATA_TF_PDU(ISingleCollection<P_DATA_ValueItem> objP_Data_PDV_Item_Collection)
	{
		P_DATA_TF_PDU objP_data_tf_pdu = new P_DATA_TF_PDU();
		objP_data_tf_pdu.setPDVItemCollection(objP_Data_PDV_Item_Collection);
		objP_data_tf_pdu.setPDUType(EnumPDUType.P_DATA_TF);
		return objP_data_tf_pdu;
	}

	/*
	 * Retrieves the pDV.
	 * @param fragmentType value of fragment type
	 * @param isLast sets status whether it is last fragment or not.
	 * @param fragment value of fragment.
	 * @return PDV - instance of pDV
	 */
	/**
	 * Gets the pDV.
	 * 
	 * @param fragmentType the fragment type
	 * @param isLast the is last
	 * @param fragment the fragment
	 * 
	 * @return the pDV
	 */
	private PDV getPDV(int fragmentType, boolean isLast, byte[] fragment)
	{
		PDV objPDV = new PDV();
		objPDV.setFragmentType(fragmentType);
		objPDV.setFragment(fragment);
		if(isLast == true)
			objPDV.setIsLast();
		return objPDV;
	}

	/**
	 * Sets the Agreed Association created in association negotiation process.
	 * 
	 * @param objAgreedAssociation instance of Agreed Association
	 */
	public void setAgreedAssociation(AgreedAssociation objAgreedAssociation) 
	{
		this.objAgreedAssociation = objAgreedAssociation;
	}

	/**
	 * Retrieves the Agreed Association created in association negotiation process.
	 * 
	 * @return AgreedAssociation - instance of AgreedAssociation
	 */
	public AgreedAssociation getAgreedAssociation() 
	{
		return objAgreedAssociation;
	}

	/**
	 * Stops the process.
	 */
	public void stopProcess()
	{
		try
		{
			stop = true;
			pduWriter = null;
			if(objFile != null)
			{
				if(objDataSink!=null)
					objDataSink.close();
				objFile.delete();
				objFile = null;
			}
			if(objDicomMessge != null)
				objDicomMessge.dispose();
			objDicomMessge = null;
			objIDataNotificationReceiver = null;
			//objP_data_tf_pdu = null;
			pduWriter = null;
			objAgreedAssociation = null;
			{
				// System.out.println("ServiceAgent Down..");
			}
		}
		catch(IOException ioe)
		{

		}
	}

	/**
	 * Dispose.
	 */
	public void dispose()
	{
		try
		{
			if(objFile != null)
			{
				if(objDataSink!=null)
					objDataSink.close();
				objFile.delete();
				objFile = null;
			}
		}
		catch(IOException ioe)
		{
		}
	}

	/**
	 * Sets the current status of association notified from Association Agent whenever association status is updated.
	 * 
	 * @param associationStatus - shows the status of association.
	 */
	public void setAssociationStatus(boolean associationStatus) 
	{
		this.associationStatus = associationStatus;
	}
	
	public boolean isPDUProcessingContinued()
	{
		return isPDUProcessing;
	}
}
