package ij.io;
import ij.IJ;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
 * This class provides the functionality to divert output sent to the System.out
 * and System.err streams to ImageJ's log console. The purpose is to allow 
 * use of existing Java classes or writing new generic Java classes that only 
 * output to System.out and are thus less dependent on ImageJ.
 * See the ImageJ plugin Redirect_System_Streams at
 *    http://staff.fh-hagenberg.at/burger/imagej/
 * for usage examples.
 *
 * @author Wilhelm Burger (wilbur at ieee.org)
 * See Also: Redirect_System_Streams (http://staff.fh-hagenberg.at/burger/imagej/)
 */
public class LogStream extends PrintStream {
    
    private static String outPrefix = "out> ";  // prefix string for System.out
    private static String errPrefix = "err >";  // prefix string for System.err
    
    private static PrintStream originalSystemOut = null;
    private static PrintStream originalSystemErr = null;
    private static PrintStream temporarySystemOut = null;
    private static PrintStream temporarySystemErr = null;
    
    /**
     * Redirects all output sent to <code>System.out</code> and <code>System.err</code> to ImageJ's log console
     * using the default prefixes.
     */
    public static void redirectSystem(boolean redirect) {
        if (redirect)
            redirectSystem();
        else
            revertSystem();
    }
    
    /**
     * Redirects all output sent to <code>System.out</code> and <code>System.err</code> to ImageJ's log console
     * using the default prefixes.
     * Alternatively use 
     * {@link #redirectSystemOut(String)} and {@link #redirectSystemErr(String)}
     * to redirect the streams separately and to specify individual prefixes.
     */
    public static void redirectSystem() {
        redirectSystemOut(outPrefix);
        redirectSystemErr(errPrefix);
    }

    /**
     * Redirects all output sent to <code>System.out</code> to ImageJ's log console.
     * @param prefix The prefix string inserted at the start of each output line. 
     * Pass <code>null</code>  to use the default prefix or an empty string to 
     * remove the prefix.
     */
    public static void redirectSystemOut(String prefix) {
        if (originalSystemOut == null) {        // has no effect if System.out is already replaced
            originalSystemOut = System.out;     // remember the original System.out stream
            temporarySystemOut = new LogStream(prefix);
            System.setOut(temporarySystemOut);
        }
    }
    
    /**
     * Redirects all output sent to <code>System.err</code> to ImageJ's log console.
     * @param prefix The prefix string inserted at the start of each output line. 
     * Pass <code>null</code>  to use the default prefix or an empty string to 
     * remove the prefix.
     */
    public static void redirectSystemErr(String prefix) {
        if (originalSystemErr == null) {        // has no effect if System.out is already replaced
            originalSystemErr = System.err;     // remember the original System.out stream
            temporarySystemErr = new LogStream(prefix);
            System.setErr(temporarySystemErr);
        }
    }
    
    /**
     * Returns the redirection stream for {@code System.out} if it exists.
     * Note that a reference to the current output stream can also be obtained directly from 
     * the {@code System.out} field.
     * @return A reference to the {@code PrintStream} object currently substituting {@code System.out}
     * or {@code null} of if {@code System.out} is currently not redirected.
     */
    public static PrintStream getCurrentOutStream() {
        return temporarySystemOut;
    }
    
    /**
     * Returns the redirection stream for {@code System.err} if it exists.
     * Note that a reference to the current output stream can also be obtained directly from 
     * the {@code System.err} field.
     * @return A reference to the {@code PrintStream} object currently substituting {@code System.err}
     * or {@code null} of if {@code System.err} is currently not redirected.
     */
    public static PrintStream getCurrentErrStream() {
        return temporarySystemErr;
    }
    
    /**
     * Use this method to revert both <code>System.out</code> and <code>System.err</code> 
     * to their original output streams.
     */
    public static void revertSystem() {
        revertSystemOut();
        revertSystemErr();
    }

    /**
     * Use this method to revert<code>System.out</code>
     * to the original output stream.
     */
    public static void revertSystemOut() {
        if (originalSystemOut != null && temporarySystemOut != null) {
            temporarySystemOut.flush();
            temporarySystemOut.close();
            System.setOut(originalSystemOut);
            originalSystemOut = null;
            temporarySystemOut = null;
        }
    }
    
    /**
     * Use this method to revert<code>System.err</code>
     * to the original output stream.
     */
    public static void revertSystemErr() {
        if (originalSystemErr != null && temporarySystemErr != null) {
            temporarySystemErr.flush();
            temporarySystemErr.close();
            System.setErr(originalSystemErr);
            originalSystemErr = null;
            temporarySystemErr = null;
        }
    }
    
    // ----------------------------------------------------------------
    
    private final String endOfLineSystem = System.getProperty("line.separator"); 
    private final String endOfLineShort = String.format("\n");  
    private final ByteArrayOutputStream byteStream;
    private final String prefix;
    
    public LogStream() {
        super(new ByteArrayOutputStream());
        this.byteStream = (ByteArrayOutputStream) this.out;
        this.prefix = "";
    }

    private LogStream(String prefix) {
        super(new ByteArrayOutputStream());
        this.byteStream = (ByteArrayOutputStream) this.out;
        this.prefix = (prefix == null) ? "" : prefix;
    }
    
    @Override
    // ever called?
    public void write(byte[] b) {
        this.write(b, 0, b.length);
    }
    
    @Override
    public void write(byte[] b, int off, int len) {
        String msg = new String(b, off, len);
        if (msg.equals(endOfLineSystem) || msg.equals(endOfLineShort)) { // this is a newline sequence only
            ejectBuffer();
        } else {
            byteStream.write(b, off, len);  // append message to buffer
            if (msg.endsWith(endOfLineSystem) || msg.endsWith(endOfLineShort)) { // line terminated by Newline
                // note that this does not seem to happen ever (even with format)!?
                ejectBuffer();
            }
        }
    }
    
    @Override
    // ever called?
    public void write(int b) {
        byteStream.write(b);
    }

    @Override
    public void flush() {
        if (byteStream.size() > 0) {
            String msg = byteStream.toString();
            if (msg.endsWith(endOfLineSystem) || msg.endsWith(endOfLineShort))
                ejectBuffer();
        }
        super.flush();
    }
    
    @Override
    public void close() {
        super.close();
    }
    
    private void ejectBuffer() {
        IJ.log(prefix + byteStream.toString());
        byteStream.reset();
    }
    
}