package com.atlassian.bitbucket.mesh.execution;

import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;

public interface ExecutionManager {

    /**
     * @return the currently active {@link ExecutionContext} if any, otherwise {@link Optional#empty()}
     */
    @Nonnull
    Optional<ExecutionContext> getContext();

    /**
     * @param contextId the ID of the context to retrieve
     * @return the {@link ExecutionContext} with the provided ID, if active; otherwise {@link Optional#empty()}
     */
    @Nonnull
    Optional<ExecutionContext> getContext(@Nonnull String contextId);

    /**
     * @return the currently active {@link ExecutionContext}
     * @throws IllegalStateException if no execution context is currently active
     */
    @Nonnull
    ExecutionContext getContextOrThrow();

    /**
     * Wraps a {@link Callable} so that it executes with the active {@link ExecutionContext} as the
     * {@link ExecutionManager#getContext() current} execution context, if a context is currently active.
     *
     * @param callable the callable to wrap
     * @param <T> the type that the callable returns
     * @return a proxy that sets up the currently active {@link ExecutionContext} around each method invocation, or the
     *         original {@code callable} if no context is currently active.
     */
    @Nonnull
    <T> Callable<T> maybeWrap(@Nonnull Callable<T> callable);

    /**
     * Wraps a {@link Runnable} so that it executes with the active {@link ExecutionContext} as the
     * {@link ExecutionManager#getContext() current} execution context, if a context is currently active.
     *
     * @param runnable the {@link Runnable} to wrap
     * @return a proxy that sets up the currently active {@link ExecutionContext} around each method invocation, or the
     *         original {@code runnable} if no context is currently active.
     */
    @Nonnull
    Runnable maybeWrap(@Nonnull Runnable runnable);

    /**
     * Creates a new {@link ExecutionLocal} to get/set values in the <em>current</em> {@link ExecutionContext}.
     * Conceptually similar to a {@link ThreadLocal}, but tied to the current {@link ExecutionContext} instead of
     * the current thread.
     *
     * @param <T> the value type
     * @return the {@link ExecutionLocal}
     */
    @Nonnull
    <T> ExecutionLocal<T> newExecutionLocal();

    /**
     * Creates a new {@link ExecutionLocal} to get/set values in the <em>current</em> {@link ExecutionContext}.
     * Conceptually similar to a {@link ThreadLocal}, but tied to the current {@link ExecutionContext} instead of
     * the current thread.
     *
     * @param cleanupListener a listener that is invoked when an {@link ExecutionContext} has completed and all values
     *                        in the context are removed. The {@code cleanupListener} is NOT called if no value has been
     *                        defined for the {@link ExecutionContext}.
     * @param <T> the value type
     * @return the {@link ExecutionLocal}
     */
    @Nonnull
    <T> ExecutionLocal<T> newExecutionLocal(@Nonnull Consumer<T> cleanupListener);

    /**
     * Sets up and tears down an {@link ExecutionContext} that can be used to manage state for the current
     * execution scope. If a gRPC request is active, the {@link ExecutionContext} will be torn down when the gRPC
     * request is canceled or completes. If no gRPC request is active, the context will be torn down after
     * {@link ExecutionCallback#callWithContext(ExecutionContext)} completes.
     * <p>
     * <b>Note</b>: The nullability of this method is intentionally not defined because it is up to the callback
     * implementation. If the provided callback can return {@code null}, so can this method. Otherwise, if the
     * callback always returns a value, this method will to.
     *
     * @param callback called with the newly created {@link ExecutionContext}
     * @param <T> type of the callback return value
     * @return the return value of the callback
     */
    <T> T withContext(@Nonnull ExecutionCallback<T> callback);
}
