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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;

/**
 * This abstract class is parent of all stream classes used in DICOM. Applications that need to define the subclass of 
 * DicomStream must set either the input stream or the output stream before start using the class.
 * @see DicomBufferStream.
 * @see @FileStream.
 */
public abstract class DicomStream implements IDicomStream 
{

	protected InputStream objInputStream;
	protected OutputStream objOutputStream;
	protected int iEndianNess;
	protected int iBytesRead;
	protected int iStreamType;
	protected int iReadAheadLimit;

	/**
	 * Default Constructor for DicomStream.
	 */
	public DicomStream()
	{
		iEndianNess = DicomConstants.DCM_ENDIANNESS_LITTLE_ENDIAN;
		iBytesRead = 0;
		iReadAheadLimit = 0;
	}

	/**
	 * Returns the stream type to which it is set to [InputStream/outputStream]
	 * @return int - integer value of Stream Type.
	 */
	public int getStream()
	{
		if(objInputStream != null)
		{
			iStreamType = DicomConstants.STREAM_TYPE_INPUT;			
		}
		else if((objOutputStream != null))
		{
			iStreamType = DicomConstants.STREAM_TYPE_OUTPUT;		
		}
		//else if(objInputStream != null && objOutputStream != null)
		//	iStreamType = DicomConstants.STREAM_TYPE_INPUTOUTPUT;
		return iStreamType;		
	}

	/**
	 * Returns the endianNess of the stream [BIG_ENDIAN/LITTLE_ENDIAN]
	 * Endianness  defines the byte ordering used to store values as bytes in computer memory.
	 * @return int - Integer value shows the endianNess.
	 */
	public int getEndianNess()
	{
		return iEndianNess;
	}

	/**
	 * Sets the endianess of the steam [BIG_ENDIAN/LITTLE_ENDIAN]
	 * Endianness  defines the byte ordering used to store values as bytes in computer memory.
	 * @param iEndianNess the endianess of the steam [BIG_ENDIAN/LITTLE_ENDIAN]
	 * @return int - status indicating success or failure of the method
	 */
	public int setEndianNess(int iEndianNess)
	{
		this.iEndianNess = iEndianNess;
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Keeps track of how much bytes are read from the stream.
	 * This method is useful when the DicomStream is being used as input stream.
	 * When called on DicomStream used as output stream, it returns 0 .
	 * @return int - number of bytes read when called on DicomStream when used as input stream
	 * 				 0 if the DicomStream is used as output stream.
	 */
	public int getReadBytes()
	{
		return iBytesRead;
	}
	
	/**
	 * Reads a single byte from the stream.
	 * While reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException .
	 * @return byte - Next read byte value.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public byte readByte() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(1);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[1];
		objInputStream.read(arrByte);
		iBytesRead += 1;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.get();
	}

	/**
	 * Reads a character from the stream.
	 * While reading the specified number of characters from the stream, it doesnt use the endianness set
	 * into the stream before reading as DICOM specifies that the characters must be read from left to right 
	 * regardless of the endianness.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return char - Next read character value.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public char readChar() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		objInputStream.read(arrByte);
		iBytesRead += 2;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.getChar();
	}

	/**
	 * Reads specified number of characters from the stream.
	 * While reading the specified number of characters from the stream, it doesn't use the endianness set
	 * into the stream before reading as DICOM specifies that the characters must be read from left to right 
	 * regardless of the endianness.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @param iLength number of characters to read.
	 * @return byte[] - Byte array of next read characters.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public byte[] readChars(int iLength) throws IOException
	{
		byte[] arrChar = new byte[iLength];
		objInputStream.read(arrChar);
		iBytesRead += iLength;
		return arrChar;
	}

	/**
	 * Writes the characters on the stream.
	 * While writing the specified number of characters to the stream, it doesn't use the endianness set
	 * into the stream before writing as DICOM specifies that the characters must be read from left to right 
	 * regardless of the endianness
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param arrbyteChar byte array of characters to write.
	 * @return int - number of bytes written when called on DicomStream when used as output stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeChars(byte[] arrbyteChar) throws IOException
	{

		objOutputStream.write(arrbyteChar);
		objOutputStream.flush();
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Reads a short from the stream.
	 * while reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return short - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public short readShort() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		objInputStream.read(arrByte);
		iBytesRead += 2;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		short shVal = objByteBuffer.getShort(); 
		return shVal;
	}
	
	/**
	 * Reads an integer from the stream.
	 * while reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return int - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public int readInt() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		objInputStream.read(arrByte);
		iBytesRead += 4;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.getInt();
	}

	/**
	 * Reads a long from stream.
	 * In Java, long is of 8 bytes, but in Dicom Standard long data type length is expected as 4 bytes. 
	 * So here it reads only 4 bytes from stream.
	 * while reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return long - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public long readLong() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		objInputStream.read(arrByte);
		iBytesRead += 4;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.getInt();
	}
	
	/**
	 * Reads a float from the stream.
	 * while reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return float - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public float readFloat() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		objInputStream.read(arrByte);
		iBytesRead += 4;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.getFloat();
	}

	/**
	 * Reads a double from the stream.
	 * while reading the byte from the stream, it uses the endianness set into the stream before reading.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @return double - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public double readDouble() throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(8);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[8];
		objInputStream.read(arrByte);
		iBytesRead += 8;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		return objByteBuffer.getDouble();
	}

	/**
	 * Reads a string from the stream with specified length.
	 * While reading the string of length specified, it doesn't use the endianness set
	 * into the stream before reading as DICOM specifies that the string of characters must be read from 
	 * left to right regardless of the endianness.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @param iStringLength Length of the string to be read.
	 * @return String - value read from the stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public String readString(int iStringLength) throws IOException
	{
		String strSource = "";

		if(0 != iStringLength)
		{
			byte[] cString = new byte[iStringLength];
			objInputStream.read(cString);

			strSource = new String(cString);			
		}
		iBytesRead += iStringLength;

		return strSource;
	}

	/**
	 * Writes a string to the stream.
	 * While writing the specified string to the stream, it doesn't use the endianness set
	 * into the stream before writing as DICOM specifies that the characters must be read from left to right 
	 * regardless of the endianness.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param strValue string to be written.
	 * @return int - success if the string is read successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeString(String strValue) throws IOException
	{
		// Added at end of string to terminate string
		//strValue += "\0";
		byte[] byteArr = strValue.getBytes();

		objOutputStream.write(byteArr);
		objOutputStream.flush();
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Reads buffer byte per byte from stream.
	 * This method simply returns the byte array from the stream without applying the rules of endianness.
	 * When called on DicomStream used as output stream, it throws IOException.
	 * @param arrbyte byte array to be populated by stream.
	 * @return int - number of bytes actually read from input stream 
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream..
	 */
	public int readBuffer(byte[] arrbyte) throws IOException
	{
		int iNumberOfBytesRead = 0;

		iNumberOfBytesRead = objInputStream.read(arrbyte); 
		if(iNumberOfBytesRead != -1)
			iBytesRead += iNumberOfBytesRead;
		return iNumberOfBytesRead;
	}

	/**
	 * Reads a word from stream
	 * Note: This method is specifically designed for OW data type of DICOM. OW is other word which
	 *       represents two bytes word. This word needs to be read according to the endianness
	 *       set into the stream  
	 * When called on DicomStream used as output stream, it throws IOException
	 * @return byte[] - byte array of length 2 which contains the word read from stream.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream.
	 */
	public byte[] readWord() throws IOException
	{
		int iNumberOfBytesRead = 0;
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		iNumberOfBytesRead = objInputStream.read(arrByte);
		iBytesRead += iNumberOfBytesRead;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.put(arrByte).order(objByteOrder);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		return arrByte;
	}

	/**
	 * Writes a word to the stream.
	 * Note: This method is specifically designed for OW data type of DICOM. OW is other word which
	 *       represents two bytes word. This word needs to be written according to the endianness
	 *       set into the stream.
	 * @param arrbyteVal byte array of length 2 representing word to be written.
	 * @return int - success if the word is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeWord(byte[] arrbyteVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		arrByte[0] = arrbyteVal[0];
		arrByte[1] = arrbyteVal[1];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.put(arrByte);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();

		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes a byte to the stream.
	 * while writing the byte to the stream, it uses the endianness set into the stream before writing
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param byteVal value to be written.
	 * @return int - success if the byte is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeByte(byte byteVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(1);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[1];
		arrByte[0] = byteVal;
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.put(arrByte);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		//By Atreya,18-05-2007 added Code to flush the Stream
		objOutputStream.flush();


		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes a character to the stream.
	 * while writing the character to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param charVal value to be written.
	 * @return int - success if the character is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeChar(char charVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.put(arrByte);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		//By Atreya,18-05-2007 added Code to flush the Stream
		objOutputStream.flush();

		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes a short value on to the stream.
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param shVal value to be written.
	 * @return int - success if the character is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeShort(short shVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(2);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[2];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.putShort(shVal);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();

		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes an int value to the stream.
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param iVal integer value to write.
	 * @return int - success if the integer is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeInt(int iVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.putInt(iVal);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes a long to the stream.
	 * n Java, long is of 8 bytes, but in Dicom Standard long data type length.
	 * is expected as 4 bytes. So here we write 4 bytes only on to the stream.
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param iVal Shows long value to write.
	 * @return int - success if the long value is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeLong(int iVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.putInt(iVal);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();

		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}
	/**
	 * Writes a float value to the stream.
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param fVal Shows float value to be written.
	 * @return int - success if the float value is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeFloat(float fVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(4);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[4];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.putFloat(fVal);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Writes a double value to the stream.
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param dVal double value to be written.
	 * @return int - success if the double value is written successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public int writeDouble(double dVal) throws IOException
	{
		ByteBuffer objByteBuffer = ByteBuffer.allocate(8);
		ByteOrder objByteOrder = ByteOrder.LITTLE_ENDIAN;
		byte[] arrByte = new byte[8];
		if(iEndianNess == DicomConstants.DCM_ENDIANNESS_BIG_ENDIAN)
		{
			objByteOrder = ByteOrder.BIG_ENDIAN;
		}
		objByteBuffer.order(objByteOrder);
		objByteBuffer.putDouble(dVal);
		objByteBuffer.flip();
		objByteBuffer.get(arrByte);
		objOutputStream.write(arrByte);
		objOutputStream.flush();

		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/** 
	 * Writes a sequence of bytes to the stream. 
	 * while writing the short to the stream, it uses the endianness set into the stream before writing.
	 * When called on DicomStream used as input stream, it throws IOException.
	 * @param arrByte byte array contains value to be written.
	 * @param iOffset the start offset in the data.
	 * @param iLength the number of bytes to be written.
	 * @return boolean - true/false indicating success/failure of the operation.
	 * @throws IOException If IO operation fails or called on DicomStream used as input stream.
	 */
	public boolean writeBuffer(byte[] arrByte,int iOffset,int iLength) throws IOException
	{
		objOutputStream.write(arrByte,iOffset,iLength);
		objOutputStream.flush();
		return true;
	}

	/**
	 * Shows the beginning of receiving process.
	 * @return boolean - shows status. 
	 */
	public boolean startReceive()
	{		
		return false;
	}

	/**
	 * This method shows the end of sending process.
	 * @return boolean - shows status as success or failure.
	 * @throws IOException Exception if IO operation fails.
	 */
	public boolean endSend() throws IOException
	{
		if(objInputStream != null)
			objInputStream.close();
		if(objOutputStream != null)
			objOutputStream.close();
		return true;
	}

	/**
	 * Marks the current position in this input stream. A subsequent call to the reset method repositions this stream 
	 * at the last marked position so that subsequent reads re-read the same bytes. 
	 * The read limit arguments tells this input stream to allow that many bytes to be read
	 * before the mark position gets invalidated.
	 * The general contract of mark is that, if the method markSupported returns true, the stream somehow remembers all the bytes read after the call to mark and stands ready to supply those same bytes again if and whenever the method reset is called. However, the stream is not required to remember any data at all 
	 * if more than read limit bytes are read from the stream before reset is called. 
	 * Marking a closed stream should not have any effect on the stream.
	 * @param iReadAheadLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
	 * @return int - success if the stream is marked successfully.
	 */
	public int mark(int iReadAheadLimit)
	{
		if(objInputStream != null)
		{
			if(objInputStream.markSupported())
			{
				objInputStream.mark(iReadAheadLimit);
				this.iReadAheadLimit = iReadAheadLimit;
			}
		}
		else
			return DicomConstants.DCM_FUNCTION_STATUS_FAIL;
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * Repositions this stream to the position at the time the mark method was last called on this input stream.
	 * The general contract of reset is:
	 * If the method markSupported returns true, then:
	 * If the method mark has not been called since the stream was created, or the number of bytes 
	 * read from the stream since mark was last called is larger than the argument to mark at that last call,
	 * then an IOException might be thrown. 
	 * If such an IOException is not thrown, then the stream is reset to a state such that all the bytes 
	 * read since the most recent call to mark (or since the start of the file, if mark has not been called) 
	 * will be resupplied to subsequent callers of the read method, followed by any bytes that 
	 * otherwise would have been the next input data as of the time of the call to reset. 
	 * If the method markSupported returns false, then: 
	 * The call to reset may throw an IOException. 
	 * If an IOException is not thrown, then the stream is reset to a fixed state
	 * that depends on the particular type of the input stream and how it was created. 
	 * The bytes that will be supplied to subsequent callers of the read method depend on the particular
	 * type of the input stream. 
	 * @return int - success if the stream is reseted successfully.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream.
	 */
	public int reset() throws IOException
	{
		objInputStream.reset();
		//This is done for keep updating number of bytes read from the stream even in the 
		//process of mark and reset
		iBytesRead -= iReadAheadLimit; 
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}

	/**
	 * This method skips the specified number of bytes from the current position.
	 * @param iNumBytes number of bytes to be skipped.
	 * @return int - success if the operation is successful.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream.
	 */
	public int skip(int iNumBytes) throws IOException
	{
		objInputStream.skip(iNumBytes);
		//This is done for keep updating number of bytes read from the stream even in the 
		//process of mark and reset
		iBytesRead += iNumBytes;
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;
	}


	/**
	 * This method returns the number of bytes available on the stream
	 * @return int - Integer value of available bytes.
	 * @throws IOException If IO operation fails or called on DicomStream used as output stream.
	 */
	public int available() throws IOException
	{
		if(objInputStream != null)
			return objInputStream.available();
		return DicomConstants.DCM_FUNCTION_STATUS_FAIL;
	}

	/**
	 * This method closes the stream.
	 * If input stream is set into the DicomStream, then it closes the input stream and if
	 * output stream is set into the DicomStream, then it closes the output stream,
	 * @return int - success if the stream is closed successfully
	 * @throws IOException  Exception if IO operation fails.
	 */
	public int close() throws IOException
	{
		if(objInputStream != null)
			objInputStream.close();
		if(objOutputStream != null)
			objOutputStream.close();
		return DicomConstants.DCM_FUNCTION_STATUS_SUCCESS;

	}
}
