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

import java.io.File;
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.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.exceptions.DicomException;
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.AgreedAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociateRQ;
import cdac.medinfo.sdk.dcm30_04.services.association.IDIMSEAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.InvalidAssociationPrimitiveException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumAbortReason;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumAbortSource;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCFindStatus;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCGetStatus;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCMoveStatus;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCommunicationMode;
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.IConnectionListener;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.IExceptionListener;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.INetworkAgent;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidDICOMMessageException;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DICOMMessageValidator;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DataNotificationEvent;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IAsyncReceiver;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IDataNotificationReceiver;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IMessageListener;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.ServiceAgent;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.ServiceMessageEvent;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDUReader;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDUWriter;
import cdac.medinfo.sdk.dcm30_04.services.scu.net.ClientSideNetworkAgent;
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;

/**
 * This class establishes SCU Session with the remote entity.
 * This class act as a data notification receiver. When ever data arrives from the remote
 * application entity is is notified to SCU Session. It also listen for the connection status.
 * If the connection does not exits then SCU session is stopped.
 */
public class SCUSession implements IDataNotificationReceiver,IConnectionListener 
{
	/** The pdu reader. */
	protected PDUReader pduReader;

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

	/** The obj client side association agent. */
	protected ClientSideAssociationAgent objClientSideAssociationAgent = null;

	/** The obj service agent. */
	protected ServiceAgent objServiceAgent = null;

	/** The obj message listener. */
	protected  IMessageListener objMessageListener = null;

	/* Maintains the current status of the Thread. */
	/** The stop. */
	protected  boolean stop;	

	/** The obj association response. */
	protected  IDIMSEAssociation objAssociationResponse = null;

	/* To check association status */
	/** The association status. */
	protected  boolean associationStatus ;

	/** The no of request send. */
	protected  int noOfRequestSend =0;

	/** The obj i async receiver. */
	protected IAsyncReceiver objIAsyncReceiver = null;

	/** Instance of Dicom Message RQ collection. */
	protected  ISingleCollection<DicomMessage> objDIMSERQCollection;

	/** Instance of short MessageID collection. */
	protected  ISingleCollection<Short> objMsgIDCollection;

	protected boolean isConnected ;

	protected boolean isSecured = false;

	protected InetSocketAddress objInetSocketAddress = null;

	protected int itimeout = 0;

	protected INetworkAgent objNetworkAgent = null;

	private IExceptionListener objExceptionListener = null;



	/**
	 * Instantiates a new SCU session.
	 * 
	 * @throws IOException Signals if an IO Exception occurs.
	 */
	public SCUSession() throws IOException
	{
		objDIMSERQCollection = new SingleCollection<DicomMessage>();
		objMsgIDCollection = new SingleCollection<Short>();
	}

	/**
	 * Start the SCU session with the IP and port given.
	 * 
	 * @param strIP String value of IP.
	 * @param iPort int value of port.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void start(String strIP, int iPort) throws IOException
	{
		objInetSocketAddress = new InetSocketAddress(strIP,iPort);
		start(objInetSocketAddress);
	}

	/**
	 * Start the SCU session with the INet socket address given.
	 * 
	 * @param addr instance of InetSocketAddress.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void start(InetSocketAddress addr) throws IOException
	{
		this.objInetSocketAddress = addr;
		if(!isConnected)
		{
			objDIMSERQCollection = new SingleCollection<DicomMessage>();
			objMsgIDCollection = new SingleCollection<Short>();

			ClientSideNetworkAgent objClientSideNetworkAgent = new ClientSideNetworkAgent();
			objClientSideNetworkAgent.connect(addr,itimeout);
			this.objNetworkAgent =  objClientSideNetworkAgent;

			pduReader = new PDUReader(objClientSideNetworkAgent);
			pduWriter = new PDUWriter(objClientSideNetworkAgent);

			objClientSideAssociationAgent = new ClientSideAssociationAgent(pduWriter,pduReader);
			objClientSideAssociationAgent.addDataNotificationListener(this);
			pduReader.addAssociatePDUReceiver(objClientSideAssociationAgent);

			//add SCUSession as Connection Listener on PDUWritter and PDUReader.
			pduReader.addConnectionListener(this);
			pduWriter.addConnectionListener(this);
			pduReader.setPduWriter(pduWriter);

			objServiceAgent = new ServiceAgent(pduWriter);
			objServiceAgent.addDataNotificationListener(this);
			pduReader.addServicePDUReceiver(objServiceAgent);

			objClientSideAssociationAgent.addAssociationStatusListener(objServiceAgent);
			isConnected = true;
			pduReader.start();
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SCUSessionStarted",new Locale(DicomConfig.createInstance().getLocale()))+"["+
						ResourceBundleWrapper.getValue("status", "IPAddress",new Locale(DicomConfig.createInstance().getLocale()))+"= "+addr.getAddress().getHostAddress()+" , "+
						ResourceBundleWrapper.getValue("status", "Port",new Locale(DicomConfig.createInstance().getLocale())) + "= "+addr.getPort()+"]");
			}
		}
		else
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "SCUSessionAlreadyConnected",new Locale(DicomConfig.createInstance().getLocale())));
			}
		}	
	}

	/**
	 * Sets the ExceptionListener on SCU session to notify user about the exceptions occurred
	 * @param objExceptionListener
	 */
	public void setExceptionListener(IExceptionListener objExceptionListener)
	{
		this.objExceptionListener = objExceptionListener; 
	}

	/**
	 * This function is automatically invoked when connection is closed by remote end.
	 */
	public void closeConnection()
	{
		isConnected = false;
		associationStatus = false;
		if(objClientSideAssociationAgent != null)
			objClientSideAssociationAgent.setAssociationStatus(false);
		if(objServiceAgent != null)
			objServiceAgent.setAssociationStatus(false);
		if(objMessageListener != null)
			objMessageListener.setAssociationStatus(false);
		close();
	}

	/**
	 * Sets the connection state
	 * @param isConnected
	 */
	public void setConnectionState(boolean isConnected)
	{
		this.isConnected = isConnected; 
	}


	/**
	 * This method gets the Early Failed Status.
	 * 
	 * @return EnumEarlyFailedStatus enum value of early failed status.
	 */
	public EnumEarlyFailedStatus getEarlyFailedStatus() 
	{
		if(objServiceAgent!=null)
			return objServiceAgent.getEarlyFailedStatus();
		return null;
	}

	/**
	 * Sets the Association Staus.
	 * 
	 * @param associationStatus boolean variable indicating Association Status.
	 * true - if association is Alive.
	 * false - if association does not exits.
	 */
	public void setAssociationStatus(boolean associationStatus) 
	{
		this.associationStatus = associationStatus;
	}

	/**
	 * Retrieves Association status.
	 * 
	 * @return boolean - indicating Association Status.
	 * true - if association is Alive.
	 * false - if association does not exits.
	 */
	public boolean getAssociationStatus()
	{
		return associationStatus;
	}

	/**
	 * @return boolean
	 */
	public boolean connectionState()
	{
		return isConnected;
	}

	/**
	 * Notify ProcessHandler according to the response received and initializes association parameters.
	 * 
	 * @param objDataNotificationEvent the obj data notification event
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void notifyData(DataNotificationEvent objDataNotificationEvent) throws IOException
	{
		EnumPDUType objPDUType = objDataNotificationEvent.getEnumPDUType();
		if(!isConnected)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
			}
		}
		else if(objPDUType == EnumPDUType.ASSOCIATE_AC)
		{
			associationStatus = true;
			if(objClientSideAssociationAgent != null)
			{
				objAssociationResponse = objClientSideAssociationAgent.getDimseAssociation();
				AgreedAssociation objAgreedAssociation = objClientSideAssociationAgent.getAgreedAssociation();
				if(objServiceAgent != null)
					objServiceAgent.setAgreedAssociation(objAgreedAssociation);
				if(objMessageListener != null)
				{
					objMessageListener.setDIMSEAssociation(objAssociationResponse);
					objMessageListener.setAssociationStatus(associationStatus);
				}
			}
		}
		else if(objPDUType == EnumPDUType.ABORT)
		{
			associationStatus = false;

			if(objServiceAgent != null)
				objServiceAgent.setAgreedAssociation(null);

			if(objClientSideAssociationAgent != null)
			{
				objAssociationResponse = objClientSideAssociationAgent.getDimseAssociation();
				if(objMessageListener != null)
				{
					objMessageListener.setDIMSEAssociation(objAssociationResponse);
					objMessageListener.setAssociationStatus(associationStatus);
				}
			}
		}
		else if(objPDUType == EnumPDUType.RELEASE_RP || objPDUType == EnumPDUType.ASSOCIATE_RJ)
		{
			associationStatus = false;

			if(objServiceAgent != null)
				objServiceAgent.setAgreedAssociation(null);

			if(objClientSideAssociationAgent != null)
			{
				objAssociationResponse = objClientSideAssociationAgent.getDimseAssociation();
			}
		}
		else if(objPDUType == EnumPDUType.P_DATA_TF)
		{
			ServiceMessageEvent objServiceMessageEvent = new ServiceMessageEvent(this);
			if(objServiceAgent!=null)
			{
				DicomMessage objDicomMessage = objServiceAgent.getDicomMessage();
				// if objDicomMessge is not null then validate messages using DICOMMessageValidator through reflection.
				DICOMMessageValidator objMessageValidator = new DICOMMessageValidator();
				//check for null value of object of DicomMessage if DicomMessage is null then throw InvalidDICOMMessageException.
				try
				{
					if(objDicomMessage == null || objMessageValidator.validate(objDicomMessage) == false)
					{
						if(DicomConfig.createInstance().isLoggingOn())
						{
							DicomLogger objDicomLogger = DicomLogger.getLogger();
							objDicomLogger.log(ResourceBundleWrapper.getValue("warning","InvalidDicomMessage",new Locale(DicomConfig.createInstance().getLocale())));
						}
					}
				}
				catch(InvalidDICOMMessageException idm)
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning","InvalidDicomMessage",new Locale(DicomConfig.createInstance().getLocale())));
					}
				}

				objServiceMessageEvent.setDicomMessge(objDicomMessage);
				fireServiceMessageEvent(objServiceMessageEvent);
				short status = -1;
				if(objDicomMessage!=null)
				{
					if(objDicomMessage.getCommandSet().getValue(0x0000,0x0900) != null)
					{
						status = Short.parseShort(objDicomMessage.getCommandSet().getValue(0x0000,0x0900).toString());
					}
					AgreedAssociation objAgreedAssociation = getAgreedAssociation();
					if(objAgreedAssociation.isAsyncMode() && !(objDicomMessage.getCommandType() == EnumCommandField.C_STORE_RQ))
					{
						short shMsgID = Short.parseShort(objDicomMessage.getCommandSet().getValue(0x0000, 0x0120).toString());
						synchronized (objMsgIDCollection)
						{
							if(!(status == EnumCFindStatus.PENDING_MATCHES_CONTINUING_CURRENT_MATCH_SUPPLIED.getValue() ||  status == EnumCFindStatus.PENDING_MATCHES_CONTINUING_WARNING_ONE_OR_MORE_OPTIONAL_KEYS_NOT_SUPPORTED.getValue()
									|| status == EnumCMoveStatus.PENDING_SUBOPERATIONS_CONTINUING.getValue() || status == EnumCGetStatus.PENDING_SUBOPERATIONS_CONTINUING.getValue())
									|| status == EnumCFindStatus.CANCEL_MATCHING_TERMINATED_DUE_TO_CANCEL_REQUEST.getValue()
									|| status == EnumCGetStatus.CANCEL_SUBOPERATIONS_TERMINATED_CANCEL_INDICATION.getValue()
									|| status == EnumCMoveStatus.CANCEL_SUBOPERATIONS_TERMINATED_CANCEL_INDICATION.getValue())
							{
								if(objMsgIDCollection.contains(shMsgID))
								{								
									Short objShort  = new Short(shMsgID);								
									objMsgIDCollection.remove(objShort);
									if(objDIMSERQCollection.size() != 0)
									{
										DicomMessage objMessage = objDIMSERQCollection.elementAt(0);
										objDIMSERQCollection.remove(objMessage);
										sendServiceMessage(objMessage);
									}
									else if(objMsgIDCollection.size() == 0)
									{
										synchronized(this)
										{
											this.notify();
										}
									}
								}
								else
								{
									// System.out.println("/////////////  Message Id not found  ********************");
								}
							}
							//else
							//System.out.println("In SCU Session Pending RSP arrived");
						}
					}
					//				else 
					//				System.out.println("CStore RQ arrived ------");
				}

			}
		}		
	}

	/** Fire service message event. */
	int count = 0;

	/**
	 * Fire service message event.
	 * 
	 * @param event the event
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void fireServiceMessageEvent(ServiceMessageEvent event) throws IOException
	{

		if(objMessageListener != null)
		{
			{
				// System.out.println("Calling Message Listener "+ (++count));
			}
			objMessageListener.receiveMessage(event);
			objMessageListener.setDIMSEAssociation(objAssociationResponse);
		}
		if(objIAsyncReceiver != null)
		{
			objIAsyncReceiver.onResponseReceive(event.getDicomMessge());
		}
	}

	/**
	 * Gets Agreed Association.
	 * 
	 * @return AgreedAssociation - instance of Agreed Association.
	 */
	public AgreedAssociation getAgreedAssociation()
	{
		if(objClientSideAssociationAgent != null)
		{
			return objClientSideAssociationAgent.getAgreedAssociation();
		}
		return null;
	}

	/**
	 * This method establishes association.
	 * 
	 * @param objDIMSEAssociation - instance of IDIMSEAssociation
	 * 
	 * @return boolean - boolean variable indicating whether association is established or not.
	 * true - if association is established.
	 * false - if association is not established.
	 */
	public boolean associate(IDIMSEAssociation objDIMSEAssociation)
	{
		if(!isConnected)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
			}
			return false;
		}
		if(objClientSideAssociationAgent != null)
		{
			return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
		}
		return false;
	}

	/**
	 * This method releases the current association.
	 * 
	 * @return boolean - variable indication whether association is released or not.
	 */
	public boolean releaseAssociation()
	{
		if(!isConnected)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
			}
			return false;
		}

		AReleaseRQorRP objAReleaseRQorRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RQ);
		if(objClientSideAssociationAgent != null)
			return objClientSideAssociationAgent.sendAssociationMessage(objAReleaseRQorRP);
		return false;
	}

	/**
	 * Sends the association Message to client side Association agent.
	 * It checks for the validity of association message.
	 * @param objDIMSEAssociation  the object of DIMSE association
	 * @return boolean true if successful else false
	 */
	public boolean sendAssociationMessage(IDIMSEAssociation objDIMSEAssociation)
	{
		try
		{
			if(objDIMSEAssociation.validate())
			{	
				if(!isConnected)
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
					}
					return false;
				}

				if(associationStatus && objDIMSEAssociation instanceof AssociateRQ)
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "Association.UnexpectedAssociationRQ",new Locale(DicomConfig.createInstance().getLocale())));
					}
					return false;
				}

				if(associationStatus || objDIMSEAssociation instanceof AssociateRQ)
				{
					if(objDIMSEAssociation instanceof AAbort)
					{
						associationStatus = false;
						if(objMessageListener != null)
						{
							objMessageListener.setAssociationStatus(false);
							objMessageListener.setDIMSEAssociation(objAssociationResponse);
						}
						if(objClientSideAssociationAgent != null)
							return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
					}
					if(objClientSideAssociationAgent != null && objDIMSEAssociation instanceof AssociateRQ)
						return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
					if(objClientSideAssociationAgent != null && objDIMSEAssociation instanceof AReleaseRQorRP)
					{
						AgreedAssociation objAgreedAssociation = getAgreedAssociation();
						if(objAgreedAssociation.isAsyncMode())
						{
							try
							{
								synchronized (this)
								{
									if(objMsgIDCollection.size() == 0 )
									{
										return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
									}
									else
									{
										System.out.println(".................................................Waiting for RSPs");
										this.wait();
										return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
									}
								}
							}
							catch(InterruptedException ie)
							{
								if(DicomConfig.createInstance().isLoggingOn())
								{
									DicomLogger objDicomLogger = DicomLogger.getLogger();
									objDicomLogger.log(ie);
								}
							}
						}
						else
						{
							// if the operation is in Syncronized mode and association status is true then release response should be processed directly.
							if(associationStatus)
								return objClientSideAssociationAgent.sendAssociationMessage(objDIMSEAssociation);
							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", "Association.UnexpectedDIMSEAssociation", new Locale(DicomConfig.createInstance().getLocale()))+ objDIMSEAssociation.getPDUType());
						}
					}
				}
				else if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "Association.AssociationDoesNotExist",new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
		}
		catch(InvalidAssociationPrimitiveException iape)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(iape.getLocalizedMessage());
			}
			throw iape;
		}
		return false;
	}

	/**
	 * Sends the Service Message to service agent.
	 * 
	 * @param objDicomMessge instance of Dicom Message
	 * 
	 * @return boolean true if successful else false
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public boolean sendServiceMessage(DicomMessage objDicomMessge) throws IOException
	{
		// check for null value of object of DicomMessage if DicomMessage is null then throw InvalidDICOMMessageException.
		if(objDicomMessge == 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","InvalidDicomMessage",new Locale(DicomConfig.createInstance().getLocale())));
		}
		else
		{
			// if objDicomMessge is not null then validate messages using DICOMMessageValidator through reflection.
			DICOMMessageValidator objMessageValidator = new DICOMMessageValidator();

			//If validation fails, it will return false.
			if(objMessageValidator.validate(objDicomMessge) == false)
			return false;

		}

		if(!isConnected)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SessionInvalidated",new Locale(DicomConfig.createInstance().getLocale())));
			}
			return false;
		}



		/*
		 * This block of code is verifying that TS supported in Association and TS of DataSet is same or not.
		 * If TS are not same then Message will not be sent and fail success will be returned.
		 */ 
		AgreedAssociation objAgreedAssociation = objClientSideAssociationAgent.getAgreedAssociation();

		IDataSet objIDataSet = objDicomMessge.getDataSet();
		ICommandSet objICommandSet = objDicomMessge.getCommandSet();

		// Retrieves SOP Class UID from Command Set.
		String strAffectedSOPClassUID = null;
		EnumCommandField  enumCommandField = objDicomMessge.getCommandType();



		// Sending the C-FIND-CANCEL RQ if associated RQ response yet to receive 
		if(enumCommandField.equals(EnumCommandField.C_CANCEL_RQ))
		{
			if(objMsgIDCollection.contains((Short)objICommandSet.getValue(0x0000, 0x0120)))
			{
				if(objServiceAgent.sendServiceMessage(objDicomMessge))
					return true;
				else
					return false;
			}
			else
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning","C_CANCEL_RQ_NOT_SENT",new Locale(DicomConfig.createInstance().getLocale())) + objICommandSet.getValue(0x0000, 0x0120).toString() );
				}
				return false;
			}	
		}

		if(enumCommandField.equals(EnumCommandField.N_ACTION_RQ) || enumCommandField.equals(EnumCommandField.N_GET_RQ) || enumCommandField.equals(EnumCommandField.N_SET_RQ) || enumCommandField.equals(EnumCommandField.N_DELETE_RQ))
		{
			strAffectedSOPClassUID = objICommandSet.getValue(0x0000, 0x0003).toString();
		}
		else
		{
			strAffectedSOPClassUID = objICommandSet.getValue(0x0000, 0x0002).toString();
		}

		if(strAffectedSOPClassUID == null || !(objAgreedAssociation.isSOPClassUIDPresent(strAffectedSOPClassUID)))
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning","SOP_CLASS_NOT_SUPPORTED",new Locale(DicomConfig.createInstance().getLocale()))+ strAffectedSOPClassUID );
			}
			return false;
		}

		if(objIDataSet != null)
		{
			String strTS = objIDataSet.getTransferSyntax().getTransferSyntaxUID();
			String strDataSetSOPClassUID = objIDataSet.getSOPClassUID();
			if(enumCommandField.equals(EnumCommandField.C_STORE_RQ))
			{
				if(strTS == null || strAffectedSOPClassUID == null|| !(objAgreedAssociation.isSOPClassUIDSupported(strAffectedSOPClassUID, strTS))   || !(strAffectedSOPClassUID.equals(strDataSetSOPClassUID)))
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning","SOP_CLASS_NOT_SUPPORTED",new Locale(DicomConfig.createInstance().getLocale()))+ strAffectedSOPClassUID +","+ strTS);
					}
					return false;
				}
			}
			else
			{
				if (strTS == null || strAffectedSOPClassUID == null || !(objAgreedAssociation.isSOPClassUIDSupported(strAffectedSOPClassUID, strTS)))
				{
					if (DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "SOP_CLASS_NOT_SUPPORTED",new Locale(DicomConfig.createInstance().getLocale()))+ strAffectedSOPClassUID +","+ strTS);
					}
					return false;
				}             
			}	
		}

		EnumCommunicationMode enumCommunicationMode = objAgreedAssociation.getCommMode();
		short shOpsInvoked = objAgreedAssociation.getMaxOpsInvoked();
		short shMsgID = Short.parseShort(objDicomMessge.getCommandSet().getValue(0x0000, 0x0110).toString());
		if(objServiceAgent != null)
		{
			if(associationStatus)
			{
				if(enumCommunicationMode == EnumCommunicationMode.UNBOUNDED_ASYNCHRONOUS || enumCommunicationMode == EnumCommunicationMode.SYNCHRONOUS)
				{
					objMsgIDCollection.add(shMsgID);
					if(objServiceAgent.sendServiceMessage(objDicomMessge))
					{						
						noOfRequestSend++;
						return true;
					}
				}
				else 
				{
					if(objMsgIDCollection.size() < shOpsInvoked)
					{
						objMsgIDCollection.add(shMsgID);
						if(objServiceAgent.sendServiceMessage(objDicomMessge))
						{
							noOfRequestSend++;
							return true;
						}
					}
					else
					{
						objDIMSERQCollection.add(objDicomMessge);
						return true;
					}
				}
			}
			else
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "Association.AssociationDoesNotExist", new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
		}
		if(DicomConfig.createInstance().isLoggingOn())
		{
			DicomLogger objDicomLogger = DicomLogger.getLogger();
			objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "ServiceMessageSendingFailed", new Locale(DicomConfig.createInstance().getLocale())) + objDicomMessge.getCommandType());
		}
		return false;
	}

	/**
	 * This function indicates the No of requests send by the user.
	 * 
	 * @return int - No of reuest send .
	 */
	public int getNumberOfRequestSend()
	{
		return noOfRequestSend;
	}

	/**
	 * Gets the DIMSE association.
	 * 
	 * @return IDIMSEAssociation - instance of DIMSE association
	 */
	public IDIMSEAssociation getDIMSEAssociation()
	{
		return objAssociationResponse;
	}

	/**
	 * Sets the end session variable true.
	 * 
	 * @param b true to end the session.
	 */
	public void endSession(boolean b)
	{
		stop = true;
	}

	/**
	 * Closes the session and set all resources free.
	 * 
	 * @return boolean true if successfully closes the session
	 */
	public boolean close()
	{
		if(!stop)
		{
			isConnected =false;
			endSession(true);
			associationStatus = false;
			if(objServiceAgent != null)
				objServiceAgent.stopProcess();
			objServiceAgent = null;
			if(pduReader != null)
				pduReader.close();
			pduReader = null;
			if(pduWriter != null)
				pduWriter.close();
			pduWriter = null;
			if(objClientSideAssociationAgent != null)
				objClientSideAssociationAgent = null;
			objMessageListener = null;
			objAssociationResponse = null;
			if(objNetworkAgent != null)
				objNetworkAgent.close();
			objNetworkAgent = null;
			{
				// System.out.println("SCU Session closed..");
			}

			if(DicomConfig.createInstance().isTempStorageInUse())
			{
				String strFilePath = null;
				if(DicomConfig.createInstance().getTempStorageFilePath() == "")
				{
					strFilePath = StandardMethods.getTempDirectoryStorage();
					strFilePath = strFilePath+"DicomSDK";
				}
				else
				{
					strFilePath  = DicomConfig.createInstance().getTempStorageFilePath();
				}
				File objDir = new File(strFilePath);
				File[] objFileList = objDir.listFiles();
				int i=0;
				if(objFileList != null)
				{
					while(objFileList.length >=1)
					{
						objFileList[i].delete();
						i++;
						if(i == objFileList.length)
							break;
					}
				}
				objDir.delete();
			}
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SCUSessionStoped",new Locale(DicomConfig.createInstance().getLocale()))+"["+
						ResourceBundleWrapper.getValue("status", "IPAddress",new Locale(DicomConfig.createInstance().getLocale()))+"= "+objInetSocketAddress.getAddress().getHostAddress()+" , "+
						ResourceBundleWrapper.getValue("status", "Port",new Locale(DicomConfig.createInstance().getLocale())) + "= "+objInetSocketAddress.getPort()+"]");
			}
		}
		return true;
	}

	/**
	 * Retrieves the ClientSideAssociationAgent.
	 * 
	 * @return ClientSideAssociationAgent instance of ClientSideAssociationAgent
	 */
	public ClientSideAssociationAgent getClientSideAssociationAgent() 
	{
		return objClientSideAssociationAgent;
	}

	/**
	 * Sets the ClientSideAssociationAgent.
	 * 
	 * @param objClientSideAssociationAgent instance of ClientSideAssociationAgent.
	 */
	public void setClientSideAssociationAgent(ClientSideAssociationAgent objClientSideAssociationAgent) 
	{
		this.objClientSideAssociationAgent = objClientSideAssociationAgent;
	}

	/**
	 * Retrieves the ServiceAgent.
	 * 
	 * @return ServiceAgent instance of ServiceAgent
	 */
	public ServiceAgent getServiceAgent() 
	{
		return objServiceAgent;
	}

	/**
	 * Sets the ServiceAgent.
	 * 
	 * @param objServiceAgent instance of ServiceAgent.
	 */
	public void setServiceAgent(ServiceAgent objServiceAgent)
	{
		this.objServiceAgent = objServiceAgent;
	}

	/**
	 * Retrieves the PDUReader.
	 * 
	 * @return PDUReader - instance of PDUReader
	 */
	public PDUReader getPduReader() 
	{
		return pduReader;
	}

	/**
	 * Sets the PDUReader.
	 * 
	 * @param pduReader instance of PDUReader
	 */
	public void setPduReader(PDUReader pduReader) 
	{
		this.pduReader = pduReader;
	}

	/**
	 * Retrieves the PDUWriter.
	 * 
	 * @return PDUWriter instance of PDUWriter
	 */
	public PDUWriter getPduWriter() 
	{
		return pduWriter;
	}

	/**
	 * Sets the PDUWriter.
	 * 
	 * @param pduWriter instance of PDUWriter
	 */
	public void setPduWriter(PDUWriter pduWriter)
	{
		this.pduWriter = pduWriter;
	}

	/**
	 * Sets the message listener.
	 * 
	 * @param objSCU object of SCU which is to be set as Message Listener.
	 */
	public void setMessageListener(SCU objSCU)
	{
		objMessageListener = objSCU;
	}

	/**
	 * Adds the async receiver.
	 * 
	 * @param objIAsyncReceiver the obj i async receiver
	 */
	public void addAsyncReceiver(IAsyncReceiver objIAsyncReceiver)
	{
		this.objIAsyncReceiver = objIAsyncReceiver;
	}


	/**
	 * Returns socket communication status.
	 * @return true if the secure otherwise false
	 */
	public boolean isSecured() 
	{
		return isSecured;
	}

	/**
	 * sets socket communication status.
	 * @param isSecured the isSecured to set
	 */
	public void setSecured(boolean isSecured)
	{
		this.isSecured = isSecured;
	}

	/**
	 * Returns setting for Socket read timeout of this session, 
	 *  0 returns implies that the option is disabled (i.e., timeout of infinity).
	 * @return the setting for socket read timeout.
	 */
	public int getTimeOut() 
	{
		return itimeout;
	}

	/**
	 *  sets  Socket read timeout of this session, in
	 *  milliseconds.  With this option set to a non-zero timeout,
	 *  a read() call on the InputStream associated with this Socket
	 *  will block for only this amount of time.If the timeout expires,
	 *  a session is closed,The timeout must be > 0. A timeout of zero
	 *  is interpreted as an infinite timeout. 
	 * @param timeout the specified timeout, in milliseconds.
	 */
	public void setTimeOut(int itimeout)
	{
		this.itimeout = itimeout;
	}

	/**
	 * 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.getLogger().log(ex.getMessage() + " " +ErrorCode.toString());
		}
		if(objExceptionListener != null)
			objExceptionListener.onException(ex, ErrorCode);
		else
		{
			switch(ErrorCode)
			{
			case REMOTE_CONNECTION_CLOSED:
			case PDUREADER_CLOSED:
			case SOCKET_TIMEOUT:
				close();
				break;
			case INVALID_COMMANDSET:
				if(getAssociationStatus())
				{

					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				break;
			case INVALID_PDUTYPE:
				if(getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				break;
			case INVALID_ASSOCIATION_PRIMITIVE:
				if(getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				break;
			case AGRRED_PDU_LENGTH_VIOLATED:
				if(getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				break;
			case ASSOCIATION_NOT_EXISTS:
				break;
			case UNEXPECTED_DIMSE_ASSOCIATION:
				break;
			case UNKNOWN:
				if(getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				close();
				break;
			case INVALID_DICOM_MESSAGE:
				if(getAssociationStatus())
				{
					AAbort objAAbort = new AAbort();
					objAAbort.setReason(EnumAbortReason.INVALID_PDU_PARAM_VALUE);
					objAAbort.setSource(EnumAbortSource.SERVICE_PROVIDER);
					sendAssociationMessage(objAAbort);
				}
				break; 
			}
		}
	}
}
