001package com.studentgui.apphelpers;
002
003import java.io.BufferedReader;
004import java.io.InputStreamReader;
005import java.nio.file.Path;
006import java.util.function.Consumer;
007
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Helper to invoke the repository's Python plot runner asynchronously.
013 * <p>
014 * This wrapper launches the repository's Python runner script in a
015 * background thread and collects its combined output. It is used by some
016 * legacy pages; newer pages prefer the Java-based charting helpers.
017 * </p>
018 */
019public class PythonPlotter {
020    private static final Logger LOG = LoggerFactory.getLogger(PythonPlotter.class);
021
022    /**
023     * Run the python runner for the given module and student name in a background thread.
024     * The {@code onComplete} consumer receives combined stdout/stderr text when the
025     * process finishes.
026     *
027     * @param moduleName module identifier passed to the python runner (non-null)
028     * @param studentName student display name used by the plotter (non-null)
029     * @param onComplete optional consumer receiving process output when complete; may be null
030     */
031    public static void runPlotAsync(final String moduleName, final String studentName, final Consumer<String> onComplete) {
032        if (studentName == null || studentName.trim().isEmpty()) {
033            String msg = "No student selected for plot generation";
034            LOG.warn(msg);
035            if (onComplete != null) {
036                onComplete.accept(msg);
037            }
038            return;
039        }
040
041        Path script = Helpers.PROJECT_ROOT.resolve("appPages").resolve("run_plot.py");
042
043        Thread t = new Thread(() -> {
044            StringBuilder out = new StringBuilder();
045            try {
046                ProcessBuilder pb = new ProcessBuilder("python", script.toString(), moduleName, studentName);
047                pb.directory(Helpers.PROJECT_ROOT.toFile());
048                pb.redirectErrorStream(true);
049                Process p = pb.start();
050                try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
051                    String line;
052                    while ((line = r.readLine()) != null) {
053                        out.append(line).append(System.lineSeparator());
054                    }
055                }
056                int rc = p.waitFor();
057                out.append("Exit code: ").append(rc).append(System.lineSeparator());
058            } catch (java.io.IOException | InterruptedException e) {
059                LOG.error("Error running python plot runner", e);
060                out.append("Error: ").append(e.toString()).append(System.lineSeparator());
061                if (e instanceof InterruptedException) {
062                    Thread.currentThread().interrupt();
063                }
064            }
065            if (onComplete != null) {
066                try {
067                    onComplete.accept(out.toString());
068                } catch (Exception ex) {
069                    // Log and continue: we don't want a faulty consumer to terminate
070                    // the worker thread unexpectedly. Avoid catching Error/Throwable.
071                    LOG.warn("onComplete consumer threw an exception; continuing. Output length={}", out.length(), ex);
072                }
073            }
074    }, "PythonPlotter-" + moduleName);
075        t.setDaemon(true);
076        t.start();
077    }
078
079    /**
080     * Private constructor to prevent instantiation of this helper class.
081     */
082    private PythonPlotter() {
083        // utility only
084    }
085}