/*
 * Name: Mike Cifelli
 * Course: CIS 443 - Programming Languages
 * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis
 */
package scanner;
import java.io.InputStream;
import java.io.FilterInputStream;
import java.io.IOException;
/**
 * A LispFilterStream is an input stream that returns the bytes
 * from an underlying input stream with all Lisp comments removed and replaced
 * with newlines.
 */
public class LispFilterStream extends FilterInputStream {
    private boolean inQuote;
    /**
     * Creates a LispFilterStream with the specified underlying
     * InputStream.
     *
     * @param in
     *  the underlying input stream (must not be null)
     */
    public LispFilterStream(InputStream in) {
        super(in);
        inQuote = false;
    }
    /**
     * Reads the next byte of data from this input stream. The value byte is
     * returned as an int in the range 0 to
     * 255. If no byte is available because the end of the stream
     * has been reached, the value -1 is returned. 
     *
     * @return
     *  the next byte of data, or -1 if the end of the stream has
     *  been reached.
     * @throws IOException
     *  Indicates that an I/O error has occurred.
     */
    @Override
    public int read() throws IOException {
        int next = super.read();
        if ((next == ';') && (! inQuote)) {
            // we have entered a comment, consume all the bytes from the
            // underlying input stream until we reach a newline character or
            // the end of the stream
            while ((next != '\n') && (next != -1)) {
                next = super.read();
            }
        } else if (next == '\n') {
            inQuote = false;
        } else if (next == '\"') {  // we have entered or left a quoted string
            inQuote = (! inQuote);
        }
        return next;
    }
    /**
     * Reads up to the specified number of data bytes from this input stream
     * into an array of bytes starting at the specified offset. If no bytes are
     * available because the end of this stream has been reached then
     * -1 is returned. Also, If the specified number of bytes is
     * more than the number of remaining data bytes in this input stream, then
     * only the number of remaining data bytes are copied into the byte array.
     *
     * @param b
     *  the buffer into which the data bytes are read (must not be
     *  null)
     * @param off
     *  the start offset in b at which the data is written (must
     *  be >= 0 and < b.length)
     * @param len
     *  the maximum number of bytes to read into b (len +
     *  off must be < b.length)
     * @return
     *  the total number of bytes read into the buffer, or -1 if
     *  there is no more data because the end of the stream has been reached
     * @throws IOException
     *  Indicates that the first byte could not be read into b
     *  for some reason other than reaching the end of the stream.
     * @throws IndexOutOfBoundsException
     *  Indicates that this method attempted to access an index in
     *  b that was either negative or greater than or equal to
     *  b.length as a result of the given parameters.
     * @throws NullPointerException
     *  Indicates that b is null.
     */
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int bytesRead = 0;
        // make sure we are supposed to read at least one byte into 'b'
        if (len > 0) {
            int next = read();
            if (next == -1) {
                // there are no more bytes to read from this input stream
                return -1;
            }
            int i = off;
            while (next != -1) {
                ++bytesRead;
                b[i++] = (byte) next;
                if (i >= (off + len)) {  // we have read 'len' bytes into 'b'
                    break;
                }
                try {
                    next = read();
                } catch (IOException e) {
                    // treat this exception like an end of stream
                    break;
                }
            }
        }
        return bytesRead;
    }
    /**
     * Skip over and discard the specified number of bytes from this input
     * stream. This method may, for a variety of reasons, end up skipping some
     * smaller number of bytes, possibly 0. The actual number of
     * bytes skipped is returned.
     *
     * @param n
     *  the number of bytes to be skipped
     * @return
     *  the actual number of bytes skipped
     * @throws IOException
     *  Indicates that an I/O error has occurred.
     */
    @Override
    public long skip (long n) throws IOException {
        long bytesSkipped = 0;
        while ((n > 0) && (read() != -1)) {
            ++bytesSkipped;
            --n;
        }
        return bytesSkipped;
    }
}