/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander 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.
 *
 * muCommander 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.commons.io;

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

/**
 * An InputStream that keeps track of the number of bytes that have been read from it. Bytes that are skipped (using
 * {@link #skip(long)} are by default accounted for, {@link #setCountSkippedBytes(boolean)} can be used to change this.
 *
 * <p>The actual number of bytes can be retrieved from the {@link ByteCounter} instance returned by {@link #getCounter()}.
 * The {@link #CounterInputStream(InputStream, ByteCounter)} constructor can be used to specify an existing
 * ByteCounter instance instead of creating a new one. The ByteCounter will always remain accessible, even
 * after this stream has been closed.
 *
 * @see ByteCounter
 * @author Maxence Bernard
 */
public class CounterInputStream extends InputStream {

    /** Underlying InputStream */
    private final InputStream in;

    /** Byte counter */
    private final ByteCounter counter;

    /** Should skipped bytes be accounted for ? (enabled by default) */
    private boolean countSkippedBytes = true;


    /**
     * Creates a new CounterInputStream using the specified InputStream. A new {@link ByteCounter} will be created.
     *
     * @param in the underlying InputStream the data will be read from
     */
    public CounterInputStream(InputStream in) {
        this.in = in;
        this.counter = new ByteCounter();
    }

    /**
     * Creates a new CounterInputStream using the specified InputStream and {@link ByteCounter}.
     * The provided <code>ByteCounter</code> will NOT be reset, whatever value it contains will be kept.
     *
     * @param in the underlying InputStream the data will be read from
     */
    public CounterInputStream(InputStream in, ByteCounter counter) {
        this.in = in;
        this.counter = counter;
    }


    /**
     * Returns the ByteCounter that holds the number of bytes that have been read (and optionally skipped) from this
     * InputStream.
     */
    public ByteCounter getCounter() {
        return this.counter;
    }


    /**
     * Specifies whether or not skipped bytes (using {@link #skip(long)} should be accounted for.
     * This is by default enabled, bytes that are skipped are added to the ByteCounter.
     *
     * @param countSkippedBytes if true, skipped bytes will be accounted for, the ByteCounter will be increased
     * by the number of skipped bytes
     */
    public void setCountSkippedBytes(boolean countSkippedBytes) {
        this.countSkippedBytes = countSkippedBytes;
    }

    /**
     * Returns true if skipped bytes (using {@link #skip(long)} are accounted for. 
     * This is by default enabled, bytes that are skipped are added to the ByteCounter.
     */
    public boolean getCountSkippedBytes() {
        return countSkippedBytes;
    }


    ////////////////////////////////
    // InputStream implementation //
    ////////////////////////////////

    @Override
    public int read() throws IOException {
        int i = in.read();
        if(i>0)
            counter.add(1);

        return i;
    }


    @Override
    public int read(byte b[]) throws IOException {
        int nbRead = in.read(b);
        if(nbRead>0)
            counter.add(nbRead);

        return nbRead;
    }

    @Override
    public int read(byte b[], int off, int len) throws IOException {
        int nbRead = in.read(b, off, len);
        if(nbRead>0)
            counter.add(nbRead);

        return nbRead;
    }

    @Override
    public long skip(long n) throws IOException {
        long nbSkipped = in.skip(n);

        // Count skipped bytes only if this has been enabled
        if(countSkippedBytes && nbSkipped>0)
            counter.add(nbSkipped);

        return nbSkipped;
    }


    @Override
    public int available() throws IOException {
        return in.available();
    }


    @Override
    public void close() throws IOException {
        in.close();
    }


    @Override
    public void mark(int readLimit) {
        in.mark(readLimit);
    }


    @Override
    public boolean markSupported() {
        return in.markSupported();
    }


    @Override
    public void reset() throws IOException  {
        in.reset();
    }
}
