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

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


import cdac.medinfo.sdk.dcm30_04.baselibs.ExplicitDE;
import cdac.medinfo.sdk.dcm30_04.baselibs.ImplicitDE;
import cdac.medinfo.sdk.dcm30_04.baselibs.Tag;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataElement;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomValue;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDictionaryReader;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IModule;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITag;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITransferSyntax;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidModuleException;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;


/**
 * This class provides basic functionality needed by all the modules
 * i.e. Adding DataElement, validating module, iterating over the
 */
public abstract class Module implements IModule 
{
	
	//Previously here static Collection was written, but it generate problem
	//while Parse/Serialize. So it is remove from here.

	ITransferSyntax objITransferSyntax;

	//Now,each Module will have the reference to the DataSet
	//All the Attributes in each Module will now be added in DataSet
	IDataSet objIDataSet;

	//Dictionary Reader object now will be set on IOD and the same reference 
	//will be used by all the modules.This is needed to give user the facility
	//to set custom dictionary. This we will confirm that there always will
	//be only one reference of Dictionary Reader getting passed to all Modules
	IDictionaryReader objIDictionaryReader;

	/**
	 * This construction initializes module with data set.
	 * @param objIDataSet instance of data set for initializing the module.
	 */
	Module(IDataSet objIDataSet)
	{
		objITransferSyntax = objIDataSet.getTransferSyntax();
		objIDictionaryReader = objIDataSet.getDictionaryReader();
		this.objIDataSet = objIDataSet;
	}

	//public abstract boolean addDataElement(IDataElement objIDataElement);
	public abstract boolean validateModule();

	/**
	 * This method returns value of DicomValue.
	 * @param iGroupNumber integer value of Group Number.
	 * @param iElementNumber integer value of element number
	 * @return Object - value of the Data Element.
	 */
	protected Object getValue(int iGroupNumber, int iElementNumber)
	{
		IDataElement objIDataElement =  objIDataSet.getDataForTag(iGroupNumber,iElementNumber);
		if(objIDataElement == null)
			return null;
		IDicomValue objIDicomValue = objIDataElement.getDicomValue();
		if(objIDicomValue == null)
			return null;
		return objIDicomValue.getValue();
	}
	
	/**
	 * This method returns DICOM Value.
	 * @param iGroupNumber integer value of Group Number.
	 * @param iElementNumber integer value of element number
	 * @return IDicomValue - value of the Data Element.
	 */
	protected IDicomValue getDicomValue(int iGroupNumber, int iElementNumber)
	{
		IDataElement objIDataElement =  objIDataSet.getDataForTag(iGroupNumber,iElementNumber);
		if(objIDataElement == null)
			return null;
		IDicomValue objIDicomValue = objIDataElement.getDicomValue();
		if(objIDicomValue == null)
			return null;
		return objIDicomValue;
	}

	/**
	 * Returns the DictionaryReader instance.
	 * @return the dictionary reader
	 */
	public IDictionaryReader getDictionaryReader()
	{
		return this.objIDictionaryReader;
	}

	/**
	 * This method removes the data element from module.
	 * If the data element is part of module it is deleted, otherwise it is not deleted.
	 * The removal of data element from the data set is according to the boolean variable passed.
	 * If boolean variable isForeceFullyDelete is true the data element is forcefully deleted from the module
	 * irrespective of DICOM validation rules.
	 * If boolean  variable isForeceFullyDelete is false then removal is done based on DICOM Validation rules.
	 * In this case if the removal of the data element makes the module invalid then it is not deleted.
	 * @param strDataElementName Data Element Name that is to be removed from the module.
	 * @param isForceFullyDelete boolean variable which decides to delete the data element forcefully or not.
	 * if isForceFullyDelete is true then the data element is forcefully deleted.
	 * if isForceFullyDelete is false then the data element is deleted based on Validation Mode specified (LOW/HIGH/STRICT)
	 * @return boolean - returns true if the data element strDataElementName is deleted from the module
	 * otherwise false is returned.
	 */
	public boolean remove(String strDataElementName, boolean isForeceFullyDelete)
	{
		if(strDataElementName == null || strDataElementName.equals(""))
		{
			log("exceptions", "DataElement.DataElementNameNull", "");
			return false;
		}
		short[] arrShTag = objIDictionaryReader.getTag(strDataElementName);
		if(arrShTag == null)
		{
			log("exceptions", "DataElement.NotPresentInDataDictionary", strDataElementName);
			return false;
		}
		ITag objITag = new Tag(arrShTag[0], arrShTag[1]);

		if(!objIDataSet.isPresent(objITag.getGroupNumber(), objITag.getElementNumber()))
		{
			log("exceptions", "DataElement.NotPresentInDataSet", strDataElementName);
			return false;
		}
		IDataElement objIDataElement = objIDataSet.getDataForTag(objITag.getGroupNumber(), objITag.getElementNumber());
		ISingleCollection<ITag> objTagCollection = getVectorOfKeys();
		if(objTagCollection.contains(objITag))
		{
			try
			{
				objIDataSet.removeDataElement(objITag);
				this.validateModule();
				log("exceptions", "DataElement.DeletedSuccessfully", strDataElementName);
			}
			catch(InvalidModuleException ime)
			{
				if(isForeceFullyDelete)
				{
					log("exceptions", "DataElement.DeleteForcefully", strDataElementName);
					return true;
				}
				else
				{
					objIDataSet.addDataElement(objIDataElement);
					log("exceptions", "DataElement.CannotDelete", strDataElementName);
					return false;
				}
			}
		}
		else
		{
			log("exceptions", "DataElement.NotPresentInModule", strDataElementName);
			return false;
		}
		return true;
	}

	/**
	 * This method adds the data element in module.
	 * If the data element is part of module it is added, otherwise it is not added.
	 * @param strDataElementName Data Element Name that is to be added to the module.
	 * @param objValue - The value of the data element to be added in data set.
	 * @return boolean - true if the data element is added in module other wise false is returned.
	 */
	public boolean add(String strDataElementName, Object objValue)
	{
		if(strDataElementName == null || strDataElementName.equals(""))
		{
			log("exceptions", "DataElement.DataElementNameNull", "");
			return false;
		}
		short[] arrShTag = objIDictionaryReader.getTag(strDataElementName);
		if(arrShTag == null)
		{
			log("exceptions", "DataElement.NotPresentInDataDictionary", strDataElementName);
			return false;
		}
		ITag objITag = new Tag(arrShTag[0], arrShTag[1]);

		ISingleCollection<ITag> objTagCollection = getVectorOfKeys();
		if(objTagCollection.contains(objITag))
		{
			IDataElement objIDataElement = null;
			if(objITransferSyntax.getVRTransferSyntax()==DicomConstants.DCM_VR_IMPLICIT)
			{
				objIDataElement = new ImplicitDE(arrShTag[0], arrShTag[1], objValue, objITransferSyntax, objIDictionaryReader);
			}
			else
			{
				objIDataElement = new ExplicitDE(arrShTag[0], arrShTag[1], StandardMethods.getiVRtype(objIDictionaryReader.getVR(arrShTag[0], arrShTag[1])),objValue, objITransferSyntax, objIDictionaryReader);
			}
			objIDataSet.addDataElement(objIDataElement);
		}
		else
		{
			log("exceptions", "DataElement.NotPresentInModule", strDataElementName);
			return false;
		}
		return true;
	}

	/*
	 * ModuleIterator internally uses SingleCollection iterator to iterate over the vector of keys.
	 * Then for these keys, it searches the DataElement in the DataSet.
	 */
	/**
	 * This inner class works as an Iterator which helps iterating over the map contained in the a Module
	 * This iterator can be used for the purpose of getting all the elements of the DataSet one by one.
	 */
	@SuppressWarnings("serial")
	//This iterator will get the Vector Of keys from the Module and then return only those elements 
	//from the DataSet whose tags match with those contained in the vector.
	//This way each module will iterate and give only the data elements belonging to it.
	class ModuleIterator implements ICollectionIterator<IDataElement>
	{

		ITag objITag = null;

		ISingleCollection<ITag> objSingleCollection = null;

		ICollectionIterator<ITag> objCollectionIterator = null;

		/**
		 * Instantiates a new module iterator.
		 */
		public ModuleIterator()
		{
			objSingleCollection = getVectorOfKeys();
			objCollectionIterator = objSingleCollection.iterator();
		}

		/* 
		 * @see cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator#hasNext()
		 */
		public boolean hasNext()
		{
			while(objCollectionIterator.hasNext())
			{
				objITag = objCollectionIterator.next();
				if(objIDataSet.isPresent(objITag.getGroupNumber(), objITag.getElementNumber()))
				{
					return true;
				}
			}
			return false;
		}

		/* 
		 * @see cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator#next()
		 */
		public IDataElement next()
		{
			IDataElement objIDataElement = objIDataSet.getDataForTag(objITag.getGroupNumber(), objITag.getElementNumber());
			if(objIDataElement != null)
				return objIDataElement;
			else
			{
				//Here exception needs to be thrown
				return null;
			}
		}

		/* 
		 * @see cdac.medinfo.sdk.dcm30_04.utils.ICollectionIterator#remove()
		 */
		public void remove()
		{}
	}

	/**
	 * This method returns the iterator over the Module.
	 * 
	 * @return the i collection iterator< i data element>
	 */
	public ICollectionIterator<IDataElement> iterator()
	{
		return new ModuleIterator();
	}

	/*
	 * Base Class Module will now contain implementation for this method which will be filled
	 * by the static blocks in Sub-class Modules.
	 */
	/**
	 * Gets the vector of keys.
	 * 
	 * @return the vector of keys
	 */
	public  ISingleCollection<ITag> getVectorOfKeys()
	{
		return null;
	}

	/**
	 * Checks if the DataElement for the given GroupNumber,ElementNumber pair exists
	 * in the Module.
	 * @param iGroupNumber - Group Number of the DataElement.
	 * @param iElementNumber - Element Number of the DataElement.
	 * @return - true,if DataElement is present.
	 * false, if DataElement is absent.
	 */
	public boolean isPresent(int iGroupNumber, int iElementNumber)
	{
		ITag objITag = new Tag(iGroupNumber, iElementNumber);
		return objIDataSet.isPresent(objITag.getGroupNumber(), objITag.getElementNumber());
	}

	/**
	 * Checks if the DataElement is present in the Module.
	 * @param strDataElementName - Name of the Data Element.
	 * @return - true,if DataElement is present.
	 * false, if DataElement is absent.
	 */
	public boolean isPresent(String strDataElementName)
	{
		if(strDataElementName == null || strDataElementName.equals(""))
		{
			log("exceptions", "DataElement.DataElementNameNull", "");
			return false;
		}
		short[] arrShTag = objIDictionaryReader.getTag(strDataElementName);
		if(arrShTag == null)
		{
			log("exceptions", "DataElement.NotPresentInDataDictionary", strDataElementName);
			return false;
		}
		ITag objITag = new Tag(arrShTag[0], arrShTag[1]);
		return objIDataSet.isPresent(objITag.getGroupNumber(), objITag.getElementNumber());
	}

	/**
	 * Retrieves the TransferSyntax.
	 * 
	 * @return ITransferSyntax- instance of TransferSyntax.
	 */
	public ITransferSyntax getTransferSyntax()
	{
		return objITransferSyntax;
	}


	/**
	 * 	This method dumps all Attributes of the Module in readable form to the specified Stream.
	 * 	@param objIDicomStream : the Stream on which the Data is to be written.
	 */
	public void dumpDICOMInfo(IDicomStream objIDicomStream) throws IOException
	{
		for(ITag tag : getVectorOfKeys())
		{
			int iGroupNumber = tag.getGroupNumber();
			int iElementNumber = tag.getElementNumber();
			// Checks whether the attribute is present in the Module or not with the help of Group Number and Element Number.
			if(isPresent(iGroupNumber,iElementNumber))
			{
				// Writes actual Attribute Name present in the Module.
				objIDicomStream.writeString("\t\t" + objIDictionaryReader.getName(iGroupNumber,iElementNumber) + " -> ");

				// Gets the Data Element from Data Set.
				IDataElement objIDataElement = objIDataSet.getDataForTag(iGroupNumber,iElementNumber);
				IDicomValue objIDicomValue = objIDataElement.getDicomValue();
				//Check whether DICOM Data is present in Data Element or not.
				if(objIDicomValue != null)
				{
					int iVRType = objIDicomValue.getIVRType();

					if(iVRType == DicomConstants.DCM_DT_SQ)
					{
						// If VR Type of DICOM Data is Sequence...
						objIDicomStream.writeString(" Hidden");
					}
					else
					{
						//	For VR Type other than Sequence...
						objIDicomValue.dumpDICOMInfo(objIDicomStream);
					}
					objIDicomStream.writeString("\n");
				}
			}
		}
	} 

	private void log(String strResourceName, String strKey, String strAddInfo)
	{
		DicomConfig objDicomConfig = DicomConfig.createInstance();
		DicomLogger objDicomLogger = DicomLogger.getLogger();
		if(objDicomConfig.isLoggingOn())
		{
			objDicomLogger.log(ResourceBundleWrapper.getValue(strResourceName,strKey,new Locale(DicomConfig.createInstance().getLocale())) + strAddInfo);
		}
	}
	
	/**
	 * Add Implicit or Explicit Data Element depending Transfer Syntax. 
	 * @param iGroupNumber GroupNumber from the Tag identifying this DataElement.
	 * @param iElementNumber ElementNumber from the Tag identifying this DataElement.
	 * @param iVRType Value Representation Type specified in @see DicomConstants.
	 * @param objValue value of the DataElement.
	 */
	protected void addDataElement(int iGroupNumber,int iElementNumber, int iVRType,Object objValue)
	{
		IDataElement objIDataElement = null;

		if(objITransferSyntax.getVRTransferSyntax() == DicomConstants.DCM_VR_IMPLICIT)
			objIDataElement = new ImplicitDE(iGroupNumber, iElementNumber, objValue, objITransferSyntax, objIDictionaryReader);
		else
			objIDataElement = new ExplicitDE(iGroupNumber, iElementNumber, iVRType, objValue,  objITransferSyntax, objIDictionaryReader);
		objIDataSet.addDataElement(objIDataElement);
	}
}
