package com.atlassian.bitbucket.mesh.process;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;

/**
 * Describes a handler for processing {@code stdin}, {@code stdout} and {@code stderr} for a process.
 * <p>
 * {@code ProcessStdioHandler}s can optionally implement {@link ProcessCompletionHandler} to manage their own
 * completion handling, rather than receiving external completion handling. This can be useful for handlers
 * that "know" their completion has been handled already, for example, to opt out of additional handling.
 *
 * @param <T> the type returned by {@link #getResult()}
 * @see ProcessCompletionHandler
 */
public interface ProcessStdioHandler<T> {

    /**
     * Retrieves the result of handling the process's output, which may be {@code null}.
     * <p>
     * This method is called at most once. If other callbacks, like {@link #onStdout}, throw exceptions, or if
     * the {@link ProcessCompletionHandler} throws an exception, this method may never be called.
     *
     * @return the processed result, or {@code null}
     */
    @Nullable
    T getResult();

    /**
     * Called when the underlying process exits.
     * <p>
     * In general, implementations shouldn't rely on the exit code being an actual process exit code. After some
     * types of failure, the provided value may be a marker instead. At best, it's <i>indicative</i> of how the
     * process exited.
     *
     * @param exitCode the process's exit code, <i>or a marker value in some cases</i>
     */
    default void onExit(int exitCode) {
    }

    /**
     * Called before any attempt to start the process at the operating system level.
     * <p>
     * Most handlers should not need to implement this method and should prefer {@link #onStart} instead, but
     * it can be useful for doing early initialization in some cases.
     * <p>
     * <b>Warning</b>: Throwing an exception from {@code onPreStart} <i>will not</i> prevent the process from
     * being started, or subsequent callbacks (like {@link #onStart}) from happening; the exception will be
     * <i>logged and ignored</i>. It is not possible for handlers to prevent starting processes.
     *
     * @param process the process about to be started
     */
    default void onPreStart(@Nonnull OsProcess process) {
    }

    /**
     * Called after the process has successfully been started at the operating system level. For input handlers,
     * {@code onStart} is a good place to call {@link OsProcess#wantWrite} to signal a desire to write.
     * <p>
     * <b>Note</b>: Exceptions are not (generally) thrown when processes fail to start; setting a flag when
     * {@code onStart} is called and checking for it in {@link #onExit} can be useful for detecting failures to
     * start (for any handlers which need to know).
     *
     * @param process the process that has been started
     */
    default void onStart(@Nonnull OsProcess process) {
    }

    /**
     * Handlers that consume {@code stderr} should override {@code onStderr} and consume the provided buffer.
     * If this method is not overridden, {@code stderr} is not consumed <i>by the handler</i> and is instead
     * buffered and provided to the {@link ProcessCompletionHandler}.
     * <p>
     * <b>Note</b>: If {@code closed} is {@code true}, this will be the last {@code onStderr} call <i>even if
     * the handler does not exhaust the buffer</i>. Any bytes left in the buffer will be discarded.
     *
     * @param buffer a buffer containing {@code stderr} bytes
     * @param closed {@code true} to indicate the process has closed its {@code stderr} pipe; otherwise,
     *               {@code false} to indicate the pipe is still open
     */
    default void onStderr(@Nonnull ByteBuffer buffer, boolean closed) {
        //If the implementation doesn't handle stderr, don't consume it here. This will "defer" it to
        //StdioNuProcessHandler, which will buffer it and provide it to the ProcessCompletionHandler
    }

    /**
     * Handlers that provide {@code stdin} should override {@code onStdinReady} and write their desired data to
     * the buffer--which may require splitting the data across multiple callbacks. On the <i>final callback</i>,
     * where the last bytes are written to the buffer, the handler should call {@link OsProcess#closeStdin}
     * <i>in addition to</i> returning {@code false} to "tell" the process that no further {@code stdin} will be
     * provided. Any data in the final buffer will be written to {@code stdin} before it's closed.
     * <p>
     * <b>Note</b>: Simply returning {@code false} is <i>not enough</i>, when dealing with processes that are
     * waiting for {@code stdin} to be closed. Returning {@code false} only indicates that the handler has no
     * additional {@code stdin} to provide <i>right now</i>; {@link OsProcess#wantWrite} can be used to
     * trigger more callbacks later.
     * <p>
     * <b>Warning</b>: After writing to the provided buffer, <i>it must be flipped</i> before returning. If the
     * buffer is not flipped, garbage input will be provided to the process.
     *
     * @param buffer a buffer to write {@code stdin} bytes to, which will be forwarded to the process
     * @return {@code true} to indicate more data is available, which did not fit in the buffer; otherwise,
     *         {@code false} to indicate there's no more data available <i>right now</i>--to finish writing
     *         to {@code stdin}, call {@link OsProcess#closeStdin} before returning {@code false}
     */
    boolean onStdinReady(@Nonnull ByteBuffer buffer);

    /**
     * Implemented by handlers to process {@code stdout}. Any bytes left in the buffer when the callback returns
     * will be provided again on the next callback.
     * <p>
     * <b>Note</b>: If {@code closed} is {@code true}, this will be the last {@code onStdout} call <i>even if
     * the handler does not exhaust the buffer</i>. Any bytes left in the buffer will be discarded.
     * <p>
     * <b>Warning</b>: If the provided buffer is full, handlers <i>must</i> consume at least one byte from the
     * buffer and ensure there is space when they return. If the buffer is returned full, the underlying process
     * library will <i>immediately abandon</i> the process and fail.
     *
     * @param buffer a buffer containing {@code stdout} bytes
     * @param closed {@code true} to indicate the process has closed its {@code stdout} pipe; otherwise,
     *               {@code false} to indicate the pipe is still open
     */
    void onStdout(@Nonnull ByteBuffer buffer, boolean closed);
}
