/*******************************************************************************
 *  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.IOException;
import java.util.Locale;


import cdac.medinfo.sdk.dcm30_04.enums.EnumMetaSOPClassUID;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.enums.EnumSOPClassUIDs;
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.ACPresContext;
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.AgreedPresContext;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociateAC;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociateRJ;
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.IDIMSEAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.InvalidAssociationPrimitiveException;
import cdac.medinfo.sdk.dcm30_04.services.association.RQPresContext;
import cdac.medinfo.sdk.dcm30_04.services.association.RQUserInfo;
import cdac.medinfo.sdk.dcm30_04.services.association.SOPClassExtendedNegotiationSubItemFields;
import cdac.medinfo.sdk.dcm30_04.services.association.UserInfo;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.AssociatePDUException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.AssociationConstants;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.AssociationException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumCommunicationMode;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumErrorCode;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumLevelofDigitalSignatureSupport;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumLevelofSupport;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumRejectReason;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidPDUTypeException;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DataNotificationEvent;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IAssociationStatusListener;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IDataNotificationReceiver;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AAbortPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AReleaseRQorRPPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AssociateACPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AssociateEvent;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AssociateRJPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AssociateRQPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.IAssociatePDUReceiver;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDUReader;
import cdac.medinfo.sdk.dcm30_04.services.pdu.PDUWriter;
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.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;

/**
 * This class provides communication between DataNotificationReceiver and PDUHandler.
 * It implements receiving and sending process of Association PDU's or commands between DataNotificationReceiver and PDUHandler.
 * It also generates AgreedAssociation and implements required validation.     
 */
public class ClientSideAssociationAgent implements IAssociatePDUReceiver 
{
	/** Instance of PDU Writer */
	PDUWriter pduWriter;

	/** Instance of PDU Reader */
	PDUReader pduReader;

	/** Instance of data notification receiver. */
	IDataNotificationReceiver objIDataNotificationReceiver = null;

	/** Instance of default association. */
	DefaultAssociation objDefaultAssociation = null;

	/** Instance of Association Status Listener */
	IAssociationStatusListener objIAssociationStatusListener = null;

	/** Instance of dimse association. */
	IDIMSEAssociation objIDimseAssociation = null;

	/** Instance of agreed association. */
	AgreedAssociation objAgreedAssociation = null;

	/** Instance of  associate RQ. */
	AssociateRQ objAssociateRQ = null;

	/** boolean variable representing status of Association */
	boolean isAlive , isACReceived;


	/**
	 * Instantiates a new association agent.
	 * @param pduWriter instance of PDU Writer.
	 * @param pduReader instance of PDU reader
	 */
	public ClientSideAssociationAgent(PDUWriter pduWriter,PDUReader pduReader)
	{
		this.pduWriter = pduWriter;
		this.pduReader = pduReader;
	}

	/**
	 * Sets the Default settings of Association.
	 * @param objDefaultAssociation  instance of Default Association.
	 */
	public void setDefaultAssociation(DefaultAssociation objDefaultAssociation) 
	{
		this.objDefaultAssociation = objDefaultAssociation;
	}

	/**
	 * Retrieves the Default Association settings.
	 * @return DefaultAssociation - instance of Default Association.
	 */
	public DefaultAssociation getDefaultAssociation() 
	{
		return objDefaultAssociation;
	}

	/**
	 * Retrieves the currently processing DimseAssociation.
	 * @return IDIMSEAssociation - instance of  Dimse Association.
	 */
	public IDIMSEAssociation getDimseAssociation() 
	{
		return objIDimseAssociation;
	}

	/**
	 * Retrieves the PDU Writer.
	 * @return PDUWriter object of PDU Writer.
	 */
	public PDUWriter getPDUWriter() 
	{
		return pduWriter;
	}

	/**
	 * Sets the PDU Writer.
	 * @param pduWriter object of PDU Writer.
	 */
	public void setPDUWriter(PDUWriter pduWriter) 
	{
		this.pduWriter = pduWriter;
	}

	/**
	 * Sets the currently processing DimseAssociation.
	 * @param objIDimseAssociation instance of DIMSE Association.
	 */
	public void setDimseAssociation(IDIMSEAssociation objIDimseAssociation)
	{
		this.objIDimseAssociation = objIDimseAssociation;
	}

	/**
	 * Adds the associate status listener. This method is used to get updated regarding the status
	 * of the Association. Implementor of the {@link IAssociationStatusListener} interface will
	 * implement the setAssociationStatus method to receive the update.
	 * @param objIAssociationStatusListener instance of Association Status Listener.
	 */
	public void addAssociationStatusListener(IAssociationStatusListener objIAssociationStatusListener)
	{
		this.objIAssociationStatusListener = objIAssociationStatusListener;
	}

	/**
	 * This method is invoked when a Association PDU arrives from PDUHandler.
	 * It generates Association Command from the PDU arrived and notifies the DataNotificationReceiver.
	 * @param objAssociateEvent instance of AssociateEvent.
	 */
	public void associatePDUReceive(AssociateEvent objAssociateEvent) throws IOException
	{
		try
		{
			DataNotificationEvent objDataNotificationEvent = new DataNotificationEvent(this);
			EnumPDUType enumPDUType = objAssociateEvent.getPDU().getPDUType();
			switch(enumPDUType)
			{
			case ASSOCIATE_AC : 
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "AssociateACReceived",new Locale(DicomConfig.createInstance().getLocale())));
				}
				isACReceived = true;
				AssociateACPDU objAssociateACPDU = (AssociateACPDU)objAssociateEvent.getPDU();
				AssociateAC objAssociateAC = new AssociateAC();
				objAssociateAC.setAssociateACPDU(objAssociateACPDU);
				objIDimseAssociation = objAssociateAC;
				objIDimseAssociation.validate();
				isAlive = true;
				objIAssociationStatusListener.setAssociationStatus(isAlive);
				createAgreedAssociation();
				//This block of code sets max Read and Write Length
				pduReader.setReadMaxLength(objAgreedAssociation.getReadMaxLength());
				pduWriter.setWriteMaxLength(objAgreedAssociation.getWriteMaxLength());

				objDataNotificationEvent.setEnumPDUType(EnumPDUType.ASSOCIATE_AC);
				objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
				synchronized(this)
				{
					this.notify();
				}
			}
			break;
			case ABORT :
			{

				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "AbortPDUReceived",new Locale(DicomConfig.createInstance().getLocale())));
				}
				AAbortPDU objAAbortPDU = (AAbortPDU)objAssociateEvent.getPDU();
				AAbort objAAbort = new AAbort();
				objAAbort.setAAbortPDU(objAAbortPDU);
				objIDimseAssociation = objAAbort;
				objDataNotificationEvent.setEnumPDUType(EnumPDUType.ABORT);
				isAlive = false;
				objAgreedAssociation = null;
				objIAssociationStatusListener.setAssociationStatus(isAlive);
				objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
				synchronized(this)
				{
					this.notify();
				}
			}
			break;
			case RELEASE_RP :
			{


				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "AssociateRPReceived",new Locale(DicomConfig.createInstance().getLocale())));
				}
				AReleaseRQorRPPDU objReleaseRQorRPPDU = (AReleaseRQorRPPDU)objAssociateEvent.getPDU();
				AReleaseRQorRP objAReleaseRP = new AReleaseRQorRP(EnumPDUType.RELEASE_RP);
				objAReleaseRP.setAReleaseRQorRPPDU(objReleaseRQorRPPDU);
				objIDimseAssociation = objAReleaseRP;
				isAlive = false;
				objAgreedAssociation = null;
				objIAssociationStatusListener.setAssociationStatus(isAlive);
				objDataNotificationEvent.setEnumPDUType(EnumPDUType.RELEASE_RP);
				objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
				synchronized(this)
				{
					this.notify();
				}
			}
			break;
			case ASSOCIATE_RJ :
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "AssociateRJReceived",new Locale(DicomConfig.createInstance().getLocale())));
				}
				AssociateRJPDU objAssociateRJPDU = (AssociateRJPDU)objAssociateEvent.getPDU();
				AssociateRJ objAssociateRJ = new AssociateRJ();
				objAssociateRJ.setAssociateRJPDU(objAssociateRJPDU);
				objIDimseAssociation = objAssociateRJ;
				objIDimseAssociation.validate();
				isAlive = false;
				if (DicomConfig.createInstance().isLoggingOn())
				{
					EnumRejectReason enumRejectReason = objAssociateRJ.getReason();
					DicomLogger objDicomLogger = DicomLogger.getLogger();

					if(EnumRejectReason.CALLED_AE_TITLE_NOT_RECOGNIZED == enumRejectReason)
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.CalledAETitleNotMatched",new Locale(DicomConfig.createInstance().getLocale())));
					else if(EnumRejectReason.SP_ACSE_PROTOCOL_VERSION_NOT_SUPPORTED == enumRejectReason)
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.RequestWithUnsupportedAbstractSyntax",new Locale(DicomConfig.createInstance().getLocale())));
					else if(EnumRejectReason.NO_REASON == enumRejectReason)
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.RequestWithUnsupportedTransferSyntax",new Locale(DicomConfig.createInstance().getLocale())));
				}
				objIAssociationStatusListener.setAssociationStatus(isAlive);
				objDataNotificationEvent.setEnumPDUType(EnumPDUType.ASSOCIATE_RJ);
				objIDataNotificationReceiver.notifyData(objDataNotificationEvent);
				synchronized(this)
				{
					this.notify();
				}
				break;
			}
			default :
			{
				throw new InvalidPDUTypeException(enumPDUType.toString());
			}
			}
		}
		catch(InvalidAssociationPrimitiveException iape)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(iape.getLocalizedMessage());
			}
			objIDataNotificationReceiver.onException(new AssociationException(iape) ,EnumErrorCode.INVALID_ASSOCIATION_PRIMITIVE);
		}
		catch(AssociatePDUException ape)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ape.getLocalizedMessage());
			}
			objIDataNotificationReceiver.onException(new AssociationException(ape),EnumErrorCode.ASSOCIATION_NOT_EXISTS);
		}
	}

	/**
	 * This method generates Association PDU according to the arrived Association command and
	 * redirect it to PDUHandler. 
	 * @param objIDimseAssociation instance of DIMSE Association.
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @return boolean - shows status as success or failure. 
	 */
	public boolean sendAssociationMessage(IDIMSEAssociation objIDimseAssociation)
	{
		try
		{
			EnumPDUType enumPDUType = objIDimseAssociation.getPDUType();

			switch(enumPDUType)
			{
			case ABORT :
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SendingAbort",new Locale(DicomConfig.createInstance().getLocale())));
				}
				AAbort objAAbort = (AAbort)objIDimseAssociation;
				AAbortPDU objAAbortPDU = objAAbort.getAAbortPDU();
				isAlive = false;
				objAgreedAssociation = null;
				objIAssociationStatusListener.setAssociationStatus(isAlive);
				if(pduWriter.sendData(objAAbortPDU))
					return true;
				else
					return false;
			}
			case ASSOCIATE_RQ:
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SendingAssociateRQ",new Locale(DicomConfig.createInstance().getLocale())));
				}
				objAssociateRQ = (AssociateRQ)objIDimseAssociation;
				AssociateRQPDU objAssociateRQPDU = objAssociateRQ.getAssociateRQPDU();
				if(pduWriter.sendData(objAssociateRQPDU))
				{
					synchronized(this)
					{
						if(!isACReceived)
						{
							this.wait();
						}
					}
					isACReceived = false;					
					return isAlive;
				}
				else
					return false;
			}
			case RELEASE_RQ:
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "SendingReleaseRQ",new Locale(DicomConfig.createInstance().getLocale())));
				}
				AReleaseRQorRPPDU objAReleaseRQorRPPDU = new AReleaseRQorRPPDU(EnumPDUType.RELEASE_RQ);
				if(pduWriter.sendData(objAReleaseRQorRPPDU))
				{
					synchronized(this)
					{
						if(isAlive)
							this.wait();
					}
					return true;//We should return the status of this method as success or failure ,
					//which caller can check for successful complition instead of checking for the status of association.
				}
				else
					return false;
			}
			default :
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "Association.UnexpectedDIMSEAssociation", new Locale(DicomConfig.createInstance().getLocale()))+ enumPDUType);
				}
			}
			}
		}
		catch(InterruptedException ie)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ie);
			}
		}
		return false;
	}

	/**
	 * Sets the DataNotificationListener.
	 * @param objIDataNotificationReceiver object of Data Notification Receiver.
	 */
	public void addDataNotificationListener(IDataNotificationReceiver objIDataNotificationReceiver)
	{
		this.objIDataNotificationReceiver = objIDataNotificationReceiver;
	}

	/**
	 * Sets the current status of the on going association.
	 * @param status  boolean variable representing association status
	 * true - if association is alive
	 * false - if association does not exits.
	 */
	public void setAssociationStatus(boolean status)
	{
		isAlive = status;
		if(!isAlive)
		{
			synchronized(this)
			{
				this.notify();
			}
		}
	}

	/**
	 * Retrieves the status of association.
	 * @return boolean - boolean variable representing association status
	 * 					 true - if association is alive
	 * 					 false - if association does not exits.
	 */
	public boolean getAssociationStatus()
	{
		return isAlive;
	}

	/**
	 * Retrieves the AgreedAssociation.
	 * @return AgreedAssociation - instance of AgreedAssociation
	 */
	public AgreedAssociation getAgreedAssociation()
	{
		return objAgreedAssociation;
	}

	/**
	 * Retrieves PDU Reader.
	 * @return PDUReader - instance of PDU Reader.
	 */
	public PDUReader getPDUReader()
	{
		return pduReader;
	}

	/**
	 * Sets the PDU Reader.
	 * @param objPDUReader object of PDU Reader.
	 */
	public void setPDUReader(PDUReader objPDUReader)
	{
		this.pduReader = objPDUReader;
	}

	/**
	 * Sets the AgreedAssociation
	 * @param objAgreedAssociation object of Agreed Association.
	 */
	public void setAgreedAssociation(AgreedAssociation objAgreedAssociation)
	{
		this.objAgreedAssociation = objAgreedAssociation;
	}


	/**
	 * Creates the AgreedAssociation according to the RQ and AC of association.
	 * @param enumCommMode Enum value of communication mode.
	 */
	private void createAgreedAssociation()
	{
		/*
		 * This block of code sets AgreedAssociation from Association AC. 
		 */
		AssociateAC objAssociateAC = (AssociateAC)objIDimseAssociation;
		objAgreedAssociation = new AgreedAssociation();
		objAgreedAssociation.setAppContextName(objAssociateAC.getAppContextName());
		objAgreedAssociation.setCalledAETitle(objAssociateAC.getCalledEntityTitle());
		objAgreedAssociation.setCallingAETitle(objAssociateAC.getCallingEntityTitle());
		objAgreedAssociation.setProtocolVersion(objAssociateAC.getProtocolversion());
		//objAgreedAssociation.setCommMode(enumCommMode);

		/*
		 * If UserInfo is present in AC then it sets it in AgreedAssociation because UserInfo is optional in RQ and AC.
		 * This block is setting communication mode according to the sent RQ and arrived AC
		 */
		UserInfo objACUserInfo = objAssociateAC.getUserInfo();
		if(objACUserInfo != null)
		{
			objAgreedAssociation.setUserInfo(objACUserInfo);
			ISingleCollection<SOPClassExtendedNegotiationSubItemFields> subItemColl = objACUserInfo.getSOPClassExtendedNegotiationSubItemFields();
			if(subItemColl != null && !subItemColl.isEmpty())
			{
				ICollectionIterator<SOPClassExtendedNegotiationSubItemFields>iterator = subItemColl.iterator();
				if(iterator.hasNext())
				{
					SOPClassExtendedNegotiationSubItemFields subItem = iterator.next();
					String strExtSOPClass = subItem.getSOPClassExtNegotiationSOPclassuid();
					EnumLevelofSupport enumLevelofSupport = subItem.getLevelofSupport();
					EnumLevelofDigitalSignatureSupport enumLevelOfDigitalSigSupport = subItem.getLevelofDigitalSignatureSupport();

					if (DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("status", "LevelOfSupport",new Locale(DicomConfig.createInstance().getLocale())) + enumLevelofSupport.name());

						objDicomLogger.log(ResourceBundleWrapper.getValue("status", "LevelOfSupportInfo",new Locale(DicomConfig.createInstance().getLocale())));
					}							

					if(enumLevelofSupport.equals(EnumLevelofSupport.LEVEL2))
					{
						if (DicomConfig.createInstance().isLoggingOn())
						{
							DicomLogger objDicomLogger = DicomLogger.getLogger();
							objDicomLogger.log(ResourceBundleWrapper.getValue("status", "AssociationNegotiation.ExtendedNegotiationInfo",new Locale(DicomConfig.createInstance().getLocale()))
									+ strExtSOPClass);

							if(enumLevelOfDigitalSigSupport.equals(EnumLevelofDigitalSignatureSupport.LEVEL0))
							{
								objDicomLogger.log(ResourceBundleWrapper.getValue("status", "DSPLevel_0",new Locale(DicomConfig.createInstance().getLocale())));
							}
							else if(enumLevelOfDigitalSigSupport.equals(EnumLevelofDigitalSignatureSupport.LEVEL1))
							{
								objDicomLogger.log(ResourceBundleWrapper.getValue("status", "DSPLevel_1",new Locale(DicomConfig.createInstance().getLocale())));
							}
							else if(enumLevelOfDigitalSigSupport.equals(EnumLevelofDigitalSignatureSupport.LEVEL2))
							{
								objDicomLogger.log(ResourceBundleWrapper.getValue("status", "DSPLevel_2",new Locale(DicomConfig.createInstance().getLocale())));
							}
							else if(enumLevelOfDigitalSigSupport.equals(EnumLevelofDigitalSignatureSupport.LEVEL3))
							{
								objDicomLogger.log(ResourceBundleWrapper.getValue("status", "DSPLevel_3",new Locale(DicomConfig.createInstance().getLocale())));
							}
						}
					}
				}
			}
			short shOpsInvoked = objACUserInfo.getMaxNumOpsPerformed();
			short shOpsPerformed = objACUserInfo.getMaxNumOpsInvoked();
			if(shOpsInvoked == 1 && shOpsPerformed == 1)
			{
				objAgreedAssociation.setCommMode(EnumCommunicationMode.SYNCHRONOUS);
				if (DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.SynchronousCommunicationModeNegotiated",new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
			else if(shOpsInvoked == 0 && shOpsPerformed == 0)
			{
				objAgreedAssociation.setCommMode(EnumCommunicationMode.UNBOUNDED_ASYNCHRONOUS);
				if (DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.AsynchronousCommunicationModeNegotiated",new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
			else
			{
				objAgreedAssociation.setCommMode(EnumCommunicationMode.BOUNDED_ASYNCHRONOUS);
				if (DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.AsynchronousCommunicationModeNegotiated",new Locale(DicomConfig.createInstance().getLocale())));
				}
			}
		}
		else
		{
			objAgreedAssociation.setCommMode(EnumCommunicationMode.SYNCHRONOUS);
			if (DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.SynchronousCommunicationModeNegotiated",new Locale(DicomConfig.createInstance().getLocale())));
			}
		}

		/*
		 * This block of code sets the Read max length in AgreedAssociation.
		 * Read max length is the reading capacity of calling Entity.
		 * If max length is not given in RQ then it sets the max length from DefaultAssociation generated from file.
		 * else sets the max length of AgreedAssociation given in RQ.  
		 */
		RQUserInfo objRQTempUserInfo = objAssociateRQ.getRQUserInfo();
		if(objRQTempUserInfo == null || objRQTempUserInfo.getmaxlength() <= 0)
			objAgreedAssociation.setReadMaxLength(16384);
		else
			objAgreedAssociation.setReadMaxLength(objRQTempUserInfo.getmaxlength());

		if (DicomConfig.createInstance().isLoggingOn())
		{
			DicomLogger objDicomLogger = DicomLogger.getLogger();
			objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.MaxLengthNegotiated",new Locale(DicomConfig.createInstance().getLocale())) + objAgreedAssociation.getReadMaxLength());
		}

		/*
		 * This block of code sets the Write max length in AgreedAssociation.
		 * Write max length is the reading capacity of called Entity.
		 * If max length is not given in AC sent from called Entity then it sets it as max length of calling entity.
		 * else sets max length given in AC.
		 */
		if(objACUserInfo == null || objACUserInfo.getmaxlength() <= 0)
			objAgreedAssociation.setWriteMaxLength(objAgreedAssociation.getReadMaxLength());
		else
			objAgreedAssociation.setWriteMaxLength(objACUserInfo.getmaxlength());

		//Initializing collection of AgreedPresContext through comparison of Association RQ and AssociateAC
		ISingleCollection<AgreedPresContext> objAgreedPresCollection = new SingleCollection<AgreedPresContext>(); 
		AgreedPresContext objAgreedPresContext = null;
		ACPresContext objACPresContext = null;
		RQPresContext objRQPresContext = null;

		IPairedCollection<Byte,ACPresContext> objACPresContextCollection = objAssociateAC.getACPresContextCollection();
		Object []arrObjPresContextID = objACPresContextCollection.getKeys();
		Byte []arrByteACPresContextID = new Byte[arrObjPresContextID.length];
		int length = 0;
		while(length < arrObjPresContextID.length)
		{
			arrByteACPresContextID[length] = (Byte.parseByte(arrObjPresContextID[length].toString()));
			length++;
		}


		IPairedCollection<Byte,RQPresContext> objRQPresContextCollection = objAssociateRQ.getRQPresContextCollection();
		arrObjPresContextID = objRQPresContextCollection.getKeys();
		Byte []arrByteRQPresContextID = new Byte[arrObjPresContextID.length];
		length = 0;
		while(length < arrObjPresContextID.length)
		{
			arrByteRQPresContextID[length] = (Byte.parseByte(arrObjPresContextID[length].toString()));
			length++;
		}

		/*
		 * AC does not contain AbstractSyntax.
		 * AC and RQ has one common field PresContext ID.
		 * This block of code compares this PresContextID
		 * and sets the accepted AbsSyntax along with it's PresContextID in AgreedAssociation.
		 * 
		 * This loop iterates through the RQPresContextCollection for each value of ACPresContextCollection
		 * and compares PresContextID of AC with RQ and sets corresponding fields in AgreedAssociation if match found. 
		 */
		for(int i=0 ; i < arrByteACPresContextID.length ; i++)
		{
			for(int j = 0; j < arrByteRQPresContextID.length ; j++)
			{
				if(arrByteACPresContextID[i] == arrByteRQPresContextID[j])
				{
					objACPresContext = objACPresContextCollection.get(arrByteACPresContextID[i]);
					objRQPresContext = objRQPresContextCollection.get(arrByteRQPresContextID[j]);
					if(objACPresContext.getReason().getValue() != AssociationConstants.ASSOCIATION_REASON_TRANSFER_SYNTAX_NOT_SUPPORTED)
					{
						objAgreedPresContext = new AgreedPresContext();
						String strAbstractSyntaxName = objRQPresContext.getAbstractSyntaxName();

						objAgreedPresContext.setAbstractSyntaxName(strAbstractSyntaxName);
						objAgreedPresContext.setPresContextId(arrByteRQPresContextID[j]);
						objAgreedPresContext.setReason(objACPresContext.getReason().getValue());
						objAgreedPresContext.setTS(objACPresContext.getTransferSyntaxName());
						objAgreedPresCollection.add(objAgreedPresContext);

						EnumMetaSOPClassUID enumMetaSOPClassUID = getMetaSOPClassUID(strAbstractSyntaxName);
						if(enumMetaSOPClassUID != null)
						{
							String strReplacedUID = "";
							ISingleCollection<String> objAllSOPClassUID = getAllSOPClassUID(enumMetaSOPClassUID);
							if(objAllSOPClassUID != null && objAllSOPClassUID.size() != 0)
							{
								for( String strSOPClassUID : objAllSOPClassUID)
								{
									strReplacedUID += strSOPClassUID + ":";
									objAgreedPresContext = new AgreedPresContext();
									objAgreedPresContext.setAbstractSyntaxName(strSOPClassUID);
									objAgreedPresContext.setPresContextId(arrByteRQPresContextID[j]);
									objAgreedPresContext.setReason(objACPresContext.getReason().getValue());
									objAgreedPresContext.setTS(objACPresContext.getTransferSyntaxName());
									objAgreedPresCollection.add(objAgreedPresContext);
								}
								if (DicomConfig.createInstance().isLoggingOn())
								{
									DicomLogger objDicomLogger = DicomLogger.getLogger();
									objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.NegotiationDoneOnMetaSOPClassUID",new Locale(DicomConfig.createInstance().getLocale())) + enumMetaSOPClassUID.getValue());
									objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.SupportedUIDForMetaValue",new Locale(DicomConfig.createInstance().getLocale())) + strReplacedUID);
								}
							}
						}
					}
					else
					{
						if (DicomConfig.createInstance().isLoggingOn())
						{
							DicomLogger objDicomLogger = DicomLogger.getLogger();
							objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.TransferSyntaxNotSupported",new Locale(DicomConfig.createInstance().getLocale())) + objRQPresContext.getAbstractSyntaxName());
						}
					}
					break;
				}
			}
		}
		/*
		 * This block of code generating log for all abstract syntax which were requested but not supported on SCP side.
		 */
		if (DicomConfig.createInstance().isLoggingOn())
		{
			RQPresContext objRQTemPresContext = null;
			for(int i=0 ; i < arrByteRQPresContextID.length ; i++)
			{
				objRQTemPresContext = objRQPresContextCollection.get(arrByteRQPresContextID[i]);
				boolean isSupported = false;
				for(int j = 0; j < arrByteACPresContextID.length ; j++)
				{
					if(arrByteACPresContextID[j] == arrByteRQPresContextID[i])
					{
						isSupported = true;
						break;
					}
				}
				if(!isSupported)
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "AssociationNegotiation.AbstractSyntaxNotSupported",new Locale(DicomConfig.createInstance().getLocale())) + objRQTemPresContext.getAbstractSyntaxName());
				}
			}
		}
		objAgreedAssociation.setAgreedPresContextCollection(objAgreedPresCollection);
	}

	/*
	 * This function gets all the SOP Class related to the Meta SOP Class.
	 */
	private ISingleCollection<String> getAllSOPClassUID(EnumMetaSOPClassUID enumMetaSOPClassUID)
	{
		ISingleCollection<String> objSingleCollection = new SingleCollection<String>();
		switch(enumMetaSOPClassUID)
		{
		case BASIC_GRAYSCALE_PRINT_MANAGEMENT_META_SOP_CLASS :
			objSingleCollection.add(EnumSOPClassUIDs.BASICFILMSESSIONIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.BASICFILMBOXIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.BASICGRAYSCALEIMAGEBOX.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.PRINTERIOD.getValue());
			return objSingleCollection;
		case BASIC_COLOR_PRINT_MANAGEMENT_META_SOP_CLASS :
			objSingleCollection.add(EnumSOPClassUIDs.BASICFILMSESSIONIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.BASICFILMBOXIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.BASICCOLORIMAGEBOX.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.PRINTERIOD.getValue());
			return objSingleCollection;
		case PULL_STORED_PRINT_MANAGEMENT_META_SOP_CLASS :
			objSingleCollection.add(EnumSOPClassUIDs.PULLPRINTREQUESTIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.PRINTERIOD.getValue());
			return objSingleCollection;
		case STUDY_MANAGEMENT_META_SOP_CLASS :
			objSingleCollection.add(EnumSOPClassUIDs.DETACHEDSTUDYMANAGEMENTIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.STUDYCOMPONENTMANAGEMENT.getValue());
			return objSingleCollection;
		case DETACHED_RESULT_MANAGEMENT_META_SOP_CLASS:
			objSingleCollection.add(EnumSOPClassUIDs.DETACHEDRESULTSMANAGEMENTIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.DETACHEDINTERPRETATIONMANAGEMENTIOD.getValue());
			return objSingleCollection;
		case DETACHED_PATIENT_MANAGEMENT_META_SOP_CLASS:
			objSingleCollection.add(EnumSOPClassUIDs.DETACHEDPATIENTMANAGEMENTIOD.getValue());
			objSingleCollection.add(EnumSOPClassUIDs.DETACHEDVISITMANAGEMENTIOD.getValue());
			return objSingleCollection;
		}
		return null;
	}

	/*
	 * This function returns the Enum value of Meta SOP Class related to strMetaSOPClass.
	 */
	private EnumMetaSOPClassUID getMetaSOPClassUID(String strMetaSOPClassUID)
	{
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.BASIC_COLOR_PRINT_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.BASIC_COLOR_PRINT_MANAGEMENT_META_SOP_CLASS;
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.BASIC_GRAYSCALE_PRINT_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.BASIC_GRAYSCALE_PRINT_MANAGEMENT_META_SOP_CLASS;
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.PULL_STORED_PRINT_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.PULL_STORED_PRINT_MANAGEMENT_META_SOP_CLASS;
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.STUDY_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.STUDY_MANAGEMENT_META_SOP_CLASS;
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.DETACHED_RESULT_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.DETACHED_RESULT_MANAGEMENT_META_SOP_CLASS;
		if(strMetaSOPClassUID.equals(EnumMetaSOPClassUID.DETACHED_PATIENT_MANAGEMENT_META_SOP_CLASS.getValue()))
			return EnumMetaSOPClassUID.DETACHED_PATIENT_MANAGEMENT_META_SOP_CLASS;
		return null;
	}

	/**
	 * 
	 * @param ex
	 */
	public void onException(DicomException ex,EnumErrorCode ErrorCode)
	{
		if(DicomConfig.createInstance().isLoggingOn())
		{
			DicomLogger objDicomLogger = DicomLogger.getLogger();
			objDicomLogger.log(ex.getMessage());
		}
		objIDataNotificationReceiver.onException(new AssociationException(ex),ErrorCode);
	}
}
