package org.implab.gradle.common.exec;

import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ProcessSpec {
    private final ProcessBuilder builder;

    private RedirectFrom inputRedirect;

    private RedirectTo outputRedirect;

    private RedirectTo errorRedirect;

    private List<String> command = new ArrayList<>();

    private File directory;

    public ProcessSpec() {
        builder = new ProcessBuilder();
    }

    public CompletableFuture<Integer> start() throws IOException {
        var tasks = new ArrayList<CompletableFuture<?>>();

        builder.command(command);
        builder.directory(directory);

        // discard stdout if not redirected
        if (outputRedirect == null)
            builder.redirectOutput(Redirect.DISCARD);

        // discard stderr if not redirected
        if (errorRedirect == null)
            builder.redirectError(Redirect.DISCARD);

        // run process
        var proc = builder.start();

        tasks.add(proc.onExit());

        if (inputRedirect != null)
            tasks.add(inputRedirect.redirect(proc.getOutputStream()));

        if (outputRedirect != null)
            tasks.add(outputRedirect.redirect(proc.getInputStream()));

        if (errorRedirect != null)
            tasks.add(errorRedirect.redirect(proc.getErrorStream()));

        return CompletableFuture
                .allOf(tasks.toArray(new CompletableFuture<?>[0]))
                .thenApply(t -> proc.exitValue());
    }

    public Integer exec() throws InterruptedException, ExecutionException, IOException {
        return start().get();
    }

    public List<String> command() {
        return command;
    }

    public ProcessSpec command(String... args) {
        command = new ArrayList<>();
        args(args);
        return this;
    }

    public ProcessSpec directory(File workingDir) {
        directory = workingDir;
        return this;
    }

    public ProcessSpec command(Collection<String> args) {
        command = new ArrayList<>();
        args(args);
        return this;
    }

    public ProcessSpec args(String... args) {
        for (String arg : args)
            command.add(arg);
        return this;
    }

    public ProcessSpec args(Collection<String> args) {
        command.addAll(args);
        return this;
    }

    public ProcessSpec stderr(RedirectTo to) {
        errorRedirect = to;
        return this;
    }

    public ProcessSpec stdin(RedirectFrom from) {
        inputRedirect = from;
        return this;
    }

    public ProcessSpec stdout(RedirectTo to) {
        outputRedirect = to;
        return this;
    }
}
