/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ExecProcess.java
 * Written by Jonathan Gainsley.
 *
 * Copyright (c) 2005, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

import java.io.*;

/**
 * Runtime.exec() has many pitfalls to it's proper use.  This class
 * wraps external executes to make it easier to use.
 */
class ExecProcess extends Thread {

    /**
     * This class is used to read data from an external process.
     * If something does not consume the data, it will fill up the default
     * buffer and deadlock.  This class also redirects data read
     * from the process (the process' output) to another stream,
     * if specified.
     */
    public static class ExecProcessReader extends Thread {

        private InputStream in;
        private OutputStream redirect;

        /**
         * Create a stream reader that will read from the stream
         * @param in the input stream
         */
        public ExecProcessReader(InputStream in) {
            this(in, null);
            setDaemon(true);
        }

        /**
         * Create a stream reader that will read from the stream, and
         * store the read text into buffer.
         * @param in the input stream
         * @param redirect read text is redirected to this
         */
        public ExecProcessReader(InputStream in, OutputStream redirect) {
            this.in = in;
            this.redirect = redirect;
            setName("ExecProcessReader");
        }

        public void run() {
            try {
                PrintWriter pw = null;
                if (redirect != null) pw = new PrintWriter(redirect);

                // read from stream
                InputStreamReader input = new InputStreamReader(in);
                BufferedReader reader = new BufferedReader(input);
//                String line = null;
                char [] cbuf = new char[256];
                int read = 0;
                while ((read = reader.read(cbuf, 0, 256)) > -1) {
                    if (pw != null) {
                        pw.write(cbuf, 0, read);
                        pw.flush();
                    }
                }

                reader.close();
                input.close();

            } catch (java.io.IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }

//    private final String command;
    private final String [] exec;
    private final String [] envVars;
    private final File dir;                       // working directory
    private final OutputStream outStreamRedir;    // output of process redirected to this stream
    private final OutputStream errStreamRedir;    // error messages of process redirected to this stream
    private PrintWriter processWriter;      // connect to input of process
    private ExecProcessReader outReader;
    private ExecProcessReader errReader;
    private Process p = null;

//    /**
//     * Execute an external process.
//     * Note: command is not a shell command line command, it is a single program and arguments.
//     * Therefore, <code>/bin/sh -c /bin/ls > file.txt</code> will NOT work.
//     * <p>
//     * Instead, use String[] exec = {"/bin/sh", "-c", "/bin/ls > file.txt"};
//     * and use the other constructor.
//     * @param command the command to run.
//     * @param envVars environment variables of the form name=value. If null, inherits vars from current process.
//     * @param dir the working directory. If null, uses the working dir from the current process
//     * @param outStreamRedir stdout of the process will be redirected to this stream if not null
//     * @param errStreamRedir stderr of the process will be redirected to this stream if not null
//     */
//    public ExecProcess(String command, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
//        this.command = command;
//        this.exec = null;
//        this.envVars = envVars;
//        this.dir = dir;
//        this.outStreamRedir = outStreamRedir;
//        this.errStreamRedir = errStreamRedir;
//        this.processWriter = null;
//        setName(command);
//    }

    /**
     * Execute an external process.
     * Note: this is not a command-line command, it is a single program and arguments.
     * @param exec the executable and arguments of the process
     * @param envVars environment variables of the form name=value. If null, inherits vars from current process.
     * @param dir the working directory. If null, uses the working dir from the current process
     * @param outStreamRedir stdout of the process will be redirected to this stream if not null
     * @param errStreamRedir stderr of the process will be redirected to this stream if not null
     */
    public ExecProcess(String [] exec, String [] envVars, File dir, OutputStream outStreamRedir, OutputStream errStreamRedir) {
//        this.command = null;
        this.exec = exec;
        this.envVars = envVars;
        this.dir = dir;
        this.outStreamRedir = outStreamRedir;
        this.errStreamRedir = errStreamRedir;
        this.processWriter = null;
        setName(exec[0]);
    }

    public void run() {
        try {
            Runtime rt = Runtime.getRuntime();

            outReader = null;
            errReader = null;

            // run program
            synchronized(this) {
//                if (command != null)
//                    p = rt.exec(command, envVars, dir);
//                else
                    p = rt.exec(exec, envVars, dir);

                // eat output (stdout) and stderr from program so it doesn't block
                outReader = new ExecProcessReader(p.getInputStream(), outStreamRedir);
                errReader = new ExecProcessReader(p.getErrorStream(), errStreamRedir);
                outReader.start();
                errReader.start();

                // attach to input of process
                BufferedOutputStream bufout = new BufferedOutputStream(p.getOutputStream());
                processWriter = new PrintWriter(bufout);
            }

            // wait for exit status
            int exitVal = p.waitFor();

            // also wait for redir threads to die, if doing redir
            if (outStreamRedir != null) outReader.join();
            if (errStreamRedir != null) errReader.join();

            StringBuffer com = new StringBuffer();
//            if (command != null)
//                com.append(command);
//            else
            {
                for (int i=0; i<exec.length; i++)
                    com.append(exec[i]+" ");
            }

            System.out.println("Process finished [exit: "+exitVal+"]: "+com.toString());

            synchronized(this) {
                if (processWriter != null) {
                    processWriter.close();
                    processWriter = null;
                }
            }

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * Send a line of text to the process. This is not useful
     * if the process is not expecting any input.
     * @param line a line of text to send to the process
     */
    public void writeln(String line) {
        synchronized(this) {
            if (processWriter == null) {
                System.out.println("No process to write to: "+line);
                return;
            }
            processWriter.println(line);
            processWriter.flush();
        }
    }

    /**
     * End this process, if it is running. Otherwise, does nothing
     */
    public synchronized void destroyProcess() {
        if (p != null) {
            p.destroy();
        }
    }

}
