package de.duehl.basics.proccess;

/*
 * Copyright 2017 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.exceptions.IORuntimeException;

/**
 * Diese Klasse startet einen externen Prozess, wartet auf seine Terminierung und wertet ggf.
 * aufgetretene Fehler in System.err des Prozesses aus.
 *
 * @version 1.01     2017-04-21
 * @author Christian Dühl
 */

public class ExternalProcess {

    /** Parameter für den Prozess. */
    private final String[] parameters;

    /** Gestarteter Prozess. */
    private Process process;

    private boolean inheritIO = false;

    /**
     * Konstruktor.
     *
     * @param parameters
     *            Liste mit den Parametern für den Prozess.
     */
    public ExternalProcess(List<String> parameters) {
        this(CollectionsHelper.stringListToArray(parameters));
    }

    /**
     * Konstruktor.
     *
     * @param parameters
     *            Parameter für den Prozess.
     */
    public ExternalProcess(String ... parameters) {
        this.parameters = parameters;
    }

    public void inheritIO() {
        inheritIO = true;
    }

    /**
     * Erzeugt und startet den Prozess, wartet dann auf dessen Beendigung und wertet am Ende die
     * nach System.err geschriebenen Fehler aus.
     */
    public void runWaitAndHandleErrors() {
        start();
        waitForProcess();
        handleErrors();
    }

    /**
     * Erstellt einen Process aus den Aufrufparametern und startet ihn.
     *
     * @throws IOException
     *             Falls dabei ein Fehler auftritt.
     */
    private void start() {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(parameters);
            if (inheritIO) {
                processBuilder.inheritIO();
            }
            process = processBuilder.start();
        }
        catch (IOException exception) {
            throw new IORuntimeException(exception);
        }
    }

    /**
     * Wartet auf die Beendigung des Prozesses.
     *
     * @throws IOException
     *             Falls das Warten unterbrochen wird.
     */
    private void waitForProcess() {
        try {
            process.waitFor();
        }
        catch (InterruptedException exception) {
            throw new IORuntimeException("Das Warten auf den Process wurde unterbrochen. "
                    + "Parameter des Prozesses: " + CollectionsHelper.stringArrayToString(parameters),
                    exception);
        }
    }

    /**
     * Ermittelt die bei einem Prozess nach STDERR geschriebenen Fehler. Wurde dabei etwas
     * empfangen, wird eine Exception geworfen.
     *
     * @throws IORuntimeException
     *             Falls etwas beim Ermitteln der nach STDERR geschriebenen Fehler auftritt.
     * @throws RuntimeException
     *             Falls Fehler nach STDERR geschrieben wurden.
     */
    private void handleErrors() {
        BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));

        StringBuilder errBuilder = new StringBuilder();
        try {
            for (String line; (line = err.readLine()) != null;) {
                errBuilder.append(line + "\n");
            }
        }
        catch (IOException exception) {
            throw new IORuntimeException(exception);
        }

        if (errBuilder.length() > 0) {
            throw new RuntimeException("Fehler beim Ausführen des Programms.\n"
                    + "Parameter des Prozesses: " + CollectionsHelper.stringArrayToString(parameters)
                    + "\nAufgetretene Fehler:"
                    + "\n\t" + errBuilder.toString());
        }
    }

}
