/*
 * ao-hodgepodge - Reusable Java library of general tools with minimal external dependencies.
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2016, 2021, 2022  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-hodgepodge.
 *
 * ao-hodgepodge 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.
 *
 * ao-hodgepodge 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 ao-hodgepodge.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.aoapps.hodgepodge.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;
  }
}
