/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.io;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import static net.ranides.assira.io.MultiOutputStream.tee;
import net.ranides.assira.text.TextOutputStream;

/**
 * Strumień zapisujący dane do więcej niż jednego celu jednocześnie.
 * @author ranides
 */
public class MultiOutputStream extends OutputStream {
    
    private final OutputStream[] streams;

    /**
     * Tworzy nowy obiekt, który przekazuje dane do wszystkich strumieni podanych
     * jako argumenty.
     * @param streams
     */
    public MultiOutputStream(OutputStream... streams) {
        this.streams = streams;
    }
    
    public static <T extends OutputStream> T tee(T observer) {
        System.setOut(new PrintStream(new MultiOutputStream(System.out, observer)));
        return observer;
    }
    
    public static TextOutputStream tee() {
        return tee(new TextOutputStream());
    }
    
    @Override
    public void write(int data) throws IOException {
        List<IOException> errors = null;
        for(OutputStream stream : streams) {
            try {
                stream.write(data);
            } catch(IOException | RuntimeException cause) {
                errors = addErrors(errors, stream, cause);
            }
        }
        throwErrors(errors);

    }

    @Override
    public void write(byte[] data) throws IOException {
        List<IOException> errors = null;
        for(OutputStream stream : streams) {
            try {
                stream.write(data);
            } catch(IOException | RuntimeException cause) {
                errors = addErrors(errors, stream, cause);
            }
        }
        throwErrors(errors);
    }

    @Override
    public void write(byte[] data, int offset, int length) throws IOException {
        List<IOException> errors = null;
        for(OutputStream stream : streams) {
            try {
                stream.write(data, offset, length);
            } catch(IOException | RuntimeException cause) {
                errors = addErrors(errors, stream, cause);
            }
        }
        throwErrors(errors);
    }

    @Override
    public void close() throws IOException {
        List<IOException> errors = null;
        for(OutputStream stream : streams) {
            try {
                stream.close();
            } catch(IOException | RuntimeException cause) {
                errors = addErrors(errors, stream, cause);
            }
        }
        throwErrors(errors);
    }

    @Override
    public void flush() throws IOException {
        List<IOException> errors = null;
        for(OutputStream stream : streams) {
            try {
                stream.flush();
            } catch(IOException | RuntimeException cause) {
                errors = addErrors(errors, stream, cause);
            }
        }
        throwErrors(errors);
    }
    
    private static List<IOException> addErrors(List<IOException> list, OutputStream stream, Throwable cause) throws IOException {
        List<IOException> result = 
            (null != list) ? list : new LinkedList<IOException>();
        
        result.add(new IOException("Error in slave stream: " + stream, cause));
        return result;
    }
    
    private static void throwErrors(List<IOException> list) throws IOException {
        if(list != null) {
            throw new IOExceptions("exception caused by one or more slave stream", list);
        }
    }
    
    
}
