package com.atlassian.bitbucket.mesh.execution;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Value that is resolved in the current {@link ExecutionContext}, if one is active. Conceptually similar to a
 * {@link ThreadLocal}, but tied to the current {@link ExecutionContext} instead of the current thread.
 * <p>
 * Note: {@code null} values are <em>NOT</em> supported because it's hard to distinguish between absent values and
 * {@code null} values.
 *
 * @param <T> the value type
 */
public interface ExecutionLocal<T> {

    /**
     * @throws IllegalStateException if no {@link ExecutionContext} is currently active.
     */
    void checkValid();

    /**
     * Attempts to compute the value given the current value (or {@code null} if there is no current value).
     * The provided {@code mappingFunction} must provide a non-null result when invoked. The value returned by the
     * {@code mappingFunction} is stored as the value for current {@link ExecutionContext}.
     * <p>
     * If there is no current {@link ExecutionContext}, the {@code mappingFunction} is not invoked and {@code null} is
     * returned.
     *
     * @param mappingFunction function that computes the value given the current value, or {@code null} if no value
     *                        is present.
     * @return the computed value, or {@code null} if no {@link ExecutionContext} is active
     */
    @Nullable
    T compute(@Nonnull Function<? super T, ? extends T> mappingFunction);

    /**
     * Returns the value, if present. If an {@link ExecutionContext} is active, but no value is set, the value supplied
     * by the {@code missingValueSupplier} is stored and returned.
     * <p>
     * If no {@link ExecutionContext} is active, {@code null} is returned.
     *
     * @param missingValueSupplier supplier of the value when
     * @return the value, possibly supplied by {@code missingValueSupplier}, or {@code null} if no
     *         {@link ExecutionContext} is active
     * @throws NullPointerException if {@code missingValueSupplier} is {@code null} or returns {@code null}
     */
    @Nullable
    T computeIfAbsent(@Nonnull Supplier<? extends T> missingValueSupplier);

    /**
     * @return the value, or {@code null} if not set or no {@link ExecutionContext} is active
     */
    @Nullable
    T get();

    /**
     * @return {@code true} if an {@link ExecutionContext} is active
     */
    boolean isActive();

    /**
     * If a value is present, invoke the specified consumer with the value, otherwise do nothing.
     *
     * @param consumer block to be executed if a value is present
     * @return {@code true} if a value was {@link #isPresent() present} and therefore the consumer was invoked
     */
    boolean ifPresent(@Nonnull Consumer<? super T> consumer);

    /**
     * @return {@code true} when a (possibly {@code null}) value is defined in the current {@link ExecutionContext}.
     */
    boolean isPresent();

    /**
     * Removes the value from the current {@link ExecutionContext}, if present.
     *
     * @return the removed value, if the value was removed; otherwise {@code null}
     */
    @Nullable
    T remove();

    /**
     * Removes the value from the current {@link ExecutionContext}, if a value present and the {@code predicate}
     * matches the value. If no value is present, the predicate will not be called. The Predicate will never be called
     * with a {@code null} value.
     *
     * @param predicate the predicate
     * @return the removed value, if the value was removed; otherwise {@code null}
     */
    @Nullable
    T removeIf(@Nonnull Predicate<? super T> predicate);

    /**
     * Sets the value
     *
     * @param value the value
     * @return {@code true} if the value was set, or {@code false} if no {@link ExecutionContext} is active
     */
    boolean set(@Nonnull T value);
}
