package de.duehl.basics.system.starter;

/*
 * Copyright 2024 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.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;

import de.duehl.basics.io.FineFileWriter;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse startet ein externes Programm ohne den Umweg über CMD und wartet auf die
 * Ausführung.
 *
 * @version 1.01     2024-11-26
 * @author Christian Dühl
 */

public class ProgramStarter {

    private static final boolean DEBUG = false;

    /** Das auszuführende Programm (mit Pfad). */
    private final String programBinary;

    /** Die Parameter die dem Programm übergeben werden. */
    private final List<String> parameters;

    /** Das Logfile, das geschrieben wird. */
    private final String logFilename;

    /** Der Extrakt der Fehler aus dem Logfile, wird ebenfalls geschrieben. */
    private final String errorLogfilename;

    /**
     * Konstruktor.
     *
     * @param programBinary
     *            Das auszuführende Programm (mit Pfad).
     * @param parameters
     *            Die Parameter die dem Programm übergeben werden.
     * @param logFilename
     *            Das Logfile, das geschrieben wird.
     * @param errorLogfilename
     *            Der Extrakt der Fehler aus dem Logfile, wird ebenfalls geschrieben.
     */
    public ProgramStarter(String programBinary, List<String> parameters, String logFilename,
            String errorLogfilename) {
        this.programBinary = programBinary;
        this.parameters = parameters;
        this.logFilename = logFilename;
        this.errorLogfilename = errorLogfilename;
    }

    /** Führt das Programm aus und wartet, bis es fertig ist. */
    public void runAndWait() {
        List<String> parameterList = createParameterList();
        String[] params = CollectionsHelper.stringListToArray(parameterList);
        ProcessBuilder processBuilder = new ProcessBuilder(params);
        Process process = startProcessBuilder(processBuilder);
        waitForProcessAndWriteStreams(process);
   }

    private List<String> createParameterList() {
        List<String> parameterList = new ArrayList<>();
        if (programBinary.contains(" ")) {
            parameterList.add("\"" + programBinary + "\"");
        }
        else {
            parameterList.add(programBinary);
        }
        parameterList.addAll(parameters);

        say("Parameterliste:");
        for (String parameter : parameterList) {
            say("    " + parameter);
        }
        return parameterList;
    }

    private Process startProcessBuilder(ProcessBuilder processBuilder) {
        try {
            return processBuilder.start();
        }
        catch (IOException exception) {
            throw new RuntimeException("Es trat ein Fehler bei der Ausführung des Programms auf.",
                    exception);
        }
    }

    /**
     * Wartet auf die Beendigung eines gestarteten Processes.
     *
     * @param process
     *            Der Gestarteter Prozess, auf den gewartet werden soll.
     */
    private void waitForProcessAndWriteStreams(Process process) {
        /* Auf Beendigung warten: */
        waitForProcess(process);

//        /*
//         * Falls der Process gewaltsam mit destroy() beendet wurde, ermitteln
//         * wir keine Log- und Fehlerdateien:
//         */
//        int exitValue = process.exitValue();
//        if (0 != exitValue) {
//            //return;
//        }

        writeOutputStream(process);
        writeErrorStream(process);
    }

    private void waitForProcess(Process process) {
        try {
            process.waitFor();
        }
        catch (InterruptedException e) {
            // nichts, das kann nicht unterbrochen werden.
        }
    }

    private void writeOutputStream(Process process) {
        writeStream(process.getInputStream(), logFilename, "Outputs");
    }

    private void writeErrorStream(Process process) {
        writeStream(process.getErrorStream(), errorLogfilename, "Fehler-Outputs");
    }

    private static void writeStream(InputStream stream, String filename, String streamDescription) {
        BufferedReader err = new BufferedReader(new InputStreamReader(stream));
        FineFileWriter errWriter = new FineFileWriter(filename);
        try {
            for (String line; (line = err.readLine()) != null;) {
                errWriter.writeln(line);
            }
        }
        catch (IOException exception) {
            throw new RuntimeException("Es trat ein Fehler bei der Ausführung des Programms beim "
                    + "Ermitteln des " + streamDescription + " auf.", exception);
        }
        errWriter.close();
    }

    private static void say(String message) {
        if (DEBUG) {
            Text.say(message);
        }
    }

}
