/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.ssl;

import java.io.InputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * This class is a subclass of InputStream and obtains its input bytes
 * from an SSL connection.
 * <P />
 * @see com.sun.midp.ssl.SSLStreamConnection
 * @see com.sun.midp.ssl.Out
 */ 
class In extends InputStream {
    /** Underlying SSL record layer from which bytes are read. */
    private Record rec;
    
    /** Start of plain text in data buffer. */
    private int start;
    /** Count of unread bytes left in data buffer. */
    private int cnt;
    /** Handle for current SSL stream connection. */
    private SSLStreamConnection ssc;
    /** Signals end of stream. */
    private boolean endOfStream = false;

    /**
     * Refills the internal store of decrypted bytes. Called when 
     * the byte count in the store reaches zero.
     *
     * @param block if true the method will not return until data is available,
     *              or end of stream
     *
     * @exception IOException is thrown, if an I/O error occurs filling the
     * the buffer
     */ 
    private void refill(boolean block) throws IOException {
        if (endOfStream) {
           return;
        }

        for (; ; ) {
            rec.rdRec(block, Record.APP);
            if (rec.plainTextLength == -1) {
                endOfStream = true;
                return;
            }

            // Do not unblock on a zero byte record unless asked
            if (!block || rec.plainTextLength > 0) {
                break;
            }
        }

	cnt = rec.plainTextLength;
	start = 0;
    }
    
    /**
     * Creates a new In object.
     * <P />
     * @param r Record layer object from which input bytes are read
     * @param c SSLStreamConnection object this In object is a part of
     */ 
    In(Record r, SSLStreamConnection c) {
	rec = r;
	ssc = c;
    }

    /**
     * Reads a byte from this input stream. The method blocks if no
     * input is available.
     * <P />
     * @return the next byte of data, or -1 if end of stream is reached
     * @exception IOException if an I/O error occurs
     */ 
    synchronized public int read() throws IOException {
	int val;

        if (rec == null) {
            throw new InterruptedIOException("Stream closed");
        }

	if (cnt == 0) {
	    refill(true);
	    if (cnt == 0) {
		return -1; // end of stream
            }
	}

	val = rec.inputData[start++] & 0xff;
	cnt--;

	return val;
    }
    
    /**
     * Reads up to <CODE>b.length</CODE> bytes of data from this
     * input stream into the byte array <CODE>b</CODE>. Blocks until 
     * some input is available. This is equivalent to 
     * <CODE>read(b, 0, b.length)</CODE>.
     * <P />
     * @param b the buffer into which data is read
     * @return the actual number of bytes read into the buffer, or -1
     * if there is no more data and the end of input stream has been
     * reached
     * @exception IOException if an I/O error occurs
     */ 
    public int read(byte[] b) throws IOException {
	return read(b, 0, b.length);
    }

    /**
     * Reads up to <CODE>len</CODE> bytes of data from this input stream
     * into <CODE>b</CODE> starting at offset <CODE>off</CODE>.
     * <P />
     * @param b buffer into which data is read
     * @param off starting offset where data is read
     * @param len maximum number of bytes to be read
     * return the actual number of bytes read into the buffer, or -1
     * if there is no more data and the end of input stream has been
     * reached
     * @return number of bytes read
     * @exception IOException if an I/O error occurs
     */ 
    synchronized public int read(byte[] b, int off, int len)
	throws IOException {

	int i = 0;
        int numBytes;

        if (rec == null) {
            throw new InterruptedIOException("Stream closed");
        }

	if (cnt == 0) {
            // Record buffer empty, block until it is refilled.
	    refill(true);
	    if (cnt == 0) {
		return -1; // end of stream
            }
	}

        if (len > cnt) {
            numBytes = cnt;
        } else {
            numBytes = len;
        }
        
        System.arraycopy(rec.inputData, start, b, off, numBytes);
        start += numBytes;
        cnt -= numBytes;

	return numBytes;
    }

    /**
     * Close the stream connection.
     *
     * @exception IOException is thrown, if an I/O error occurs while
     * shutting down the connection
     */
    synchronized public void close() throws IOException {
	if (ssc != null) {
            ssc.inputStreamState = SSLStreamConnection.CLOSED;
            ssc.cleanupIfNeeded();
	    ssc = null;
            rec = null;
	}
    }

    /**
     * Returns the number of bytes that can be read (or skipped over) from
     * this input stream without blocking by the next caller of a method for
     * this input stream.  The next caller might be the same thread or
     * another thread.
     *
     * @return     the number of bytes that can be read from this input stream
     *             without blocking.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    public int available() throws IOException {
        if (rec == null) {
            throw new InterruptedIOException("Stream closed");
        }

	if (cnt == 0) {
            // The record buffer is empty, try to refill it without blocking.
	    refill(false);
	}

        return cnt;
    }

    /*
     * The remaining methods: markSupported(), mark(int), 
     * reset() need not be overridden
     */ 
}
