/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.io;

import java.io.*;

/**
 * Filter input stream that - in addition to normal processing - writes a copy
 * of the data, as it is being read, to a specified output stream. Allows for
 * basic splitting of an input stream. Name comes from Unix command "tee"
 * which has similar applications.
 *
 * @see ForkOutputStream
 * @see RedirectingInputStream
 *
 * @author Dawid Kurzyniec
 * @version 1.0
 */
public class TeeInputStream extends FilterInputStream {
    final OutputStream tee;
    final boolean autoFlush;
    final boolean autoClose;
    final boolean bestEffort;

    /**
     * Creates a new tee input stream that reads from a specified input and
     * sends a copy to a specified output. The tee output will be flushed after
     * each read from the input. EOF in the input will not cause closing of the
     * tee output. IOExceptions when writing to tee (e.g. if tee is closed)
     * are propagated.
     *
     * @param in the input to read from
     * @param tee the output to send copy to
     */
    public TeeInputStream(InputStream in, OutputStream tee) {
        this(in, tee, true, false, false);
    }

    /**
     * Creates a new tee input stream that reads from a specified input, sends
     * a copy to a specified output, and has specified flushing and closing
     * policy. The autoFlush parameter decides whether the tee output should be
     * automatically flushed after every read from the input. The autoClose
     * parameter decides whether the output should be closed upon EOF from
     * the input. IOExceptions when writing to tee (e.g. if tee is closed)
     * are propagated.
     *
     * @param in the input to read from
     * @param tee the output to send copy to
     * @param autoFlush decides whether to flush tee output after each read
     * @param autoClose decides whether to close tee output upon EOF on input
     */
    public TeeInputStream(InputStream in, OutputStream tee,
                          boolean autoFlush, boolean autoClose) {
        this(in, tee, autoFlush, autoClose, false);
    }

    /**
     * Creates a new tee input stream that reads from a specified input, sends
     * a copy to a specified output, and has specified flushing and closing
     * policy. The autoFlush parameter decides whether the tee output should be
     * automatically flushed after every read from the input. The autoClose
     * parameter decides whether the output should be closed upon EOF from
     * the input. The bestEffort parameter decides whether IOExceptions when
     * writing to tee (e.g. if tee is closed) should be propagated.
     *
     * @param in the input to read from
     * @param tee the output to send copy to
     * @param autoFlush decides whether to flush tee output after each read
     * @param autoClose decides whether to close tee output upon EOF on input
     * @param bestEffort if true, ignores IOExceptions on the tee (e.g. if tee
     *        is closed)
     */
    public TeeInputStream(InputStream in, OutputStream tee,
                          boolean autoFlush, boolean autoClose, boolean bestEffort) {
        super(in);
        this.tee = tee;
        this.autoFlush = autoFlush;
        this.autoClose = autoClose;
        this.bestEffort = bestEffort;
    }

    public int read() throws IOException {
        int b = super.read();
        if (b < 0) {
            if (autoClose) closeTee();
        }
        else {
            try {
                tee.write(b);
            }
            catch (IOException e) {
                if (!bestEffort) throw e;
            }
            if (autoFlush) flushTee();
        }
        return b;
    }

    public int read(byte[] buf, int off, int len) throws IOException {
        int read = super.read(buf, off, len);
        if (read < 0) {
            if (autoClose) closeTee();
        }
        else {
            try {
                tee.write(buf, off, read);
            }
            catch (IOException e) {
                if (!bestEffort) throw e;
            }
            if (autoFlush) flushTee();
        }
        return read;
    }

    public void close() throws IOException {
        super.close();
        if (autoClose) closeTee();
    }

    private void closeTee() throws IOException {
        try {
            tee.close();
        }
        catch (IOException e) {
            if (bestEffort) return;
            throw e;
        }
    }

    private void flushTee() throws IOException {
        try {
            tee.flush();
        }
        catch (IOException e) {
            if (bestEffort) return;
            throw e;
        }
    }
}
