package org.implab.gradle.common.tasks;

import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

import org.gradle.api.DefaultTask;
import org.implab.gradle.common.dsl.ProcessSpec;
import org.implab.gradle.common.exec.Executor;
import org.implab.gradle.common.exec.RedirectFrom;
import org.implab.gradle.common.exec.RedirectTo;

public abstract class ExternalTask extends DefaultTask {

    /**
     * A default redirection to the build log when loglevel is set to INFO,
     * otherwise returns an empty redirection.
     */
    protected Optional<RedirectTo> loggerInfoRedirect() {
        return getLogger().isInfoEnabled()
                ? Optional.of(RedirectTo.consumer(getLogger()::info))
                : Optional.empty();
    }

    /**
     * A default redirection to the build log when loglevel is set to ERROR,
     * otherwise returns an empty redirection. Note that ERROR level is set
     * by default for a build runs.
     */
    protected Optional<RedirectTo> loggerErrorRedirect() {
        return getLogger().isErrorEnabled()
                ? Optional.of(RedirectTo.consumer(getLogger()::error))
                : Optional.empty();
    }

    /**
     * Stdout redirection for {@link #exec(ProcessSpec)}, default implementation
     * will forward stdout to the build log through {@link #loggerErrorRedirect()}
     */
    protected Optional<RedirectTo> stdoutRedirection() {
        return loggerInfoRedirect();
    }

    /**
     * Stderr redirection for {@link #exec(ProcessSpec)}, default implementation
     * will forward stderr to build log through {@link #loggerErrorRedirect()}.
     */
    protected Optional<RedirectTo> stderrRedirection() {
        return loggerErrorRedirect();
    }

    /**
     * Stdin redirection for {@link #exec(ProcessSpec)}, empty by default.
     */
    protected Optional<RedirectFrom> stdinRedirection() {
        return Optional.empty();
    }

    /**
     * Executes the specified process specification
     * 
     * @param spec
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws IOException
     */
    protected void exec(ProcessSpec spec) throws InterruptedException, ExecutionException, IOException {
        var executor = exec();

        stdoutRedirection().ifPresent(executor::stdout);
        stderrRedirection().ifPresent(executor::stderr);
        stdinRedirection().ifPresent(executor::stdin);

        getLogger().info("Staring: {}", spec.command());

        spec.accept(executor);

        // runs the command and checks the error code
        var code = executor.start().get();

        // check success code
        if (code != 0)
            throw new IOException("The process exited with error code " + code);
    }

    protected boolean checkRetCode(ProcessSpec proc, int code)
            throws InterruptedException, ExecutionException, IOException {

        var executor = exec();

        if (getLogger().isInfoEnabled()) {
            loggerInfoRedirect().ifPresent(executor::stdout);
            loggerInfoRedirect().ifPresent(executor::stderr);
        }

        getLogger().info("Starting: {}", proc.command());

        proc.accept(executor);

        return executor.start().get() == code;
    }

    protected abstract Executor exec();
}
