/*
 * aocode-public - Reusable Java library of general tools with minimal external dependencies.
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aocode-public.
 *
 * aocode-public is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aocode-public 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aocode-public.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.io;

import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * A <code>NestedInputStream</code> reads data from
 * within an <code>InputStream</code> as if it were
 * a separate stream.  The underlying <code>InputStream</code>
 * is never closed, allowing it to be used for multiple
 * nested streams.
 *
 * @author  AO Industries, Inc.
 */
public final class NestedInputStream extends FilterInputStream {

    private boolean isEOF = false;
    final private byte[] buffer = new byte[32768];
    private int bufferFilled = 0;
    private int bufferRead = 0;

    public NestedInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int available() {
        return bufferFilled-bufferRead;
    }

    private int readBlockSize() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (ch1 << 8) | ch2;
    }

    private void readFully(byte b[], int off, int len) throws IOException {
	if (len < 0)
	    throw new IndexOutOfBoundsException();
	int n = 0;
	while (n < len) {
	    int count = in.read(b, off + n, len - n);
	    if (count < 0)
		throw new EOFException();
	    n += count;
	}
    }

    private void loadNextBlock() throws IOException {
        // Load the next block, if needed
        if(!isEOF && bufferRead>=bufferFilled) {
            bufferFilled = readShort();
            if(bufferFilled>0) {
                readFully(buffer, 0, bufferFilled);
                bufferRead=0;
            } else {
                isEOF=true;
                bufferFilled = bufferRead = 0;
                if(code!=EOF) throw new IOException("Unexpected code: "+code);
            }
        }
    }

    @Override
    public void close() throws IOException {
        if(!isEOF) {
            // Read the rest of the underlying stream
            int code;
            while((code=in.read())==NEXT) {
                int len=in.readShort();
                while(len>0) {
                    int skipped=(int)in.skip(len);
                    len-=skipped;
                }
            }
            isEOF=true;
            bufferFilled=bufferRead=0;
            if(buffer!=null) {
                BufferManager.release(buffer);
                buffer=null;
            }
            if(code!=EOF) throw new IOException("Unexpected code: "+code);
        }
    }
    
    public int read() throws IOException {
        if(isEOF) return -1;
        loadNextBlock();
        if(isEOF) return -1;
        return ((int)buffer[bufferRead++])&0xff;
    }
    
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if(isEOF) return -1;
        loadNextBlock();
        if(isEOF) return -1;
        int bufferLeft=bufferFilled-bufferRead;
        if(bufferLeft>len) bufferLeft=len;
        System.arraycopy(buffer, bufferRead, b, off, bufferLeft);
        bufferRead+=bufferLeft;
        return bufferLeft;
    }

    @Override
    public long skip(long n) throws IOException {
        if(isEOF) return -1;
        loadNextBlock();
        if(isEOF) return -1;
        int bufferLeft=bufferFilled-bufferRead;
        if(bufferLeft>n) bufferLeft=(int)n;
        bufferRead+=bufferLeft;
        return bufferLeft;
    }
    
    @Override
    public void finalize() throws Throwable {
        if(buffer!=null) {
            BufferManager.release(buffer);
            buffer=null;
        }
        super.finalize();
    }

    public void mark(int readlimit) {
        throw new RuntimeException("mark not supported");
    }

    @Override
    public void reset() throws IOException {
        throw new IOException("mark not supported");
    }

    @Override
    public boolean markSupported() {
	return false;
    }
}