package com.atlassian.bitbucket.mesh.process.nu;

import com.atlassian.bitbucket.mesh.process.ProcessParameters;
import com.atlassian.bitbucket.mesh.process.ProcessStdioHandler;
import com.zaxxer.nuprocess.NuProcess;
import com.zaxxer.nuprocess.NuProcessBuilder;

import javax.annotation.Nonnull;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;

/**
 * Extends {@link StdioNuProcessHandler} and applies latching that allows a caller to wait for the process to either
 * be started or fail to start.
 * <p>
 * When using NuProcess's pump threads, {@link NuProcessBuilder#start}, doesn't return until either {@link #onExit}
 * or {@link #onStart} has been called. (If the process fails to start, {@link #onExit} will be called with a marker
 * exit code to signal that starting failed.) This handler allows {@link NuProcessCreator#start} to delay returning
 * its {@code Future} until the same point in the new process's lifecycle.
 *
 * @param <T> the type of result returned by the {@link ProcessStdioHandler}
 */
class LatchedStdioNuProcessHandler<T> extends StdioNuProcessHandler<T> {

    private final CountDownLatch latch;

    LatchedStdioNuProcessHandler(ProcessParameters<T> parameters, ScheduledExecutorService timeoutExecutor) {
        super(parameters, timeoutExecutor);

        latch = new CountDownLatch(1);
    }

    @Override
    public void onExit(int exitCode) {
        try {
            super.onExit(exitCode);
        } finally {
            //If the process fails to start, allow the starting thread to continue. If this is
            //called after onStart has been called, this countDown() will do nothing
            latch.countDown();
        }
    }

    @Override
    public void onStart(NuProcess nuProcess) {
        try {
            super.onStart(nuProcess);
        } finally {
            //Once the process starts, allow the starting thread to continue
            latch.countDown();
        }
    }

    /**
     * Waits until either the process is successfully started, or the system determines the process can't be started,
     * and then returns a {@link Future} which can be used to interact with the process.
     * <p>
     * This wait allows the starting thread to continue at the same point it would be able to continue if the process
     * was being pumped using a NuProcess thread.
     */
    @Nonnull
    @Override
    Future<T> asFuture() {
        try {
            latch.await();
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }

        return super.asFuture();
    }
}
