/*
 * $Id: RereadableInputStream.java 4558 2008-11-28 14:08:45Z uckelman $
 *
 * Copyright (c) 2008 by Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available 
 * at http://www.opensource.org.
 */

package VASSAL.tools.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;

/**
 * An rereadable {@link InputStream}. The {@link #reset()} method may be
 * uesd to rewind this input stream to a previously {@link #mark(int)}ed
 * location.
 *
 * @author Joel Uckelman
 * @since 3.1.0
 */
public class RereadableInputStream extends InputStream { 
  /* 
   * The implementation here is based on the one found at
   * http://www.mattryall.net/blog/2007/03/composition-resettable-stream
   */
  private InputStream src;
  private boolean marked;
  private ByteArrayOutputStream savedBytes;

  /**
   * Creates a <code>RereadableInputStream</code> which uses the given
   * <code>InputStream</code> as its source.
   *
   * @param src source input stream
   */ 
  public RereadableInputStream(InputStream src) {
    if (src == null) throw new IllegalArgumentException();
    this.src = src;
  }

  /** {@inheritDoc} */
  @Override
  public boolean markSupported() {
    return true;
  }

  /**
   * Set the current marked position in the stream.
   *
   * Note: The <code>readLimit</code> for this class determines only the
   * initial size of the buffer. Reading more than <code>readLimit</code>
   * bytes after calling this method will not invalidate the previous mark,
   * but will cause the buffer to be resized.
   *
   * @param readLimit the initial buffer size
   */
  @Override
  public synchronized void mark(int readlimit) {
    savedBytes = new ByteArrayOutputStream(readlimit);
    marked = true;
  }

  /** {@inheritDoc} */
  @Override
  public synchronized void reset() throws IOException {
    if (!marked)
      throw new IOException("Cannot reset unmarked stream");

    src = new CompositeInputStream(
      new ByteArrayInputStream(savedBytes.toByteArray()), src);

    marked = false;
  }

  /** {@inheritDoc} */
  @Override
  public int available() throws IOException {
    return src.available();
  }

  /** {@inheritDoc} */
  @Override 
  public int read() throws IOException {
    final int result = src.read();
    if (marked) savedBytes.write(result);
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    final int count = src.read(b, off, len);
    if (count > 0 && marked) savedBytes.write(b, off, count);
    return count;
  }

  /** {@inheritDoc} */
  @Override
  public void close() throws IOException {
    src.close();
  }

  public static void main(String[] args) throws Exception {
    final InputStream input =
      new ByteArrayInputStream(new byte[]{ 0, 1, 2, 3, 4, 5, 6, 7 });
    final InputStream resettable = new RereadableInputStream(input);

    assert resettable.markSupported();
    resettable.mark(4);

    final byte[] peekBuffer = new byte[4];
    final int peekBytesRead = resettable.read(peekBuffer, 0, 4);
    assert 4 == peekBytesRead;
    for (int i = 0; i < 4; i++) assert i == peekBuffer[i];

    resettable.reset();

    final byte[] completeBuffer = new byte[8];
    int pos = 0;
    int bytesRead = 0;
    while (pos < 8 && (bytesRead = resettable.read(completeBuffer, pos, 8-pos)) != -1) {
      pos += bytesRead;
    }

    assert 8 == pos : pos;
    for (int i = 0; i < 8; i++) assert i == completeBuffer[i];
  }  
}
