/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.dcm30_04.services.dimsecommons;

import java.io.IOException;

import cdac.medinfo.sdk.dcm30_04.baselibs.DicomV3Factory;
import cdac.medinfo.sdk.dcm30_04.baselibs.Tag;
import cdac.medinfo.sdk.dcm30_04.baselibs.TransferSyntax;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomValue;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDictionaryReader;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITag;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITransferSyntax;
import cdac.medinfo.sdk.dcm30_04.dictionaryreaderwriters.DictionaryReader;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDataTypeException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.ICommandElement;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.InvalidCommandElementException;
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.StandardMethods;

/**
 * This class represents Command Element of Dicom which reads the datatype of
 * its value from the DataDictionary provided to it.
 */
public class CommandElement implements ICommandElement 
{

	private ITag objITag;

	private int iVM;

	private IDicomValue objIDicomValue;

	private int iValueLength,iCommandElementLength;

	private IDictionaryReader objIDictionaryReader;

	private ITransferSyntax objITransferSyntax;

	/**
	 * Instantiates a new command element.
	 */
	CommandElement()
	{
		this(null);
	}

	/**
	 * Creates empty Command Element with specified dictionary reader. If
	 * Dictionary Reader is empty then default dictionary reader is created.
	 * @param objIDictReader instance of Dictionary Reader
	 */
	CommandElement(IDictionaryReader objIDictReader)
	{
		iVM = 0;
		iValueLength = 0;

		objITransferSyntax = new TransferSyntax();
		//If Dictionary Reader is not set then the default dictionary will be used.
		if(objIDictionaryReader == null)
		{
			objIDictionaryReader = DictionaryReader.createInstance();
			if(!objIDictionaryReader.isDictionaryPopulated())
				objIDictionaryReader.populate();
		}
		else
			objIDictionaryReader = objIDictReader;
	}

	/**
	 * This constructor can be used to create a Command Element.
	 * @param iGroupNumber  GroupNumber from the Tag identifying this Command Element
	 * @param iElementNumber  ElementNumber from the Tag identifying this Command Element
	 * @param objValue value of the Command Element
	 * @throws InvalidCommandElementException If Command Element is invalid
	 */
	public CommandElement(int iGroupNumber,int iElementNumber,Object objValue) throws InvalidCommandElementException
	{
		try
		{
			iVM = 0;
			iValueLength = 0;
			objITransferSyntax = new TransferSyntax();
			objIDictionaryReader = DictionaryReader.createInstance();
			if(!objIDictionaryReader.isDictionaryPopulated())
				objIDictionaryReader.populate();

			this.objITag = new Tag(iGroupNumber,iElementNumber);
			int iVRType = 0;
			String strVRType = objIDictionaryReader.getVR(iGroupNumber,iElementNumber);

			if(strVRType!= null)
			{
				if(strVRType.equalsIgnoreCase("US/SS") || strVRType.equalsIgnoreCase("SS/US"))
				{
					strVRType = "US";
				}
				iVRType = StandardMethods.getiVRtype(strVRType);
			}

			DicomV3Factory objDicomV3Factory = DicomV3Factory.createInstance();

			objIDicomValue = objDicomV3Factory.createDataType(iVRType);
			if(objIDicomValue != null)
			{

				objIDicomValue.setValue(objValue);
				int iActualValueLength = objIDicomValue.getValueLength();
				int iWithPaddingLength = 0;
				if(iActualValueLength == DicomConstants.DCM_UNDEFINED_LENGTH)
					iWithPaddingLength = iActualValueLength;
				else if(iActualValueLength % 2 != 0)
					iWithPaddingLength = iActualValueLength + 1;
				else
					iWithPaddingLength = iActualValueLength;
				setLength(iWithPaddingLength);
			}
			setCommandElementLength();
		}
		catch(InvalidDataTypeException ide)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(ide.getMessage());

//			DicomLogger objDicomLogger = DicomLogger.getLogger();
//			objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","InvalidCommandElement",new Locale(DicomConfig.createInstance().getLocale()))+ iGroupNumber + "," + iElementNumber);
			String strMessage = Integer.toHexString(objITag.getGroupNumber()) + "," + Integer.toHexString(objITag.getElementNumber());
			{
				throw new InvalidCommandElementException(strMessage, ide);
			}
		}
	}

	/**
	 * Retrieves the length part of the Command Element.
	 * @return int - Length part of Data Element.
	 */
	public int getLength()
	{
		return iValueLength;
	}

	/**
	 * Sets the length part of the Command Element.
	 * @param iValueLength Length part of Data Element.
	 */
	public void setLength(int iValueLength)
	{
		/*
		 * The value length of the Command Element should always be even.DicomValue would held 
		 * the actual length of the value.
		 */
		int iDicomValueLength = iValueLength;
		if((iValueLength >0) && ((iDicomValueLength % 2) != 0))
			this.iValueLength = iValueLength + 1;
		else
			this.iValueLength = iValueLength;
	}

	/**
	 * Retrieves value length of Command element.
	 * @return int - value length of Command element
	 */
	public int getValueLength()
	{
		return this.iValueLength;
	}

	/**
	 * Retrieves the value multiplicity of the value.
	 * @return integer - the value multiplicity of the value in CommandElement
	 *         
	 */
	public int getValueMultiplicity()
	{
		return iVM;
	}

	/**
	 * Sets the value multiplicity of the value in the CommandElement.
	 * @param iVM value multiplicity of the value
	 */
	public void setValueMultiplicity(int iVM)
	{
		this.iVM = iVM;
	}

	/**
	 * Sets the Tag in Command Element.
	 * @param iGroupNumber Value of Group Number.
	 * @param iElementNumber Value of Element Number.
	 */
	public void setTag(int iGroupNumber,int iElementNumber)
	{
		objITag = new Tag(iGroupNumber,iElementNumber);
	}

	/**
	 * Retrieves the Tag part of the Command Element.
	 * @return ITag - Tag containing Group Number and Element Number.
	 */
	public ITag getTag()
	{
		return objITag;
	}

	/**
	 * Retrieves the value part of the Command Element.
	 * @return IDicomValue - instance containing the value of the Command Element
	 */
	public IDicomValue getDicomValue()
	{
		return objIDicomValue;
	}

	/**
	 * Sets the DicomValue as a value in the CommandElement.
	 * @param objDicomValue instance of DicomValue representing the value of CommandElement
	 */
	public void setDicomValue(IDicomValue objDicomValue)
	{
		int iValMultiplicity = objDicomValue.getValueMultiplicity();
		setValueMultiplicity(iValMultiplicity);
		objIDicomValue = objDicomValue;
	}

	/**
	 * Sets the Dictionary Reader. 
	 * Dictionary Reader is used to verify the Value Representation of a Command Element while creating a Data Element.
	 * @param objIDictionaryReader instance of DictionaryReader to read from the Dictionary.
	 */
	public void setDictionaryReader(IDictionaryReader objIDictionaryReader)
	{
		if(!objIDictionaryReader.isDictionaryPopulated())
			objIDictionaryReader.populate();
		this.objIDictionaryReader = objIDictionaryReader;
	}

	/**
	 * Retrieves the dictionaryreader set in the CommandElement.
	 * @return objIDictionaryReader - instance of dictionary reader set in the Implicit DataElement
	 */
	public IDictionaryReader getDictionaryReader()
	{
		return objIDictionaryReader;
	}

	/**
	 * Reads VR from dictionary.
	 * @param iGroupNumber Value of Group Number.
	 * @param iElementNumber Value of Element Number.
	 * @return String - value of VR
	 */
	private String readVRFromDictionary(int iGroupNumber,int iElementNumber)
	{
		return objIDictionaryReader.getVR(iGroupNumber,iElementNumber);
	}

	/**
	 * Reads the Command Element from the given Stream.
	 * @param objIDicomStream instance of Dicom Stream
	 * @return int - shows status as success or failure.
	 * @throws IOException If Input/Output operation fails.
	 * @throws InvalidCommandElementException If Data Element fails to satisfy Validation Criteria.
	 */
	public int parse(IDicomStream objIDicomStream) throws InvalidCommandElementException, IOException
	{
		try
		{
			int iGroupNumber = 0;
			int iElementNumber = 0;
			int iVRType=0;
			//Read GroupNumber and ElementNumber 
			iGroupNumber=objIDicomStream.readShort();
			iElementNumber=objIDicomStream.readShort();

			//Set the GroupNumber and ElementNumber to the Command Element
			setTag(iGroupNumber, iElementNumber);

			//Read the Value Representation Type from the Dictionary
			String strTempVR = readVRFromDictionary(iGroupNumber, iElementNumber);

			if(strTempVR != null)
				iVRType = StandardMethods.getiVRtype(strTempVR);
			else iVRType = DicomConstants.DCM_INVALID_VR;

			//Read 2 bytes for value length and set it to Command Element
			/*
			 * This method will set the length read from the stream in the Command Element.
			 * But it should not be setting it in the IDicomValue. since lengths may be different.
			 * For detailed reason, see description in IDicomValue interface
			 */
			int iLen = objIDicomStream.readInt();
			setLength(iLen);
			setCommandElementLength();

			DicomV3Factory objDicomV3Factory=DicomV3Factory.createInstance();

			//Get Datatype Object from Factory and set it to Command Element
			if(iVRType != DicomConstants.DCM_INVALID_VR)
				objIDicomValue = objDicomV3Factory.createDataType(iVRType);

			//Read the DataValue and Get the Value Multiplicity from the DataValue
			objIDicomValue.read(objIDicomStream, iValueLength);

			setDicomValue(objIDicomValue);	
		}
		catch(InvalidDataTypeException ide)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(ide.getMessage());

//			DicomLogger objDicomLogger = DicomLogger.getLogger();
//			objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","InvalidCommandElement",new Locale(DicomConfig.createInstance().getLocale()))+ iGroupNumber + "," + iElementNumber);
			String strMessage = Integer.toHexString(objITag.getGroupNumber()) + "," + Integer.toHexString(objITag.getElementNumber());
			{
				throw new InvalidCommandElementException(strMessage, ide);
			}
		}
		return 1;
	}

	/**
	 * Writes the Command Element to the given Stream.
	 * @param objIDicomStream instance of Dicom Stream 
	 * @return int - shows status as success or failure.
	 * @throws InvalidDataTypeException  If DataType validation fails.
	 * @throws IOException If Input/Output operation fails.
	 */
	public  int serialize(IDicomStream objIDicomStream) throws InvalidDataTypeException, IOException
	{
		IDicomValue objIDicomValue=null;
		ITag objITag=null;
		short shGroupNumber=0,shElementNumber=0;

		//Get the GroupNumber and the Element Number
		objITag=this.getTag();
		shGroupNumber=(short)objITag.getGroupNumber();
		shElementNumber=(short)objITag.getElementNumber();

		//Write the GroupNumber and the Element Number to the Stream
		objIDicomStream.writeShort(shGroupNumber);
		objIDicomStream.writeShort(shElementNumber);

		//If the odd length value is present, make it even by incrementing it
		//Value will be padded with a NULL character and written to stream
		//This will be taken care by the write method of datatype class

		/*
		 *The value length should be modified only if it is not UNDEFINED LENGTH
		 */
		if((iValueLength % 2) != 0)
			objIDicomStream.writeInt(iValueLength + 1);
		else
			//Write the Length to Stream
			objIDicomStream.writeInt(iValueLength);

		//Get the Value
		objIDicomValue=this.getDicomValue();

		//Write the Value
		//check if DicomValue object is not NULL then only call write on it.
		if(objIDicomValue != null)
			return objIDicomValue.write(objIDicomStream);
		return 0;
	}

	/**
	 * Sets the length of Command Element.
	 */
	public  void setCommandElementLength()
	{
		iCommandElementLength=4+4+iValueLength;
	}

	/**
	 * Retrieves the length of Command Element
	 * @return int - value of length.
	 */
	public  int getCommandElementLength()
	{
		return iCommandElementLength;
	}

	/**
	 * Retrieves the TransferSyntax set in the CommandElement object.
	 * @return ITransferSyntax - instance of TransferSyntax set in the CommandElement object.
	 */
	public ITransferSyntax getTransferSyntax()
	{
		objITransferSyntax = new TransferSyntax(DicomConstants.DCM_VR_IMPLICIT,DicomConstants.DCM_ENDIANNESS_LITTLE_ENDIAN);
		return objITransferSyntax;
	}
}