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

import java.util.Locale;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AppContextItem;
import cdac.medinfo.sdk.dcm30_04.services.pdu.AssociateRQPDU;
import cdac.medinfo.sdk.dcm30_04.services.pdu.RQPresContextItem;
import cdac.medinfo.sdk.dcm30_04.services.pdu.RQUserInfoItem;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;

/**
 * A-Association RQ service shall be used to create the association.
 * This Class implements the setter and getter for required attributes of Association RQ Command.  
 */
public class AssociateRQ extends AssociateRQOrAC 
{

	/** The RQ presentation context Paired collection */
	IPairedCollection<Byte, RQPresContext> objRQPresContextCollection = null;

	/** The object of RQ user info. */
	RQUserInfo objRQUserInfo;

	private final short shDefProcVersion = (short)1;
	private final String strDefAppContextName = "1.2.840.10008.3.1.1.1"; 
	private final int iDefMaxLength = 16384;
	private final String strDefImplClassUID = "1.2.40.0.13.1.1";
	private final String strDefImplVersionName = "SDK";
	private final short shDefMaxOpsInvoked = (short)1;
	private final short shDefMaxOpsPerformed = (short)1;

	/**
	 * Instantiates Associate RQ.
	 */
	public AssociateRQ()
	{
		objRQPresContextCollection = new PairedCollection<Byte, RQPresContext>();
	}

	/**
	 * Instantiates Associate RQ with RQPresentation Context Collection populated from DataSet Collection.
	 * The other parameters to populate AssociateRQ are populate from DefaultAssociation.xml and DefaultAssociation.xsd
	 * which should be in SupportFiles.
	 * @param objDataSetCollection DataSetCollection to populate RQ Presentation Context collection.
	 */
	public AssociateRQ(ISingleCollection<IDataSet> objDataSetCollection,String strCalledAETitle,String strCallingAETitle)
	{
		this.calledEntityTitle = strCalledAETitle;
		this.callingEntityTitle = strCallingAETitle;
		loadDefault();
		this.objRQPresContextCollection = getRQPresContextCollection(objDataSetCollection);
	}

	/**
	 * Instantiates Associate RQ with objRQPresContextCollection.
	 * The other parameters to populate AssociateRQ are populate from strXMLFile and strXSDFile.
	 * @param objRQPresContextCollection object of RQ Presentation Context Collection.
	 * @param arrenumServiceType array of EnumServiceType for which default association needs to be populated.
	 * @param strXMLFile
	 * @param strXSDFile
	 */
	public AssociateRQ(IPairedCollection<Byte,RQPresContext> objRQPresContextCollection,String strCalledAETitle,String strCallingAETitle)
	{
		this.calledEntityTitle = strCalledAETitle;
		this.callingEntityTitle = strCallingAETitle;
		loadDefault();
		this.objRQPresContextCollection = objRQPresContextCollection;
	}

	/**
	 * Instantiates Associate RQ with parameters given.
	 * @param objDataSetCollection DataSetCollection to populate RQ Presentation Context collection.
	 * @param calledEntityTitle String value of Called AE Title.
	 * @param callingEntityTitle String value of Calling AE Title.
	 * @param protocolversion short value of Protocol Version.
	 * @param objRQUserInfo object of USer Info.
	 */
	public AssociateRQ(ISingleCollection<IDataSet> objDataSetCollection, String calledEntityTitle, String callingEntityTitle,short protocolversion, RQUserInfo objRQUserInfo)
	{
		super(calledEntityTitle,callingEntityTitle,protocolversion);
		this.objRQUserInfo = objRQUserInfo;
		this.objRQPresContextCollection = getRQPresContextCollection(objDataSetCollection);
	}

	/**
	 * Instantiates Associate RQ with parameters given.
	 * @param objRQPresContextCollection
	 * @param calledEntityTitle String value of Called AE Title.
	 * @param callingEntityTitle String value of Calling AE Title.
	 * @param protocolversion short value of Protocol Version.
	 * @param objRQUserInfo object of User Info.
	 */
	public AssociateRQ(IPairedCollection<Byte,RQPresContext> objRQPresContextCollection, String calledEntityTitle, String callingEntityTitle,short protocolversion, RQUserInfo objRQUserInfo)
	{
		super(calledEntityTitle,callingEntityTitle,protocolversion);
		this.objRQUserInfo = objRQUserInfo;
		this.objRQPresContextCollection = objRQPresContextCollection;
	}

	/**
	 * Retrieves the PDU Type.
	 * @return EnumPDUType Enum value of PDU type.
	 */
	public EnumPDUType getPDUType()
	{
		return EnumPDUType.ASSOCIATE_RQ;
	}

	/**
	 * Retrieves the collection of RQ Presentation Context.
	 * @return ISingleCollection<RQPresContext> - collection of RQ Presentation Context.
	 */
	public IPairedCollection<Byte,RQPresContext> getRQPresContextCollection() 
	{
		return objRQPresContextCollection;
	}

	/**
	 * Sets the collection of RQ Presentation Context.
	 * @param objRQPresContextCollection collection of RQ Presentation Context.
	 */
	public void setRQPresContextCollection(IPairedCollection<Byte,RQPresContext> objRQPresContextCollection) 
	{
		this.objRQPresContextCollection = objRQPresContextCollection;
	}

	/**
	 * Retrieves the DICOM Application Entity user information
	 * @return RQUserInfo - instance of RQUserInfo
	 */
	public RQUserInfo getRQUserInfo()
	{
		return objRQUserInfo;
	}

	/**
	 * Sets the DICOM Application Entity user information
	 * @param objRQUserInfo instance of RQUserInfo
	 */
	public void setRQUserInfo(RQUserInfo objRQUserInfo) 
	{
		this.objRQUserInfo = objRQUserInfo;
	}

	/**
	 * Initializes the AssociateRQ according to the passed AssociateRQPDU
	 * @param objAssociateRQPDU instance of AssociateRQPDU
	 */
	public void setAssociateRQPDU(AssociateRQPDU objAssociateRQPDU)
	{
		super.setAssocRQorACPDU(objAssociateRQPDU);
		objRQPresContextCollection = new PairedCollection<Byte,RQPresContext>();
		IPairedCollection<Byte,RQPresContextItem> objRQPresContextItemCollection = objAssociateRQPDU.getRQPresContextItemCollection();
		if(objRQPresContextItemCollection != null)
		{
			for(RQPresContextItem objRQPresContextItem : objRQPresContextItemCollection)
			{
				RQPresContext objRQPresContext = new RQPresContext();
				objRQPresContext.populate(objRQPresContextItem);
				objRQPresContextCollection.put(objRQPresContextItem.getPresContextId(), objRQPresContext);
			}
		}
		objRQUserInfo = new RQUserInfo();
		objRQUserInfo.populate(objAssociateRQPDU.getRQUserInfoItem());
	}

	/**
	 * Initializes the AssociateRQ PDU
	 * @return AssociateRQPDU - instance of AssociateRQPDU
	 */
	public AssociateRQPDU getAssociateRQPDU()
	{
		AssociateRQPDU objAssociateRQPDU = new AssociateRQPDU();
		objAssociateRQPDU.setPDUType(EnumPDUType.ASSOCIATE_RQ);
		objAssociateRQPDU.setCalledAETitle(this.getCalledEntityTitle());
		objAssociateRQPDU.setCallingAETitle(this.getCallingEntityTitle());
		objAssociateRQPDU.setProtocolVersion(this.getProtocolversion());

		AppContextItem objAppContextItem = new AppContextItem();
		objAppContextItem.setAppContextName(this.getAppContextName());
		objAssociateRQPDU.setAppContextItem(objAppContextItem);

		/*
		 * Iterating through collection of RQPresContext and initializing RQPresContext Item.
		 * Populating collection of RQPresContextItem.
		 */
		IPairedCollection<Byte, RQPresContextItem> objRQPresContextItemCollection = new PairedCollection<Byte, RQPresContextItem>();
		if(objRQPresContextCollection != null)
		{
			Object []arrObjPresContextID = objRQPresContextCollection.getKeys();
			Byte []arrBytePresContextID = new Byte[arrObjPresContextID.length];
			int length = 0;
			while(length < arrObjPresContextID.length)
			{
				arrBytePresContextID[length] = (Byte.parseByte(arrObjPresContextID[length].toString()));
				length++;
			}
			int i = 0;
			for(RQPresContext objRQPresContext : objRQPresContextCollection)
			{
				RQPresContextItem objRQPresContextItem = objRQPresContext.getItem();
				objRQPresContextItem.setPresContextId(arrBytePresContextID[i++]);
				objRQPresContextItemCollection.put(objRQPresContextItem.getPresContextId(), objRQPresContextItem);
			}
		}
		objAssociateRQPDU.setRQPresContextItemCollection(objRQPresContextItemCollection);

		if(this.getRQUserInfo() != null)
		{
			RQUserInfoItem objRQUserInfoItem = this.getRQUserInfo().getItem();
			objAssociateRQPDU.setRQUserInfoItem(objRQUserInfoItem);
		}

		return objAssociateRQPDU;
	}	

	private IPairedCollection<Byte,RQPresContext> getRQPresContextCollection(ISingleCollection<IDataSet> objDataSetCollection)
	{
		byte bPresContextId = 1;
		byte bIncrement = 2;
		ICollectionIterator<IDataSet> objDataSetIterator = objDataSetCollection.iterator();
		IPairedCollection<Byte,RQPresContext> objRQPresContextCollection = new PairedCollection<Byte, RQPresContext>();
		while(objDataSetIterator.hasNext())
		{
			IDataSet objIDataSet = objDataSetIterator.next();
			ISingleCollection<String> TSCollection = new SingleCollection<String>();
			TSCollection.add(objIDataSet.getTransferSyntax().getTransferSyntaxUID());
			RQPresContext objRQPresContext = new RQPresContext(objIDataSet.getSOPClassUID(),TSCollection);
			objRQPresContextCollection.put(bPresContextId,objRQPresContext);
			bPresContextId = (byte)(bPresContextId + bIncrement);
		}
		return objRQPresContextCollection;
	}

	/**
	 * Validates Association Request primitive for mandatory attributes
	 * @return boolean - shows status as true or false
	 * @throws InvalidAssociationPrimitiveException if validation fails
	 */
	public boolean validate()throws InvalidAssociationPrimitiveException
	{
		if(super.validate())
		{
			if(objRQPresContextCollection != null)
			{
				//Validating RQPresContext populated in list
				int ival = validate(objRQPresContextCollection);
				
				if(ival == 1 || ival == 2 || ival == 3)
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidPresContext", new Locale(DicomConfig.createInstance().getLocale())));
					}
					throw new InvalidAssociationPrimitiveException(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidPresContext", new Locale(DicomConfig.createInstance().getLocale())));
				}
				else if(ival == 4)
				{
					{
						throw new InvalidAssociationPrimitiveException(ResourceBundleWrapper.getValue("exceptions","AssociationException.DefTSNotPresent",new Locale(DicomConfig.createInstance().getLocale())));
					}
				}
			}
			else
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidPresContext", new Locale(DicomConfig.createInstance().getLocale())));
				}
				throw new InvalidAssociationPrimitiveException(ResourceBundleWrapper.getValue("exceptions", "AssociationException.MissingPresContextList", new Locale(DicomConfig.createInstance().getLocale())));
			}

			if(objRQUserInfo != null && !objRQUserInfo.validate())
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidUserinfo", new Locale(DicomConfig.createInstance().getLocale())));
				}
				throw new InvalidAssociationPrimitiveException(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidUserinfo", new Locale(DicomConfig.createInstance().getLocale())));
			}
		}
		else
			throw new InvalidAssociationPrimitiveException(ResourceBundleWrapper.getValue("exceptions", "AssociationException.InvalidAttribute", new Locale(DicomConfig.createInstance().getLocale())));

		return true;
	}

	/*
	 * Populates the default values for DefaultAssociation if values not initialized
	 */
	private void loadDefault()
	{
		if(this.protocolversion == 1)
			this.protocolversion = shDefProcVersion;
		if(strAppContextName == null)
			this.strAppContextName = strDefAppContextName;
		if(objRQUserInfo == null)
			objRQUserInfo = new RQUserInfo();

		if(objRQUserInfo.getmaxlength() == -1)
			objRQUserInfo.setmaxlength(iDefMaxLength);
		if(objRQUserInfo.getImplclassUID() == null)
			objRQUserInfo.setImplclassUID(strDefImplClassUID);
		if(objRQUserInfo.getImplVersionName() == null)
			objRQUserInfo.setImplVersionName(strDefImplVersionName);
		if(objRQUserInfo.getMaxNumOpsPerformed() == 1)
			objRQUserInfo.setMaxNumOpsPerformed(shDefMaxOpsPerformed);
		if(objRQUserInfo.getMaxNumOpsInvoked() == 1)
			objRQUserInfo.setMaxNumOpsInvoked(shDefMaxOpsInvoked);
	}

	/**
	 * Validates the Collection of Presentation context. Where if Associate RQ contains more than one Transfer
	 * Syntax for same Abstract Syntax, then default transfer syntax corresponding to that abstract syntax should
	 * be present. 
	 * @param objRQPresContextCollection Paired Collection of RQPress Context 
	 * @return integer 1 - if AbstractSyntaxName is null.
	 * 				   2 - if collection of TS or its size is null.
	 * 				   3 - if collection of TS contains null value.
	 * 				   4 - if collection of TS does not contain default value of TS for corresponding AS.
	 * 				   0 - if validation is successful.
	 */
	private int validate(IPairedCollection<Byte, RQPresContext> objRQPresContextCollection)
	{
		Object[] objTempKeyList = objRQPresContextCollection.getKeys();
		Byte[] objKeyList = new Byte[objTempKeyList.length];
		int i =0;
		for(Object object : objTempKeyList)
		{
			objKeyList[i]= (Byte)object;
			i++;
		}
		
		ISingleCollection<Byte> objVerifiedRQPresContextIDCollection = new SingleCollection<Byte>();
		for(Byte bKey : objKeyList)
		{
			// Skip already verified RQPresContext
			if(objVerifiedRQPresContextIDCollection.contains(bKey))
				continue;
			RQPresContext objRQPresContext = objRQPresContextCollection.get(bKey);
			int ivalForsinglePressCon = objRQPresContext.validate();
			if(ivalForsinglePressCon != 0)
				return ivalForsinglePressCon;
			
			String strAbsSyntax = objRQPresContext.getAbstractSyntaxName();
			ISingleCollection<String> objCurrentTS = objRQPresContext.getTSCollection();

			ISingleCollection<String> objCompleteTSCollectionForAbs = new SingleCollection<String>();
			for(String str : objCurrentTS)
			{
				if(!objCompleteTSCollectionForAbs.contains(str))
					objCompleteTSCollectionForAbs.add(str);
			}
			// When validate is called and Transfer syntax are added in collection , add that key to 
			// verified RQPresContextIDCollection
			objVerifiedRQPresContextIDCollection.add(bKey);
			
			
			// This loop will iterate through collection and fill CompleteTSCollection available For a single AbsSyntax
			for(Byte bInnerKey : objKeyList)
			{
				if(objVerifiedRQPresContextIDCollection.contains(bInnerKey))
					continue;
				RQPresContext objExistingRQPresContext = objRQPresContextCollection.get(bInnerKey);
				String strExistingAbsSyntax = objExistingRQPresContext.getAbstractSyntaxName();
				if(strAbsSyntax.equalsIgnoreCase(strExistingAbsSyntax))
				{
					int iVlaidateResult = objRQPresContext.validate();
					if(iVlaidateResult != 0)
						return iVlaidateResult;
					ISingleCollection<String> objExistingTS = objExistingRQPresContext.getTSCollection();
					for(String str : objExistingTS)
					{
						if(!objCompleteTSCollectionForAbs.contains(str))
							objCompleteTSCollectionForAbs.add(str);
					}
					objVerifiedRQPresContextIDCollection.add(bInnerKey);
				}
			}
			RQPresContext TempRQPressContext = new RQPresContext(strAbsSyntax, objCompleteTSCollectionForAbs);
			int ival = TempRQPressContext.validate(objCompleteTSCollectionForAbs);
			if(ivalForsinglePressCon != 0)
				return ival;
		}				
		return 0;
	}
}