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

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITag;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomBufferStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomFileStream;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDataTypeException;
import cdac.medinfo.sdk.dcm30_04.exceptions.ValidationException;
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;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

/**
 * This class is for parsing and serialization of OW data element containing pixel data.
 * Basically this class is for memory efficiency. If the pixel data i.e. tag 0x7FE0, 0x0010 is present
 * then it is parsed and written into a temporary file so that whole data is not into memory at the same time.
 * When the tag is serialized it is read from the file in chunks and written to the Dicom Stream.
 * Additional functionality to get bytes of image data is also provided.
 * If the image is very large then getBytes function of this method can be used to get the OBImageObject data byte by byte.
 */
public class OWImageObject extends DicomValue  
{
	

	private boolean isUndefinedLength;

	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = 1L;

	//this collection will hold the length of each image frame to make offset tabel.
	//ISingleCollection<Integer> objISingleCollection = new SingleCollection<Integer>();
	
	ISingleCollection<Frame> objCollOfFrames = new SingleCollection<Frame>();

	//this variable will tell number of frames in the image.
//	private int numberOfFrame = 0;

	//The file where the OBImage Data resides.
	private File file =null;

	//The File Stream having file as output and input stream .
	private DicomFileStream objDicomFileStream = new DicomFileStream();

	//This flag shows whether offset table is read or not.
	boolean flag = false;

	//Byte array containing Remaining bytes of Offset table.
	byte[] arrOffSetTable = null;

	//Byte array containing remaining bytes of frame structure.
	byte[] arrFrameStructureRem = null;

	//byte array containing remaining bytes of OB end structure.
	byte[] arrOWEndStructure = null;

	//this variable tell the current Frame Number 
	int iFrameNumber = 0;

	//this boolean variable tells whether some bytes of frame is remaining.
	boolean frameRemaining = false;

	//This variable tells the number of bytes remaining of current frame.
	int bytesRemainingOfCurrentFrame = 0;

	/**
	 * Default constructor initializes VR Type.
	 */
	public OWImageObject()
	{
		iVRType = DicomConstants.DCM_DT_OW;
	}

	/**
	 * The implementation of this method gets number of frames of a image.
	 * @return int - value of number of frames.
	 */
	public int getNumberOfFrame()
	{
		return objCollOfFrames.size();
	}

	/**
	 * The implementation of this method gets the length of the image data.
	 * @return int - length of the image data.
	 */
	public int getImageDataLength()
	{
		int iImageDataLength = 0;
		for(int iFrameCount=0 ; iFrameCount < objCollOfFrames.size(); iFrameCount++)
		{
			iImageDataLength = iImageDataLength + getFrameLength(iFrameCount);
		}
		return iImageDataLength;
	}

	/**
	 * Retrieves Value length of OWImageObject.
	 */
	public int getValueLength()
	{
		if(isUndefinedLength)
			return DicomConstants.DCM_UNDEFINED_LENGTH;

		return  getImageDataLength();
	}

	/**
	 * This function returns the Image data in vector of byte array.
	 * The size of vector tells the no of frame in the Image Data.
	 * @return ISingleCollection<byte[]>  - Vector of byte array containing Image Data.
	 */
	public Object getValue()
	{
		ISingleCollection< byte[]> objFrameCollection = null;
		byte[] arrByte = null;
		try
		{
			objFrameCollection = new SingleCollection<byte[]>();
			
			for (Frame frame : objCollOfFrames) 
			{
				arrByte = frame.getFrame();
				objFrameCollection.add(arrByte);
			}
		}
		catch( Exception e)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(e.getMessage());
			}
		}
		return objFrameCollection;
	}

	/**
	 * This function retirves the Image data in the Dicom Stream Provided by the User.
	 * The Size of each frame is in Interger array returned.
	 * @param objIDicomStream Dicom Stream in which Image Data should be written and given to the user.
	 * @return int[] Interger array containing Size of each frame.
	 */
	public int[] getValue(IDicomStream objIDicomStream)
	{
		int[] arrFrameSize = new int[objCollOfFrames.size()];
		try
		{
			for (Frame frame : objCollOfFrames) 
			{
				frame.getFrame(objIDicomStream);
			}
			for(int i=0; i<objCollOfFrames.size(); i++)
			{
				arrFrameSize[i] = getFrameLength(i);
			}
			
		}
		catch( Exception e)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(e.getMessage());
			}
		}
		return arrFrameSize;
	}

	/**
	 * This method Retrieves the frame specified by the index.
	 * @param index index number of the frame. This should be between 1 and the no of frame present in the image.
	 * @return byte[] byte array containing the image frame data. 
	 * @throws IOException Signals IOException if IO Operation fails.
	 */
	public byte[] getFrame(int index) throws IOException
	{															
		byte arrByte[] = null;
		if(index <= objCollOfFrames.size())
		{
			Frame frame = objCollOfFrames.get(index);
			arrByte = frame.getFrame();
		}
		return arrByte;
	}

	/**
	 * This function retirves the Frame specified by the index in the Dicom Stream Provided by the User.
	 * @param index index number of the frame. This should be between 1 and the no of frame present in the image.
	 * @param objDicomStream Dicom Stream provided by the user.
	 * @return boolean - return true if frame data is written to the Dicom Stream.
	 * 							false if fails to write frame data to Dicom Stream.
	 * @throws IOException Signals IOException if IO Operation fails.
	 */
	public boolean getFrame(int index, IDicomStream objDicomStream) throws IOException
	{
		if(index <= objCollOfFrames.size())
		{
			Frame frame = objCollOfFrames.get(index);
			frame.getFrame(objDicomStream);
			
			return true;
		}
		return false;
	}


	/**
	 * This function will add the frame of the image data.
	 * @param arrByte Byte array contianing the image data.
	 * @throws IOException throws IOException if the IO Operation fails.
	 */
	public void addFrame(byte[] arrByte) throws IOException
	{
		int iLastIndex = getValueLength();
		initialize();
		objCollOfFrames.add(new Frame(file, arrByte.length, iLastIndex));
		objDicomFileStream.writeBuffer(arrByte, 0, arrByte.length);
	}
	
	/**
	 * This function will add the frame of the image data.
	 * @param objFrame Object of Frame representing one frame of a image.
	 * @throws IOException throws IOException if the IO Operation fails.
	 */
	public void addFrame(Frame objFrame) throws IOException
	{
		int iLastIndex = getValueLength();
		initialize();
		objCollOfFrames.add(new Frame(file, objFrame.getFrameLength(), iLastIndex));
		objDicomFileStream.writeBuffer(objFrame.getFrame(), 0, objFrame.getFrameLength());
	}

	/**
	 * This function will add all the frames of the image data.
	 * @param objFrameCollection Vector of byte array containing the image data.
	 * @throws IOException throws IOException if the IO Operation fails.
	 */
	public void addFrames(ISingleCollection<byte[]> objFrameCollection) throws IOException
	{
		initialize();
		byte[] arrByte = null;
		for(int i= 0; i<objFrameCollection.size(); i++)
		{
			arrByte = objFrameCollection.get(i);
			//objISingleCollection.add(arrByte.length);
			int iStartPoint = getValueLength();
			objCollOfFrames.add(new Frame(file, arrByte.length, iStartPoint));
			objDicomFileStream.writeBuffer(arrByte, 0, arrByte.length);
		}
	}

	/**
	 * This function will add all the frames of the image data present in the Dicom Stream.
	 * The size of each frame should be given in the integer array.
	 * @param objIDicomStream Dicom Stream provided by the user.
	 * @param iArrSize Integer array containing the Size of each frame.
	 * @throws IOException throws IOException if the IO Operation fails.
	 */
	public void addFrames(IDicomStream objIDicomStream , int[] iArrSize) throws IOException
	{
		byte arrByte[] = null;
		initialize();
		int index=0;
		for(int i=0; i< iArrSize.length; i++)
		{
			//objISingleCollection.add(iArrSize[i]);
			int iStartPoint = getValueLength();
			objCollOfFrames.add(new Frame(file, iArrSize[i], iStartPoint));
		}
		while(objIDicomStream.available() != 0 && index < objCollOfFrames.size())
		{
			arrByte = new byte[getFrameLength(index)];
			objIDicomStream.readBuffer(arrByte);
			objDicomFileStream.writeBuffer(arrByte, 0, arrByte.length);
			index++;
		}
	}

	/**
	 * This function set the value of image data provided in vector of byte array.
	 * @param objFrameCollection Vector of byte array containing the image data.
	 * @throws IOException throws IOException if the IO Operation fails.
	 */
	public void setValue(ISingleCollection<byte[]> objFrameCollection) throws IOException
	{
		initialize();
		byte[] arrByte = null;
		for(int i= 0; i<objFrameCollection.size(); i++)
		{
			arrByte = objFrameCollection.get(i);
			objDicomFileStream.writeBuffer(arrByte, 0, arrByte.length);
		}
	}

	/**
	 * This method reads OW in the structure -> OW Basic Offset Table with NO Item Value(Tag and Length) 
	 * then Item Tag, length and value for each frame specified in the offset table3. 
	 * All the data read is written into a temporary file.
	 * @param objIDicomStream stream from which the Item is to be written
	 * @param iOBLength length of the DicomValue to be read from the stream
	 * @return integer - returns status success/failure
	 * @throws InvalidDataTypeException If data type validation fails.
	 * @throws IOException If Input/Output operation fails.
	 */
	public boolean read(IDicomStream objIDicomStream,int iOWLength) throws InvalidDataTypeException, IOException
	{
		initialize();
		int iGroupNumber = 0;
		int iElementNumber = 0;
		boolean bSequenceDelimitationExpected = false;
		int iStartPoint = 0;
		if(DicomConstants.DCM_UNDEFINED_LENGTH == iOWLength)
		{
			bSequenceDelimitationExpected = true;
			isUndefinedLength = true;
		}
		if(bSequenceDelimitationExpected == true)
		{
			int[] arrFrameCount;

			objDicomFileStream.setEndianNess(objIDicomStream.getEndianNess());
			iGroupNumber = objIDicomStream.readShort();
			iElementNumber = objIDicomStream.readShort();

			if(iGroupNumber < 0 )
			{
				iGroupNumber+= 2;	
				iGroupNumber = (65534 + iGroupNumber);
			}
			if(iElementNumber < 0)
			{
				iElementNumber+= 2;
				iElementNumber = (65534 + iElementNumber);
			}

			if(iGroupNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG 
					&& iElementNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG)
			{
				int iItemLength = objIDicomStream.readInt();
				if(iItemLength != 0)
				{
					int numberOfFrame = iItemLength/4;
					arrFrameCount = new int[numberOfFrame];

					for(int iFrameCount=0 ; iFrameCount < numberOfFrame; iFrameCount++)
					{
						arrFrameCount[iFrameCount] = objIDicomStream.readInt();
					}
				}
			}
			while(true)
			{
				iGroupNumber = objIDicomStream.readShort();
				iElementNumber = objIDicomStream.readShort();

				if(iGroupNumber < 0 )
				{
					iGroupNumber+= 2;	
					iGroupNumber = (65534 + iGroupNumber);
				}
				if(iElementNumber < 0)
				{
					iElementNumber+= 2;
					iElementNumber = (65534 + iElementNumber);
				}

				if(iGroupNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG  && iElementNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG)
				{
					int iItemLength = objIDicomStream.readInt();
					objCollOfFrames.add(new Frame(file, iItemLength, iStartPoint));
					iStartPoint += iItemLength;
					readChunk(objIDicomStream ,  iItemLength);		//reading chunk of data and writting to temporary file.
				}
				if(iGroupNumber == DicomConstants.DCM_SQ_DELIMITATION_DATA_MAJOR_TAG 
						&& iElementNumber == DicomConstants.DCM_SQ_DELIMITATION_DATA_MINOR_TAG)
				{
					objIDicomStream.readInt();
					break;
				}
			}
		}
		else
		{
			if(iOWLength > 0)
			{
				objCollOfFrames.add(new Frame(file, iOWLength, 0));
				readChunk(objIDicomStream ,  iOWLength);	//reading chunk of data and writting to temporary file.
			}
		}
		return true;
	}


	//this method read length of data from the objIDicomStream and writes it in temporary file.
	private void readChunk(IDicomStream objIDicomStream , int length) throws IOException
	{
		while(length != 0)
		{
			if(length > 1024)
			{
				byte[] buffer = new byte[1024];
				int iCount = 0;
				byte[] temp;
				while(iCount != 1024)
				{
					temp = objIDicomStream.readWord();
					buffer[iCount++] = temp[0];
					buffer[iCount++] = temp[1];
				}
				length = length - 1024;
				writeBuffer(buffer);		//writting to temporary file.
			}
			else
			{
				byte[] buffer = new byte[length];
				int iCount = 0;
				byte[] temp;
				while(iCount != length)
				{
					temp = objIDicomStream.readWord();
					buffer[iCount++] = temp[0];
					buffer[iCount++] = temp[1];
				}
				length = 0;
				writeBuffer(buffer);		//writting to temporary file.
			}
		}
	}
	/**
	 * This method writes the series of byte buffers present in the OW datatype.
	 * For this purpose it writes them in the structure specified in the Dicom standard.
	 * @param objIDicomStream stream on which to write the DataElements
	 * @return integer - status indicating success/failure
	 * @throws IOException If Input/Output operation fails.
	 */
	public int write(IDicomStream objIDicomStream) throws IOException
	{
		DicomFileStream objDicomFileStream = new DicomFileStream();
		FileInputStream objFileInputStream = new FileInputStream(file);
		objDicomFileStream.setInputStream(objFileInputStream);
		objDicomFileStream.setEndianNess(objIDicomStream.getEndianNess());

		if(getValueLength() == DicomConstants.DCM_UNDEFINED_LENGTH)
		{
			//Writing tag for multiframe image offset
			ITag objStartTag = new Tag(DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG,DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG);
			objStartTag.write(objIDicomStream);

			int iItemLength = objCollOfFrames.size() * 4;

			//Writing length of offset table
			objIDicomStream.writeInt(iItemLength);

			//Writing offset for each image byte array starting counter
			objIDicomStream.writeInt(0);    //writing first array offset
			for(int iFrameCount = 1; iFrameCount<objCollOfFrames.size();iFrameCount++)
			{
				objIDicomStream.writeInt(getFrameLength(iFrameCount-1));
			}

			//Writing actual pixel data of images
			for(int iDataCount = 0;iDataCount<objCollOfFrames.size();iDataCount++)
			{
				objStartTag.write(objIDicomStream);                                     //writing tag number
				int iFrameLength  = getFrameLength(iDataCount);
				objIDicomStream.writeInt(iFrameLength);   //writing length of image

				writeChunk(objIDicomStream ,objDicomFileStream , iFrameLength);
			}

			//If OBLength is undefined then write SQ delimiter and zero length at the end
			if(getValueLength() == DicomConstants.DCM_UNDEFINED_LENGTH)
			{
				ITag objDelimitationTag = new Tag(DicomConstants.DCM_SQ_DELIMITATION_DATA_MAJOR_TAG,DicomConstants.DCM_SQ_DELIMITATION_DATA_MINOR_TAG); 
				//writes the Tag information on the stream
				objDelimitationTag.write(objIDicomStream);
				//writes length as zero as specified in the Dicom Standard
				objIDicomStream.writeInt(0);
			}
		}
		else
		{
			//Since the value is now a collection and its first element is going to be the byte buffer
			int len = objFileInputStream.available();
			writeChunk(objIDicomStream ,objDicomFileStream , len);
		}
		objDicomFileStream.close();
		return 1;
	}

//	this method will read length of data from the file stream and write it into dicom stream
	private void writeChunk(IDicomStream objIDicomStream ,DicomFileStream objDicomFileStream , int length) throws IOException
	{
		while(length!=0)
		{
			if(length>=2)
			{
				byte[] buffer = new byte[2];
				objDicomFileStream.readBuffer(buffer);
				objIDicomStream.writeWord(buffer);
				length = length -2;
			}
			else
			{
				byte[] buffer = new byte[1];
				objDicomFileStream.readBuffer(buffer);
				objIDicomStream.writeWord(buffer);
				length = 0;
			}
		}
	}

	boolean flagOWEnd;

	/**
	 * The implementation of this method Reads iNumberOfByte bytes from the file stream and returns byte array of iNumberOfByte.
	 * @param iNumberOfBytes iNumberOfByte integer value of number of bytes required.
	 * @return byte[] - byte array of iNumberOFBytes.
	 * @throws IOException If IO operation fails
	 */
	public byte[] getBytes(int iNumberOfBytes) throws IOException
	{
		byte arrByte[] = new byte[iNumberOfBytes];
		int arrByteIndex = 0;
		if(getValueLength()  == DicomConstants.DCM_UNDEFINED_LENGTH)
		{
			//flag is false if offset table is not read.
			if(flag == false)								
			{
				//read the offset table and make flag true.
				arrOffSetTable = getOffsetTable();
				//flag is set to true after header is read once. So that each time get bytes is called the header is not read.
				flag = true;
			}
			if(arrOffSetTable != null)
			{
				//if length of data element header is equal to number of bytes required then return it.
				if(arrOffSetTable.length == iNumberOfBytes)
				{
					System.arraycopy(arrOffSetTable, 0, arrByte, 0, iNumberOfBytes);
					arrOffSetTable = null;
					return arrByte;
				}
				//if length of data element header is greater number of bytes required then copy iNumberOfBytes in arrByte
				//initialize arrDataElementByteHeader with remaining bytes and return arrByte.
				else if(arrOffSetTable.length > iNumberOfBytes)
				{
					System.arraycopy(arrOffSetTable, 0, arrByte, 0, iNumberOfBytes);
					byte[] tempByte = new byte[arrOffSetTable.length-iNumberOfBytes];
					System.arraycopy(arrOffSetTable, iNumberOfBytes, tempByte, 0, tempByte.length);
					arrOffSetTable = tempByte;
					return arrByte;
				}
				//if length of data element header is smaller than number of bytes required then copy arrDataElementByteHeader in arrByte
				//read the remaining bytes of image data
				else
				{
					System.arraycopy(arrOffSetTable, 0, arrByte, arrByteIndex, arrOffSetTable.length);
					arrByteIndex = arrByteIndex + arrOffSetTable.length;
					int iLeftBytes = iNumberOfBytes - arrOffSetTable.length;
					byte[] arrImageByte = getImageBytes(iLeftBytes);
					System.arraycopy(arrImageByte, 0, arrByte, arrByteIndex, arrImageByte.length);
					arrByteIndex = arrByteIndex + arrImageByte.length;
					arrOffSetTable = null;
				}
			}
			//if frame offset table is read then read the bytes directly from the image data.
			else
			{
				byte[] arrImageByte = getImageBytes(iNumberOfBytes);
				System.arraycopy(arrImageByte, 0, arrByte, 0, arrImageByte.length);
				arrByteIndex = arrByteIndex + arrImageByte.length;
			}

			if(arrByteIndex == iNumberOfBytes)
				return arrByte;

			int iByteLeft = iNumberOfBytes - arrByteIndex;
//			If OBLength is undefined then write SQ delimiter and zero length at the end
			if(iFrameNumber == objCollOfFrames.size())
			{
				//if arrOBEndStructure is not null i.e. remaining bytes of OBEndStructure is remaining then 
				//write it.
				if(arrOWEndStructure != null)
				{
					//if length of OW End structure is greater than bytes left then copy bytes left to arrByte and 
					//rest hold rest in arrOWEndStructure.
					if(arrOWEndStructure.length > iByteLeft)
					{
						System.arraycopy(arrOWEndStructure, 0, arrByte, arrByteIndex, iByteLeft);
						byte[] arrTemp = new byte[arrOWEndStructure.length - iByteLeft];
						System.arraycopy(arrOWEndStructure, iByteLeft, arrTemp, arrByteIndex, arrTemp.length);
						arrOWEndStructure = arrTemp;
						return arrByte;
					}
					//if length of OW End structure is equal to bytes left then copy bytes left to arrByte and 
					//set arrOWEndStructure null.
					//set flagOWEnd to false
					else if(arrOWEndStructure.length == iByteLeft)
					{
						System.arraycopy(arrOWEndStructure, 0, arrByte, arrByteIndex, iByteLeft);
						arrOWEndStructure = null;
						flagOWEnd = false;
						return arrByte;
					}
					//if length of OW End structure is less than bytes left then copy bytes left to arrByte and 
					//set arrOWEndStructure null.
					//set flagOWEnd to false
					else
					{
						System.arraycopy(arrOWEndStructure, 0, arrByte, arrByteIndex, iByteLeft);
						arrByteIndex = arrByteIndex + iByteLeft;
						flagOWEnd = false;
						arrOWEndStructure = null;
					}
				}
				//if arrOWEndStructure is null then get the bytes of OBEndStructure.
				else
				{
					byte[] arrTempByte = getOWEndStructure();
					int bytesLeft = iNumberOfBytes - arrByteIndex;
					if(bytesLeft >= arrTempByte.length)
					{
						System.arraycopy(arrTempByte, 0, arrByte, arrByteIndex, arrTempByte.length);
						arrByteIndex = arrByteIndex + arrTempByte.length;
						flagOWEnd = false;
					}
					else
					{
						System.arraycopy(arrTempByte, 0, arrByte, arrByteIndex, bytesLeft);
						byte[] arrTemp =  new byte[arrTempByte.length-bytesLeft];
						System.arraycopy(arrTempByte, bytesLeft, arrTemp, 0, arrTemp.length);
						arrOWEndStructure = arrTemp;
						arrByteIndex = arrByteIndex + bytesLeft;
					}
				}
			}
			//if bytes iNumberOfBytes is greater then the bytes of image data then copy it to 
			//bytes get and return it to the user.
			if(arrByteIndex < iNumberOfBytes)
			{
				byte[] bytesGet = new byte[arrByteIndex];
				System.arraycopy(arrByte, 0, bytesGet, 0, arrByteIndex);
				return bytesGet;
			}
			else
				return arrByte;
		}
		//if OW is not of -1 length then one frame should be present.
		//read it and return it.
		else
		{
			if(objDicomFileStream.available() != 0 && objDicomFileStream.available() >=iNumberOfBytes)
				arrByte = new byte[iNumberOfBytes];
			else
				arrByte = new byte[objDicomFileStream.available()];

			objDicomFileStream.readBuffer(arrByte);
			return arrByte;
		}
//		return null;
	}


	//this function will get the image bytes.
	private byte[] getImageBytes(int iNumberOfBytes) throws IOException
	{
		byte[] arrImageByte = null;
		int bytesLeft = iNumberOfBytes;
		int iFrameLength = 0;
		byte[] arrByte = new byte[iNumberOfBytes];
		int arrByteIndex = 0;

		if(arrFrameStructureRem != null)
		{
			if(arrFrameStructureRem.length > bytesLeft)
			{
				System.arraycopy(arrFrameStructureRem, 0, arrByte, arrByteIndex, bytesLeft);
				byte[] tempByte = new byte[arrFrameStructureRem.length-bytesLeft];
				System.arraycopy(arrFrameStructureRem, iNumberOfBytes, tempByte, 0, tempByte.length);
				arrFrameStructureRem = tempByte;
				arrFrameStructureRem = null;
				return arrByte;
			}
			else if(arrFrameStructureRem.length == bytesLeft)
			{
				System.arraycopy(arrFrameStructureRem, 0, arrByte, arrByteIndex, bytesLeft);
				arrFrameStructureRem = null;
				return arrByte;
			}
			else 
			{
				System.arraycopy(arrFrameStructureRem, 0, arrByte, arrByteIndex, arrFrameStructureRem.length);
				arrByteIndex = arrByteIndex + arrFrameStructureRem.length;
				bytesLeft = bytesLeft - arrFrameStructureRem.length;
				arrFrameStructureRem = null;
			}
		}
		//if some of the bytes of frame is remaining then frameRemaining will be true and 
		//get the remaining bytes of the frame
		if(frameRemaining == true)
		{
			//if number of bytes required is greater than bytes remaining of current frame then 
			//copy bytes remaining from file sream and copy in arrByte.
			if(bytesLeft > bytesRemainingOfCurrentFrame)
			{
				if(objDicomFileStream.available() != 0 && objDicomFileStream.available() >= bytesRemainingOfCurrentFrame)
					arrImageByte = new byte[bytesRemainingOfCurrentFrame];
				else
					arrImageByte = new byte[objDicomFileStream.available()];
				objDicomFileStream.readBuffer(arrImageByte);
				System.arraycopy(arrImageByte, 0, arrByte, arrByteIndex, arrImageByte.length);
				//update bytes left.
				bytesLeft = bytesLeft - arrImageByte.length;
				//update index of arr byte.
				arrByteIndex = arrByteIndex + arrImageByte.length;
				frameRemaining = false;
			}
			//else copy iNumber of bytes from the bytesRemainingOfCurrentFrame and 
			//update bytesRemainingOfCurrentFrame. return arriMAGEbYTES.
			else
			{
				//copy iNumber of byte from file stream and returns to the user.
				if(objDicomFileStream.available() != 0 && objDicomFileStream.available() >= bytesLeft)
					arrImageByte = new byte[bytesLeft];
				else
					arrImageByte = new byte[objDicomFileStream.available()];
				objDicomFileStream.readBuffer(arrImageByte);
				bytesRemainingOfCurrentFrame = bytesRemainingOfCurrentFrame - bytesLeft;
				if(bytesLeft == iNumberOfBytes)
					return arrImageByte;
				else
					System.arraycopy(arrImageByte, 0, arrByte, arrByteIndex, arrImageByte.length);
				bytesLeft = bytesLeft - arrImageByte.length;
				arrByteIndex = arrByteIndex + arrImageByte.length;
			}
		}
		//copy from file stream to byte array iNumberOFBytes till iFrameLength is greater then bytes left and 
		//iFrame Number is leff than number of frames.
		if(iFrameNumber < objCollOfFrames.size())
		{
			iFrameLength = getFrameLength(iFrameNumber);
			while(bytesLeft > iFrameLength)
			{
				//write frame structe.
				byte[] arrFrameStructure = getFrameStructure(iFrameNumber);
				bytesLeft = bytesLeft - arrFrameStructure.length;
				System.arraycopy(arrFrameStructure, 0, arrByte, arrByteIndex, arrFrameStructure.length);
				arrByteIndex = arrByteIndex + arrFrameStructure.length;

				if(objDicomFileStream.available() != 0 && objDicomFileStream.available() >= getFrameLength(iFrameNumber))
					arrImageByte = new byte[iFrameLength];
				else
					arrImageByte = new byte[objDicomFileStream.available()];
				objDicomFileStream.readBuffer(arrImageByte);

				bytesLeft = bytesLeft - arrImageByte.length;

				System.arraycopy(arrImageByte, 0, arrByte, arrByteIndex, arrImageByte.length);
				arrByteIndex = arrByteIndex + arrImageByte.length;
				iFrameNumber++;
				if(iFrameNumber < objCollOfFrames.size())
					iFrameLength = getFrameLength(iFrameNumber);
			}
		}
		//if bytesLeft is less than Frmae length but not equal to zero.
		//it means that some bytes of frame need to be wriiten not the whole frame
		//get the frame structue.
		//get the bytes left
		//make frameRemaining bytes true as some bytes of frame is remaining. Update its value. Increment the frame number. 
		if(bytesLeft != 0 && iFrameNumber < objCollOfFrames.size())
		{
			byte[] arrFrameStructure = getFrameStructure(iFrameNumber);
			if(arrFrameStructure.length <= bytesLeft)
			{
				bytesLeft = bytesLeft - arrFrameStructure.length;
				System.arraycopy(arrFrameStructure, 0, arrByte, arrByteIndex, arrFrameStructure.length);
				arrByteIndex = arrByteIndex + arrFrameStructure.length;

				if(objDicomFileStream.available() != 0 && objDicomFileStream.available() >= bytesLeft)
					arrImageByte = new byte[bytesLeft];
				else
					arrImageByte = new byte[objDicomFileStream.available()];
				objDicomFileStream.readBuffer(arrImageByte);
				System.arraycopy(arrImageByte, 0, arrByte, arrByteIndex, arrImageByte.length);
				arrByteIndex = arrByteIndex + arrImageByte.length;
				bytesRemainingOfCurrentFrame = getFrameLength(iFrameNumber) - bytesLeft;
				iFrameNumber++;
				frameRemaining = true;
			}
			else
			{
				System.arraycopy(arrFrameStructure, 0, arrByte, arrByteIndex, bytesLeft);
				arrFrameStructureRem = new byte[arrFrameStructure.length-bytesLeft];
				System.arraycopy(arrFrameStructure, bytesLeft, arrFrameStructureRem, 0, arrFrameStructureRem.length);
				bytesRemainingOfCurrentFrame = getFrameLength(iFrameNumber);
				iFrameNumber++;
				frameRemaining = true;
				bytesLeft = 0;
				return arrByte;
			}
		}
//		if the bytes of image data is less then number of bytes then make array of bytes get and return it.
		if(arrByteIndex < iNumberOfBytes)
		{
			byte[] arrByteGet = new byte[arrByteIndex];
			System.arraycopy(arrByte, 0, arrByteGet, 0, arrByteGet.length);
			return arrByteGet;
		}
		else
			return arrByte;
	}

//	this function will return the lenth of the frame given the frame number.
	private int getFrameLength(int iFrameNo)
	{
		return objCollOfFrames.get(iFrameNo).getFrameLength();
	}

//	This function will return the frame structure that is to be written before writting each frmae if the lenth is -1.
	private byte[] getFrameStructure(int index) throws IOException
	{
		DicomBufferStream objDicomBufferStream = new DicomBufferStream();
		ByteArrayOutputStream objByteArrayOutputStream = new ByteArrayOutputStream();
		objDicomBufferStream.setOutputStream(objByteArrayOutputStream);
		objDicomBufferStream.setEndianNess(objDicomFileStream.getEndianNess());

		ITag objITag = new Tag(DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG,DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG);
		objITag.write(objDicomBufferStream);

		int iFrameLength = getFrameLength(index);
		objDicomBufferStream.writeInt(iFrameLength * 2);

		return objDicomBufferStream.toByteArray();
	}

	private byte[] getOWEndStructure() throws IOException
	{
		DicomBufferStream objDicomBufferStream = new DicomBufferStream();
		ByteArrayOutputStream objByteArrayOutputStream = new ByteArrayOutputStream();
		objDicomBufferStream.setOutputStream(objByteArrayOutputStream);
		objDicomBufferStream.setEndianNess(objDicomFileStream.getEndianNess());
		ITag objITag = new Tag(DicomConstants.DCM_SQ_DELIMITATION_DATA_MAJOR_TAG,DicomConstants.DCM_SQ_DELIMITATION_DATA_MINOR_TAG);
		//writes the Tag information on the stream
		objITag.write(objDicomBufferStream);
		//writes length as zero as specified in the Dicom Standard
		objDicomBufferStream.writeInt(0);

		return objDicomBufferStream.toByteArray();
	}

	/*
	 * This function will return the offset table of OB Image Object if the Length is -1.
	 * @throws IOException if IO operation fails
	 */
	private byte[] getOffsetTable() throws IOException
	{
		DicomBufferStream objDicomBufferStream = new DicomBufferStream();
		ByteArrayOutputStream objByteArrayOutputStream = new ByteArrayOutputStream();
		objDicomBufferStream.setOutputStream(objByteArrayOutputStream);
		objDicomBufferStream.setEndianNess(objDicomFileStream.getEndianNess());

		ITag objITag = new Tag(DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG,DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG);
		objITag.write(objDicomBufferStream);

		int iItemLength = objCollOfFrames.size() * 4;

		//Writing length of offset table
		objDicomBufferStream.writeInt(iItemLength);

		//Writing offset for each image byte array starting counter
		objDicomBufferStream.writeInt(0);    //writing first array offset
		for(int iFrameCount = 1; iFrameCount<objCollOfFrames.size();iFrameCount++)
		{
			objDicomBufferStream.writeInt(getFrameLength(iFrameCount));
		}

		return objDicomBufferStream.toByteArray();
	}
	/**
	 * This method gives information if OB image Object data is avalilabe.
	 * @return boolean - true if OB image Object data is avalilable
	 * 					 false if OB image Object data is not avalilable
	 * @throws IOException if IO operation fails
	 */ 
	public boolean isAvailable() throws IOException
	{
		if(objDicomFileStream.available() != 0)
			return true;
		else if (flagOWEnd == true)
			return true;
		else 
		{
			return false;
		}
	}

	//This method writes byte array to file.
	private void writeBuffer(byte[] arrByte) throws IOException
	{
		objDicomFileStream.writeBuffer(arrByte, 0, arrByte.length);
	}

	//This function creates temporay file when the object of OBImageObject is instantiated.
	private void initialize() throws IOException
	{
		String strTempStorageFilePath = DicomConfig.createInstance().getTempStorageFilePath();
		if(file == null)
		{
			if(strTempStorageFilePath.equals(""))
			{
				strTempStorageFilePath = StandardMethods.getTempDirectoryStorage();
				strTempStorageFilePath = strTempStorageFilePath + "DicomSDK";
			}
			File objDir = new File(strTempStorageFilePath);
			if(!objDir.exists())
				objDir.mkdirs();
			String strTempStorageFile = strTempStorageFilePath + "//" + StandardMethods.generateUUID();
			file = new File(strTempStorageFile);
			file.deleteOnExit();
			FileOutputStream objFileOutputStream = new FileOutputStream(file, true);
			FileInputStream objFileInputStream = new FileInputStream(file);
			objDicomFileStream.setInputStream(objFileInputStream);
			objDicomFileStream.setOutputStream(objFileOutputStream);
		}
	}

	/**
	 * Validates OB DataType
	 * @param objValue value of OB to be validated
	 * @return boolean status indicating success/failure
	 * @throws ValidationException If validation of DataType fails.
	 */
	public boolean validate(Object objValue) throws ValidationException
	{
		return true;
	}

	/**
	 * Validates OB DataType
	 * @return boolean status indicating success/failure
	 * @throws ValidationException If validation of DataType fails.
	 */
	public boolean validate() throws ValidationException
	{
		return true;
	}

	/**
	 * Retrieves the max length for OB DataType
	 * @return integer - max length for OB DataType
	 */
	public long getMaxLength()
	{
		return 0;
	}

	/**
	 * This function disposes OW Image Object.
	 * It closes the File Stream and Deletes the temporary file.
	 * @throws IOException Signals if IO Operation fails.
	 */
	public void dispose() throws IOException
	{
		if(objDicomFileStream != null)
			objDicomFileStream.close();
		if(file!=null)
			file.delete();
	}

	/**
	 * 	Dumps the value of OW Image Object as Hidden to the specified Stream.
	 * 	@param objIDicomStream - the Stream on which the Data is to be written.
	 *  @throws IOException - Exception is thrown indicating runtime failure of writing operation.
	 */
	public void dumpDICOMInfo(IDicomStream objIDicomStream) throws IOException
	{
		objIDicomStream.writeString("Value : " + "hidden");
	}

	/**
	 * This function sets the Value length to -1.
	 * If this function is called then then length of value is undefined length(-1)
	 */
	public void changeToUndefined()
	{
		this.isUndefinedLength = true;
	}
}
