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

import java.io.FileInputStream;
import java.io.IOException;

import cdac.medinfo.sdk.dcm30_04.baselibs.DicomFileHeader;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomFileHeader;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomFileReader;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomFileStream;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidHeaderException;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;

/**
 * This class will be used as a file reader capable of reading part-10 complaint dicom file
 */

public class DicomFileReader implements IDicomFileReader 
{

	private IDicomStream objIDicomStream;
	
	private String strFilePath;

	private boolean isStrFilePathInUse = false;
	
	/*
	 * Added instance member DicomFileHeader so that it can be retrieved by user from the DicomFileReader
	 */
	IDicomFileHeader instanceDicomFileHeader = null;
	
	boolean isHeaderPopulated = false;

	private static final int DCM_FILE_PREAMBLE_LENGTH = 128;
	private static final int DCM_FILE_HEADER_MARK = 4;

	/**
	 * This constructor creates a DicomFileReader by accepting file name with  complete path
	 * @param strFileNameWithPath : The file with complete path.
	 * @throws IOException Signals that an I/O exception of some sort has occurred.
	 */
	public DicomFileReader(String strFileNameWithPath) throws IOException
	{
		this.strFilePath = strFileNameWithPath;
		isStrFilePathInUse = true;
	}

	/**
	 * This constructor accepts DicomStream in which file is already set .
	 * @param objIDicomStream_ : The DicomStream in which file is already set.
	 */
	public DicomFileReader(IDicomStream objIDicomStream_)
	{
		objIDicomStream = objIDicomStream_;	
	}

	/**
	 * This method checks whether the preamble is present, and if present reads the preamble 
	 * from the stream .
	 * @param preamble Preamble in byte array.
	 * @return int - returns 1 on success.
	 * @throws IOException Signals that an I/O exception of some sort has occurred.
	 */
	public int getPreamble(byte[] preamble) throws IOException
	{
		if(DicomConstants.DCM_FILE_HEADER_PRESENT == isHeaderPresent())
		{
			init();
			//It marks the stream for reading preamble and then resets it after reading the preamble
			objIDicomStream.mark(128);
			objIDicomStream.readBuffer(preamble);
			objIDicomStream.reset();
			if(isStrFilePathInUse == true)
                close();
			return 1;
		}
		return 0;
	}

	/**
	 * This method returns the DicomFileHeader from the DicomFileReader
	 * When called after parsing the Dicom File having DicomFileHeader, the
	 * user can get the populated DicomFileHeader class object from the reader.
	 * @return IDicomFileHeader instance of DicomFileHeader on presence of DicomFileHeader otherwise null.
	 * @throws InvalidHeaderException Signals that Dicom File Header is Corrupted.
	 * @throws IOException Signals that an I/O exception of some sort has occurred. 
	 */
	public IDicomFileHeader readHeader() throws InvalidHeaderException,IOException
	{
		isHeaderPopulated = true;

		int iHeaderLength = DicomConstants.DCM_FILE_HEADER_ABSENT;
		if(isHeaderPresent() == DicomConstants.DCM_FILE_HEADER_PRESENT)
		{
			init();

			//Here we need to think about should we use hard coded values or
			//some constants defined at a common place 
			objIDicomStream.mark(142);
			objIDicomStream.skip(132);
			iHeaderLength = calculateHeaderLength();
			objIDicomStream.reset();

			byte[] header = null;

			objIDicomStream.mark(132+iHeaderLength);
			objIDicomStream.skip(132);
			header = new byte[iHeaderLength];
			objIDicomStream.readBuffer(header);
			objIDicomStream.reset();

			/*
			 * Added code for populating the instance member DicomFileHeader
			 * from the retrieved header from the Dicom File
			 */
			if(instanceDicomFileHeader == null)
				instanceDicomFileHeader = new DicomFileHeader();
			instanceDicomFileHeader.populate(header);
			if(isStrFilePathInUse == true)
                close();
			return instanceDicomFileHeader;
		}
		return null;
	}

	/**
	 * This method returns the Dicom File Header from the file attached to it, if this 
	 * method is called after parsing the file it returns the Dicom File Header read 
	 * while parsing the file.
	 * Note: if this method is called in between reading the stream then unexpected behavior.
	 * @return IDicomFileHeader instance of DicomFileHeader.
	 * @throws IOException Signals that an I/O exception of some sort has occurred. 
	 */
	public IDicomFileHeader getDicomFileHeader() throws IOException
	{
		if(isHeaderPopulated)
		{
			return instanceDicomFileHeader;
		}
		if(instanceDicomFileHeader == null && DicomConstants.DCM_FILE_HEADER_PRESENT == isHeaderPresent() )
			instanceDicomFileHeader = readHeader();
		return instanceDicomFileHeader;
	}

	/**
	 * This method returns the offset from which DataSet is present in the file.
	 * @return int - DataOffset if header is present otherwise 0.
	 * @throws IOException Signals that an I/O exception of some sort has occurred. 
	 */
	private int getDataOffset() throws IOException
	{
		int iHeaderLength = 0;

		if(isHeaderPresent() == DicomConstants.DCM_FILE_HEADER_PRESENT)
		{
			init();
			objIDicomStream.mark(142);
			objIDicomStream.skip(132);
			iHeaderLength = calculateHeaderLength();
			objIDicomStream.reset();
			int iDataOffset =  DCM_FILE_PREAMBLE_LENGTH + DCM_FILE_HEADER_MARK + iHeaderLength;
			if (isStrFilePathInUse == true)
                close();
			return iDataOffset;
		}
		return 0;
	}

	/**
	 * This method returns the DataSet present in the dicom file.
	 * @return byte[] - byte array of data content.
	 * @throws IOException Signals that an I/O exception of some sort has occurred.  
	 */
	public byte[] getDataContents() throws IOException
	{
		byte[] dataContents = null;
		int iDataOffset = getDataOffset();
		
		init();

		objIDicomStream.skip(iDataOffset);
		int iDataLength = objIDicomStream.available();
		dataContents = new byte[iDataLength];
		objIDicomStream.readBuffer(dataContents);
		if (isStrFilePathInUse == true)
            close();
		return dataContents;
	}

	/**
	 * This method decides whether the header is present in the file or not.
	 * @return integer representing the presence or absence of header. Enumerated values are - 
	 * 		   DicomConstants.DCM_FILE_HEADER_ABSENT - header absent
	 * 		   DicomConstants.DCM_FILE_HEADER_PRESENT - header present
	 */
	private int isHeaderPresent() throws IOException
	{
		if(this.strFilePath != null)
		{
			init();
		}
		
		if(objIDicomStream.available() != 0)
		{
			objIDicomStream.mark(132);
			objIDicomStream.skip(128);

			byte byteOne = objIDicomStream.readByte();
			byte byteTwo = objIDicomStream.readByte();
			byte byteThree = objIDicomStream.readByte();
			byte byteFour = objIDicomStream.readByte();
			if( byteOne != 'D' && byteTwo != 'I' && byteThree != 'C'
				&& byteFour != 'M')
			{
				//This logic resets the ArchiveStream if Header is not present in the file
				objIDicomStream.reset();

				//Header is absent
				return DicomConstants.DCM_FILE_HEADER_ABSENT;
			}
			objIDicomStream.reset();
			if (isStrFilePathInUse == true)
                close();
			return DicomConstants.DCM_FILE_HEADER_PRESENT;
		}
		if (isStrFilePathInUse == true)
            close();
		return DicomConstants.DCM_FILE_HEADER_ABSENT;
	}

	/**
	 * This method returns the header length if present otherwise retuns 0.
	 * @return int- calculated length of Dicom File Header.
	 * @throws IOException Signals that an I/O exception of some sort has occurred.  
	 */
	public int getHeaderLength() throws IOException
	{
		return calculateHeaderLength();
	}

	/**
	 * This method identifies whether the header is present or not.
	 * If the header is present, then it calculates the header length .
	 * @return int- calculated length of Dicom File Header.
	 * @throws IOException Signals that an I/O exception of some sort has occurred. 
	 */
	private int calculateHeaderLength() throws IOException 
	{
		int iLengthOfRemainingHeader = 0;

		// Reading the length of the Header
		// For this purpose, FileReader reads tag, VRType, length and value from stream
		// The value specifies the Header length which is coming after this read

		//objIDicomStream.mark(10);
		byte[] tempBuffer = new byte[8];
		objIDicomStream.readBuffer(tempBuffer);
		tempBuffer = null;
		iLengthOfRemainingHeader = objIDicomStream.readShort();  //reading length of Header
		//objIDicomStream.reset();

		//12 is the length of GroupLengthElement which is the part of header
		int iLengthOfFileHeader = 12+iLengthOfRemainingHeader;
		return iLengthOfFileHeader;
	}
	
	private void init() throws IOException
    {
        if (objIDicomStream == null)
        {
        	FileInputStream objFileInputStream = new FileInputStream(strFilePath);
			DicomFileStream objDicomFileStream = new DicomFileStream();
			objDicomFileStream.setInputStream(objFileInputStream);
			objIDicomStream = objDicomFileStream;
			objIDicomStream.setEndianNess(DicomConstants.DCM_ENDIANNESS_LITTLE_ENDIAN);
        }
    }
	
	 private void close() throws IOException
     {
         objIDicomStream.close();
         objIDicomStream = null;
     }
}
