/*******************************************************************************
 *  Copyright 2017 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.hl7282.hl7streams;

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

import cdac.medinfo.sdk.hl7282.commoninterface.IHL7Stream;
import cdac.medinfo.sdk.hl7282.utils.HL7Constants;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;
/**
 * This is an abstract class providing all the common functionality and refernce for all extending classes.
 * This class facilitates reading primitive data from the stream and set in the stream.
 *
 */
public abstract class HL7Stream implements IHL7Stream 
{
	protected InputStream objInputStream;
	protected OutputStream objOutputStream;
	protected int iBytesRead;
	protected int iStreamType;
	protected int iReadAheadLimit;
	
	/**
	 * Returns the stream type to which it is set to [InputStream/outputStream].
	 * @return int - returns the stream type which is being used.
	 */
	public int getStream()
	{
		if(objInputStream != null)
		{
			iStreamType = HL7Constants.STREAM_TYPE_INPUT;			
		}
		else if((objOutputStream != null))
		{
			iStreamType = HL7Constants.STREAM_TYPE_OUTPUT;		
		}
		else if(objInputStream != null && objOutputStream != null)
				iStreamType = HL7Constants.STREAM_TYPE_INPUTOUTPUT;
		else
			iStreamType = HL7Constants.STREAM_TYPE_INVALID;
		return iStreamType;		
	}
	
	/**
	 * Tracks bytes read from the stream.
	 * @return int - the total no of bytes read 
	 */
	public int getReadBytes()
	{
		return iBytesRead;
	}
	
	/**
	 * Reads a single byte from the stream.
	 * @return byte - returns the byte read
	 */
	public byte readByte()
	{
		try
		{
			ByteBuffer objByteBuffer = ByteBuffer.allocate(1);
			byte[] arrByte = new byte[1];
			objInputStream.read(arrByte);
			iBytesRead += 1;
			objByteBuffer.put(arrByte);
			objByteBuffer.flip();
			return objByteBuffer.get();
		}
		catch(IOException ioe)
		{
			
		}
		return 0;
	}
	
	/**
	 * Reads a char from the stream.
	 * @return char - Character read from stream
	 * 				- '\0' if no character is read
	 */
	public char readChar()
	{
		char cTemp = '\0';
		try
		{	
			ByteBuffer objByteBuffer = ByteBuffer.allocate(1);
			byte[] arrByte = new byte[1];
			int iRetVal = objInputStream.read(arrByte);
			if(iRetVal == -1)
				return cTemp;
			iBytesRead += 1;
			objByteBuffer.put(arrByte);
			objByteBuffer.flip();
			cTemp = (char)objByteBuffer.get();
		}
		catch(IOException ioe)
		{
			
		}
		return cTemp;
		
	}

	/**
	 * Reads a string from the stream with specified length.
	 * @param strBuffValue length of the string to be read
	 * @param iCount specified length
	 * @return int - the read string 
	 */
	public int readString(StringBuffer strBuffValue, int iCount)
	{
		int iBytesRead = 0;
		String strTemp = "";
		if (iCount < 0)
			return HL7Constants.HL7_FUNCTION_FAIL;
		try
		{
			byte[] bytString = new byte[iCount];
			iBytesRead = objInputStream.read(bytString);
			strTemp = new String(bytString);
			//Clear string buffer
			StandardMethods.clearStringBuffer(strBuffValue);
			strBuffValue.append(strTemp);
		}
		catch (IOException ioe)
		{}
		return iBytesRead;
	}
	

	/**
	 * Writes the string specified in the parameter on the stream.
	 * @param strValue the String to write
	 * @return int - Success\Fail
	 */
	public int writeString(String strValue)
	{
		try
		{				
			for(int i=0;i<strValue.length();i++)
			{
				objOutputStream.write(strValue.charAt(i));
				objOutputStream.flush();
			}
			return HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_FAIL;
	}
	
	/** 
	 * Reads buffer byte by byte from stream.
	 * @param arrbyte byte of buffer
	 * @return iNumberOfBytesRead - Returns the number of bytes actually read from input stream <br>
	 * If no byte is available because the stream is at end of file, the value -1 is returned.
	 */
	public int readBuffer(byte[] arrbyte)
	{
		int iNumberOfBytesRead = 0;
		try
		{
			iNumberOfBytesRead = objInputStream.read(arrbyte); 
			if(iNumberOfBytesRead != -1)
				iBytesRead += iNumberOfBytesRead;
			return iNumberOfBytesRead;
		}
		catch(IOException IOExcep)
		{
			
		}
		return iNumberOfBytesRead;
	}
	
	/**
	 * Writes a byte to the stream.
	 * @param byteVal byte to write
	 * @return int - Success\Fail
	 */
	public int writeByte(byte byteVal)
	{
		try
		{
			objOutputStream.write(byteVal);
			//added Code to flush the Stream
			objOutputStream.flush();
			return HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_FAIL;
	}
	
	/**
	 * Writes a character with specified endianNess to the stream.
	 * @param charVal character to write
	 * @return int - Success\Fail
	 */
	public int writeChar(char charVal)
	{
		try
		{	
			objOutputStream.write(charVal);
			objOutputStream.flush();
			return HL7Constants.HL7_FUNCTION_SUCCESS;
			
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_FAIL;
	}
	
	/** 
	 * Writes Byte array to the DataOutputStream. 
	 * @param arrbyte byte array to write 
	 * @param iOffset the start offset in the data
	 * @param iLength the number of bytes to write
	 * @return int - Success\Fail
	 */
	public int writeBuffer(byte[] arrbyte,int iOffset,int iLength)
	{
		try
		{
			objOutputStream.write(arrbyte,iOffset,iLength);
			objOutputStream.flush();
			return HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_FAIL;
	}
	
	/**
	 * 
	 * @return boolean
	 */
	public boolean startReceive()
	{		
			return false;
	}
	
	/**
	 * 
	 * @return boolean
	 */
	public boolean endSend()
	{
		try
		{
			if(objInputStream != null)
				objInputStream.close();
			if(objOutputStream != null)
				objOutputStream.close();
		}
		catch(IOException ioe)
		{
		
		}
		return true;
	}
	
	/**
	 * Marks the stream for further resetting with specified readAheadLimit.
	 * @param iReadAheadLimit 
	 * @return int - Success\Fail
	 */
	public int mark(int iReadAheadLimit)
	{
		if(objInputStream != null)
		{
		    if(objInputStream.markSupported())
		    {
		    	objInputStream.mark(iReadAheadLimit);
		    	this.iReadAheadLimit = iReadAheadLimit;
		    }
		}
		else
			return HL7Constants.HL7_FUNCTION_FAIL;
		return HL7Constants.HL7_FUNCTION_SUCCESS;
	}
	
	/**
	 * Resets the stream if stream is read further within the readAheadLimit.
	 * @return int - Success\Fail
	 */
	public int reset()
	{
		try
		{
			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; 
			iReadAheadLimit = 0;
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_SUCCESS;
	}
	
	/**
	 * Skips the number of bytes specified in the parameter.
	 * @param iNumBytes
	 * @return int - Success\Fail
	 */
	public int skip(int iNumBytes)
	{
		try
		{
			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 HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(IOException ioe)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_SUCCESS;
	}
	
	//Need to think on whether we should separate inputstream and outputstream
	//so that available can be provided in inputstream 
	/**
	 * Returns the number of bytes available on the input stream.
	 * @return int returns the number of bytes that can be read
	 */
	public int available()
	{
		try
		{
			if(objInputStream != null)
				return objInputStream.available();
		}
		catch(IOException ioe)
		{
			
		}
		return 0;
	}
	
	/**
	 * Closes the Streams. 
	 * @return int - Success\Fail
	 */
	public int close()
	{
		try
		{
			if(objInputStream != null)
				objInputStream.close();
			if(objOutputStream != null)
				objOutputStream.close();
			return HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(Exception e)
		{
			
		}
		return HL7Constants.HL7_FUNCTION_FAIL;
	}
}
