/*******************************************************************************
 *  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 cdac.medinfo.sdk.dcm30_04.baselibs.DicomV3Factory;
import cdac.medinfo.sdk.dcm30_04.baselibs.ExplicitDE;
import cdac.medinfo.sdk.dcm30_04.baselibs.ImageElement;
import cdac.medinfo.sdk.dcm30_04.baselibs.ImplicitDE;
import cdac.medinfo.sdk.dcm30_04.baselibs.OB;
import cdac.medinfo.sdk.dcm30_04.baselibs.OBImageObject;
import cdac.medinfo.sdk.dcm30_04.baselibs.OW;
import cdac.medinfo.sdk.dcm30_04.baselibs.OWImageObject;
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.ITag;
import cdac.medinfo.sdk.dcm30_04.enums.EnumBitsAllocated;
import cdac.medinfo.sdk.dcm30_04.enums.EnumBitsStored;
import cdac.medinfo.sdk.dcm30_04.enums.EnumHighBit;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPhotometricInterpretation;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPixelDataVRType;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPixelRepresentation;
import cdac.medinfo.sdk.dcm30_04.enums.EnumSamplesPerPixel;

import cdac.medinfo.sdk.dcm30_04.exceptions.ConditionFailedException;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidModuleException;
import cdac.medinfo.sdk.dcm30_04.exceptions.MandatoryMissingException;
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.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;

//TODO: Auto-generated Javadoc
/**
 \* This module describes a printable 8 or 12 bit grayscale image.
 * 
 * 
 */
public class HardcopyGrayscaleImageModule extends Module  
{


	/**
	 * The obj single collection.
	 */
	public static ISingleCollection<ITag> objSingleCollection = null;


	/**
	 * Instantiates a new hardcopy grayscale image module.
	 * 
	 * @param objIDataSet the obj i data set
	 */
	public HardcopyGrayscaleImageModule(IDataSet objIDataSet)
	{
		super(objIDataSet);
	}

	static
	{
		objSingleCollection = new SingleCollection<ITag>();
		objSingleCollection.add(new Tag(0x0028,0x0002));
		objSingleCollection.add(new Tag(0x0028,0x0004));
		objSingleCollection.add(new Tag(0x0028,0x0010));
		objSingleCollection.add(new Tag(0x0028,0x0011));
		objSingleCollection.add(new Tag(0x0028,0x0034));
		objSingleCollection.add(new Tag(0x0028,0x0100));
		objSingleCollection.add(new Tag(0x0028,0x0101));
		objSingleCollection.add(new Tag(0x0028,0x0102));
		objSingleCollection.add(new Tag(0x0028,0x0103));
		objSingleCollection.add(new Tag(0x7FE0,0x0010));
	}

	/**
	 * This method validates each Attribute in the Module according to the Type
	 * of the Attribute.Attributes are of 5 types.
	 * Type 1,Type 2 are Mandatory:
	 * Mandatory Attributes have to present in the Module.
	 * Type 1C,2C are Conditional:
	 * Conditional Attributes' presence depend on a condition.
	 * If the condition is satisfied the Attribute has to be present.
	 * Type 3 are Optional
	 * Optional Attributes can be added as and when required.
	 * 
	 * @return true, if validate module
	 * 
	 * @throws InvalidModuleException the invalid module exception
	 */

	public  boolean validateModule() throws InvalidModuleException
	{

		try
		{			/*
		 * Validation Rules for Mandatory Elements
		 */
			if(!isPresent(0x0028,0x0002))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0002");
			else if(!isPresent(0x0028,0x0004))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0004");
			else if(!isPresent(0x0028,0x0010))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0010");
			else if(!isPresent(0x0028,0x0011))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0011");
			else if(!isPresent(0x0028,0x0100))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0100");
			else if(!isPresent(0x0028,0x0101))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0101");
			else if(!isPresent(0x0028,0x0102))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0102");
			else if(!isPresent(0x0028,0x0103))
				throw new MandatoryMissingException("Missing Mandatory Element 0028,0103");
			else if(!isPresent(0x7FE0,0x0010))
				throw new MandatoryMissingException("Missing Mandatory Element 7FE0,0010");
			else
			{
				/*
				 * Log:(0028,0034) must be present since the Aspect Ratio
				 * is not equal to 1.
				 */
				String[] strPixelAspectRatio=getPixelAspectRatio();

				if(strPixelAspectRatio != null)	
				{
					int iVerticalSize=Integer.parseInt(strPixelAspectRatio[0]);
					int iHorizontalSize=Integer.parseInt(strPixelAspectRatio[1]);
					if((iVerticalSize/iHorizontalSize)==1)
					{
						throw new ConditionFailedException("Condition failed for element 0028,0034 ");
					}
				}					
			}
		}
		catch(ConditionFailedException cfe)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(cfe.getMessage());

			
				throw new InvalidModuleException(this.getClass().getSimpleName(),cfe);

		}
		catch(MandatoryMissingException mme)
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(mme.getMessage());

			
				throw new InvalidModuleException(this.getClass().getSimpleName(),mme);

		}
		return true;
	}

	/**
	 * Sets Samples per Pixel (0028,0002) that is the number of separate planes in the image. One, three, and
	 * four image planes are defined. For monochrome (gray scale) and palette color images, the number of planes is 1. For RGB and
	 * other three vector color models, the value of this attribute is 3. For four vector color models, the
	 * value of this attribute is 4.
	 * Enumerated Value:
	 * 3
	 * 
	 * @param enumSamplesPerPixel the enum samples per pixel
	 */
	public void setSamplesPerPixel(EnumSamplesPerPixel enumSamplesPerPixel)
	{
		/*Accept Number of samples (planes) in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0002, DicomConstants.DCM_DT_US, enumSamplesPerPixel.getValue());
	}

	/**
	 * Sets the intended interpretation of the pixel data.
	 * The function accepts a String Value and creates the
	 * DE based on the transfer syntax and populates the DE with right values.
	 * This De is then added to the Module HashMap
	 * Enumerated Values:
	 * MONOCHROME1
	 * MONOCHROME2
	 * 
	 * @param enumPhotometricInterpretation the enum photometric interpretation
	 */
	public void setPhotometricInterpretation(EnumPhotometricInterpretation enumPhotometricInterpretation)
	{
		/*Accept Specifies the intended interpretation of the pixel data in String format of type ST.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0004, DicomConstants.DCM_DT_CS, enumPhotometricInterpretation.getValue());
	}

	/**
	 * Sets number of rows in the image.This describes the images's height.
	 * 
	 * @param sRows the s rows
	 */
	public void setRows(short sRows)
	{
		/*Accept Number of Rows in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0010, DicomConstants.DCM_DT_US, sRows);
	}

	/**
	 * Sets number of columns in the image. This describes the images's width.
	 * 
	 * @param sColumns the s columns
	 */
	public void setColumns(short sColumns)
	{
		/*Accept Number of Columns in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0011, DicomConstants.DCM_DT_US, sColumns);
	}

	/**
	 * Sets	number of bits allocated for each pixel sample. Each sample shall have the same
	 * number of bits allocated.
	 * 
	 * @param enumBitsAllocated - specifies the number of bits allocated
	 * Enumerated Values:
	 * 8 (if Bits Stored = 8)
	 * 16 (if Bits Stored =12)
	 */
	public void setBitsAllocated(EnumBitsAllocated enumBitsAllocated)
	{
		/*Accept bits allocated for each pixel in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0100, DicomConstants.DCM_DT_US, enumBitsAllocated.getValue());
	}

	/**
	 * Sets number of bits stored for each pixel sample. Each sample shall have the same
	 * number of bits stored.
	 * 
	 * @param enumBitsStored the enum bits stored
	 */
	public void setBitsStored(EnumBitsStored enumBitsStored)
	{
		/*Accept bits stored for each pixel in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0101, DicomConstants.DCM_DT_US, enumBitsStored.getValue());
	}

	/**
	 * Sets most significant bit for pixel sample data. Each sample shall have the same high bit.
	 * 
	 * @param enumHighBit the enum high bit
	 */
	public void setHighBit(EnumHighBit enumHighBit)
	{
		/*Accept Most significant bit for pixel in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0102, DicomConstants.DCM_DT_US, enumHighBit.getValue());
	}

	/**
	 * Sets Data representation of the pixel samples. Each sample shall have the same pixel
	 * representation. Enumerated Values: 0000H = unsigned integer. 0001H = 2's complement
	 * 
	 * @param enumPixelRepresentation the enum pixel representation
	 */
	public void setPixelRepresentation(EnumPixelRepresentation enumPixelRepresentation)
	{
		/*Accept Data representation of pixel in this image in short format of type US.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0103, DicomConstants.DCM_DT_US, enumPixelRepresentation.getValue());
	}

	/**
	 * Sets a data stream of the pixel samples that comprise the Image.
	 * @param objCollectionOfPixelData the obj collection of pixel data
	 * @param enumPixelDataVRType VR type pf the pixel data . It can be OW/OB.
	 * @param isUndefined true then the Data Element created is of undefined length.
	 * false then the Data Element created is of defined length.
	 */ 
	public void setPixelData(ISingleCollection<byte[]> objCollectionOfPixelData, EnumPixelDataVRType enumPixelDataVRType, boolean isUndefined)
	{
		/*Accept Pixel Data in this image in short format of type OB/OW.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		int	iVRTransferSyntax = objITransferSyntax.getVRTransferSyntax();
		IDataElement objIDataElement=null;
		if(enumPixelDataVRType.getValue() == DicomConstants.DCM_DT_OB)
		{
			OB objOb = (OB)DicomV3Factory.createInstance().createDataType(DicomConstants.DCM_DT_OB);
			if(isUndefined == true)
				objOb.changeToUndefined();
			objOb.setValue(objCollectionOfPixelData);
			if(iVRTransferSyntax==DicomConstants.DCM_VR_IMPLICIT)
				objIDataElement = new ImplicitDE(0x7FE0, 0x0010,objOb,objITransferSyntax,objIDictionaryReader);
			else
				objIDataElement = new ExplicitDE(0x7FE0, 0x0010, DicomConstants.DCM_DT_OB,objOb,objITransferSyntax,objIDictionaryReader);
		}
		else
		{
			OW objOW = (OW)DicomV3Factory.createInstance().createDataType(DicomConstants.DCM_DT_OW);
			if(isUndefined == true)
				objOW.changeToUndefined();
			objOW.setValue(objCollectionOfPixelData);
			if(iVRTransferSyntax==DicomConstants.DCM_VR_IMPLICIT)
				objIDataElement = new ImplicitDE(0x7FE0, 0x0010,objOW,objITransferSyntax,objIDictionaryReader);
			else
				objIDataElement = new ExplicitDE(0x7FE0, 0x0010, DicomConstants.DCM_DT_OW,objOW,objITransferSyntax,objIDictionaryReader);
		}
		if(objIDataElement != null)
			objIDataSet.addDataElement(objIDataElement);
	}

	/**
	 * Sets a data stream of the pixel samples that comprise the Image.
	 * @param objIDicomStream the Dicom Stream Containing pixel data
	 * @param iFrameSizeArr int array containing size of each frame.
	 * @param enumPixelDataVRType VR type pf the pixel data . It can be OW/OB.
	 * @param isUndefined true then the Data Element created is of undefined length.
	 * false then the Data Element created is of defined length.
	 */ 
	public void setPixelData(IDicomStream objIDicomStream, int[] iFrameSizeArr, EnumPixelDataVRType enumPixelDataVRType, boolean isUndefined) throws IOException
	{
		/*Accept Pixel Data in this image in short format of type OB/OW.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		IDataElement objIDataElement=null;
		if(enumPixelDataVRType.getValue() == DicomConstants.DCM_DT_OB)
		{
			OBImageObject objObImageObject = new OBImageObject();
			if(isUndefined == true)
				objObImageObject.changeToUndefined();
			objObImageObject.addFrames(objIDicomStream, iFrameSizeArr);
			objIDataElement = new ImageElement(objIDataSet.getTransferSyntax(), objIDictionaryReader,objObImageObject);
		}
		else
		{
			OWImageObject objOWImageObject = new OWImageObject();
			if(isUndefined == true)
				objOWImageObject.changeToUndefined();
			objOWImageObject.addFrames(objIDicomStream, iFrameSizeArr);
			objIDataElement = new ImageElement(objIDataSet.getTransferSyntax(), objIDictionaryReader,objOWImageObject);
		}
		if(objIDataElement != null)
			objIDataSet.addDataElement(objIDataElement);
	}

	/**
	 * Sets Ratio of the vertical size and horizontal size of the pixels in the image specified by
	 * a pair of integer values where the first value is the vertical pixel size.
	 * 
	 * @param strPixelAspectRatio the  pixel aspect ratio
	 */
	public void setPixelAspectRatio(String[] strPixelAspectRatio)
	{
		/*Accept Pixel Aspect Ratio in this image in String format of type IS.
		 * Read the Transfer Syntax and check if ImplicitDE or ExplicitDE to create.
		 * Set the iGroupNumber and iElementNumber to ImplicitDE object 
		 * Set the iVRType
		 * Set length from the element
		 * Add this element to the Data Set
		 */
		addDataElement(0x0028, 0x0034, DicomConstants.DCM_DT_IS, strPixelAspectRatio);
	}

	/**
	 * Retrieves the samples per pixel.
	 * 
	 * @return short - returns the samples per pixel
	 */
	public short getSamplesPerPixel()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0002);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the photometric interpretation.
	 * 
	 * @return String - returns the photometric interpretation
	 */
	public String getPhotometricInterpretation()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0004);
		if(objValue != null)
			return objValue.toString();
		return null;
	}

	/**
	 * Retrieves the number of rows.
	 * 
	 * @return short - returns the number of rows
	 */
	public short getRows()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0010);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the number of columns.
	 * 
	 * @return short - returns the number of columns
	 */
	public short getColumns()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0011);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the number of bits allocated.
	 * 
	 * @return short - returns the number of bits allocated
	 */
	public short getBitsAllocated()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0100);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the number of bits stored.
	 * 
	 * @return short - returns the number of bits stored
	 */
	public short getBitsStored()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0101);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the high bit.
	 * 
	 * @return short - returns the high bit
	 */
	public short getHighBit()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0102);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the pixel representation.
	 * 
	 * @return short - returns the pixel representation
	 */
	public short getPixelRepresentation()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0103);
		if(objValue != null)
			return Short.parseShort(objValue.toString());
		return DicomConstants.DCM_NO_VALUE;
	}

	/**
	 * Retrieves the collection of pixel data.
	 * 
	 * @return collection of byte[] - returns the collection pixel data
	 */
	@SuppressWarnings("unchecked")
	public ISingleCollection<byte[]> getPixelData()
	{
		Object objValue = objIDataSet.getValue(0x7fe0, 0x0010);
		if(objValue != null)
			return ((ISingleCollection<byte[]>)objValue);
		return null;
	}

	/**
	 * Retrieves the pixel aspect ratio.
	 * 
	 * @return string - returns the pixel aspect ratio
	 */
	public String[] getPixelAspectRatio()
	{
		Object objValue = objIDataSet.getValue(0x0028, 0x0034);
		if(objValue != null)
			return (String[])(objValue);
		return null;
	}

	/**
	 * This method returns the custom collection containing the list of DataElements belonging to the Hardcopy Grayscale
	 * Image Module specified by DICOM Standard.
	 * 
	 * @return collection - custom collection containing the list of DataElements belonging to the Hardcopy Grayscale Image Module
	 */
	public ISingleCollection<ITag> getVectorOfKeys()
	{
		return objSingleCollection;
	}
}
