/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.jini.phoenix;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.Date;

/**
 * PipeWriter plugs together two pairs of input and output streams by
 * providing readers for input streams and writing through to
 * appropriate output streams.  Both output streams are annotated on a
 * per-line basis.
 *
 * 
 * 
 * @since 2.0
 */
class PipeWriter implements Runnable {

    /** stream used for buffering lines */
    private final ByteArrayOutputStream bufOut;

    /** count since last separator */
    private int cLast;  
    
    /** current chunk of input being compared to lineSeparator.*/
    private final byte[] currSep;
    
    private final PrintWriter out;
    private final InputStream in;
    
    private final String execString;
    private final Object[] date = new Object[]{ new Date() };
    
    private static final String lineSeparator =
	System.getProperty("line.separator");
    private static final int lineSeparatorLength = lineSeparator.length();
    private static final String format = "{0,date} {0,time}";
    private static final MessageFormat formatter = new MessageFormat(format);

    /**
     * Create a new PipeWriter object. All methods of PipeWriter,
     * except plugTogetherPair, are only accessible to PipeWriter
     * itself.  Synchronization is unnecessary on functions that will
     * only be used internally in PipeWriter.
     *
     * @param in input stream from which pipe input flows
     * @param out output stream to which log messages will be sent
     * @param name name of the process
     * @param tag name of the stream (out or err)
     */
    private PipeWriter (InputStream in,
			OutputStream out,
			String name,
			String tag)
    {

	this.in = in;
	this.out = new PrintWriter(out);
	
	bufOut = new ByteArrayOutputStream();
	currSep = new byte[lineSeparatorLength];
	
	/* set unique pipe/pair annotations */
	execString = ":" + name + ':' + tag + ':';
    }
    
    /**
     * Create a thread to listen and read from input stream, in.  buffer
     * the data that is read until a marker which equals lineSeparator
     * is read.  Once such a string has been discovered; write out an
     * annotation string followed by the buffered data and a line
     * separator.  
     */
    public void run() {
	byte[] buf = new byte[256];
	int count;
	
	try {
	    /* read bytes till there are no more. */
	    while ((count = in.read(buf)) != -1) {
		write(buf, 0, count);
	    }

	    /*  flush internal buffer... may not have ended on a line
             *  separator, we also need a last annotation if 
             *  something was left.
	     */
	    String lastInBuffer = bufOut.toString();
	    bufOut.reset();
	    if (lastInBuffer.length() > 0) {
		out.println (createAnnotation() + lastInBuffer);    
		out.flush();                    // add a line separator
                                                // to make output nicer
	    }

	} catch (IOException e) {
	}
    }
    
    /**
     * Write a subarray of bytes.  Pass each through write byte method.
     */
    private void write(byte b[], int off, int len) throws IOException {

	if (len < 0) {
	    throw new ArrayIndexOutOfBoundsException(len);
	}
	for (int i = 0; i < len; ++ i) {
	    write(b[off + i]);
	}
    }

    /**
     * Write a byte of data to the stream.  If we have not matched a
     * line separator string, then the byte is appended to the internal
     * buffer.  If we have matched a line separator, then the currently
     * buffered line is sent to the output writer with a prepended 
     * annotation string.
     */
    private void write(byte b) throws IOException {
	int i = 0;
	
	/* shift current to the left */
	for (i = 1 ; i < (currSep.length); i ++) {
	    currSep[i-1] = currSep[i];
	}
	currSep[i-1] = b;
	bufOut.write(b);
	
	/* enough characters for a separator? */
	if ( (cLast >= (lineSeparatorLength - 1)) &&
	     (lineSeparator.equals(new String(currSep))) )
	{
	    
	    cLast = 0;
	    
	    /* write prefix through to underlying byte stream */
	    out.print(createAnnotation() + bufOut.toString());
	    out.flush();
	    bufOut.reset();
	    
	    if (out.checkError()) {
		throw new IOException
		    ("PipeWriter: IO Exception when"+
		     " writing to output stream.");
	    }
	    
	} else {
	    cLast++;
	}
    }
    
    /** 
     * Create an annotation string to be printed out after
     * a new line and end of stream.
     */
    private String createAnnotation() {
	
	/* construct prefix for log messages:
	 * date/time stamp...
	 */
	StringBuffer text = new StringBuffer();
	((Date) date[0]).setTime(System.currentTimeMillis());
	formatter.format(date, text, null);
	text.append(execString);
	return text.toString();
    }

    /**
     * Allow plugging together two pipes at a time, to associate
     * output from an exec'ed process.
     *
     * @param name name of the process
     * @param in input stream from which pipe input comes
     * @param out output stream to which log messages will be sent
     * @param in1 input stream from which pipe input comes
     * @param out1 output stream to which log messages will be sent
     */
    static void plugTogetherPair(String name,
				 InputStream in,
				 OutputStream out,
				 InputStream in1,
				 OutputStream out1)
    {
	/* start threads to read output from child process */
	Thread outThread = new Thread(new PipeWriter(in, out, name, "out"),
				      name + "-out");
	outThread.setDaemon(true);
	outThread.start();
	Thread errThread = new Thread(new PipeWriter(in1, out1, name, "err"),
				      name + "-err");
	errThread.setDaemon(true);
	errThread.start();
    }
}
