/*
 * Adapted from The MIT License (MIT)
 *
 * Copyright (c) 2018-2025 DaPorkchop_
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * Any persons and/or organizations using this software must include the above copyright notice and this permission notice,
 * provide sufficient credit to the original authors of the project (IE: DaPorkchop_), as well as provide a link to the original project.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package net.daporkchop.lib.concurrent;

import io.netty.util.concurrent.CompleteFuture;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import net.daporkchop.lib.concurrent.compatibility.CompletableFutureAsPFuture;
import net.daporkchop.lib.concurrent.compatibility.NettyFutureAsCompletableFuture;
import net.daporkchop.lib.concurrent.compatibility.NettyFutureAsPFuture;
import net.daporkchop.lib.concurrent.future.done.FailedPFuture;
import net.daporkchop.lib.concurrent.future.done.SucceededPFuture;
import net.daporkchop.lib.concurrent.future.group.CollectAllToListFuture;
import net.daporkchop.lib.concurrent.future.runnable.CallablePFutureTask;
import net.daporkchop.lib.concurrent.future.runnable.ConsumerPFutureTask;
import net.daporkchop.lib.concurrent.future.runnable.FunctionPFutureTask;
import net.daporkchop.lib.concurrent.future.runnable.RunnablePFutureTask;
import net.daporkchop.lib.concurrent.future.runnable.RunnableWithResultPFutureTask;
import net.daporkchop.lib.concurrent.future.runnable.SupplierPFutureTask;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static net.daporkchop.lib.common.util.PorkUtil.*;

/**
 * Helpers for dealing with various implementations of a future value.
 *
 * @author DaPorkchop_
 */
@UtilityClass
public class PFutures {
    private static final MethodHandle DEFAULTPROMISE_EXECUTOR; // (DefaultPromise) -> EventExecutor
    private static final MethodHandle COMPLETEFUTURE_EXECUTOR; // (CompleteFuture) -> EventExecutor

    static {
        try {
            Method _DEFAULTPROMISE_EXECUTOR = DefaultPromise.class.getDeclaredMethod("executor");
            _DEFAULTPROMISE_EXECUTOR.setAccessible(true);
            DEFAULTPROMISE_EXECUTOR = MethodHandles.publicLookup().unreflect(_DEFAULTPROMISE_EXECUTOR);

            Method _COMPLETEFUTURE_EXECUTOR = CompleteFuture.class.getDeclaredMethod("executor");
            _COMPLETEFUTURE_EXECUTOR.setAccessible(true);
            COMPLETEFUTURE_EXECUTOR = MethodHandles.publicLookup().unreflect(_COMPLETEFUTURE_EXECUTOR);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <V> PFuture<V> wrap(@NonNull CompletableFuture<? extends V> toWrap) {
        return wrap((Object) toWrap);
    }

    public static <V> PFuture<V> wrap(@NonNull CompletionStage<? extends V> toWrap) {
        return wrap((Object) toWrap);
    }

    public static <V> PFuture<V> wrap(@NonNull java.util.concurrent.Future<? extends V> toWrap) {
        return wrap((Object) toWrap);
    }

    public static <V> PFuture<V> wrap(@NonNull Future<? extends V> toWrap) {
        return wrap((Object) toWrap);
    }

    public static <V> PFuture<V> wrap(@NonNull Object toWrap) {
        if (toWrap instanceof PFuture) {
            return uncheckedCast(toWrap);
        } else if (toWrap instanceof Future) {
            //TODO: cache instances of this...
            return new NettyFutureAsPFuture<>(uncheckedCast(toWrap));
        } else if (toWrap instanceof CompletableFuture) {
            if (toWrap instanceof NettyFutureAsCompletableFuture) {
                return wrap((Object) ((NettyFutureAsCompletableFuture) toWrap).delegate());
            } else {
                //TODO: cache instances of this...
                return new CompletableFutureAsPFuture<>(uncheckedCast(toWrap));
            }
        }
        throw new IllegalArgumentException(className(toWrap));
    }

    /**
     * Gets the {@link EventExecutor} used by a given {@link Future}.
     *
     * @param future the {@link Future} to get the {@link EventExecutor} for
     * @return the {@link EventExecutor} used by the given {@link Future}
     */
    @SneakyThrows
    public static EventExecutor executor(@NonNull Future<?> future) {
        if (future instanceof DefaultPromise) {
            return (EventExecutor) DEFAULTPROMISE_EXECUTOR.invokeExact((DefaultPromise) future);
        } else if (future instanceof CompleteFuture) {
            return (EventExecutor) COMPLETEFUTURE_EXECUTOR.invokeExact((CompleteFuture) future);
        } else {
            return null;
        }
    }

    /**
     * Returns a {@link PFuture} that has already been completed with the given value.
     *
     * @param value the value that the future was completed with
     * @return a {@link PFuture} that has already been completed with the given value
     */
    public static <V> PFuture<V> successful(V value) {
        return successful(value, PExecutors.FORKJOINPOOL);
    }

    /**
     * Returns a {@link PFuture} that has already been completed with the given value.
     *
     * @param value    the value that the future was completed with
     * @param executor the {@link Executor} that the future should use
     * @return a {@link PFuture} that has already been completed with the given value
     */
    public static <V> PFuture<V> successful(V value, @NonNull Executor executor) {
        return new SucceededPFuture<>(PExecutors.toNettyExecutor(executor), value);
    }

    /**
     * Returns a {@link PFuture} that has already been unsuccessfully completed with the given cause.
     *
     * @param cause the cause of the future's failure
     * @return a {@link PFuture} that has already been unsuccessfully completed with the given cause
     */
    public static <V> PFuture<V> failed(@NonNull Throwable cause) {
        return failed(cause, PExecutors.FORKJOINPOOL);
    }

    /**
     * Returns a {@link PFuture} that has already been unsuccessfully completed with the given cause.
     *
     * @param cause    the cause of the future's failure
     * @param executor the {@link Executor} that the future should use
     * @return a {@link PFuture} that has already been unsuccessfully completed with the given cause
     */
    public static <V> PFuture<V> failed(@NonNull Throwable cause, @NonNull Executor executor) {
        return new FailedPFuture<>(PExecutors.toNettyExecutor(executor), cause);
    }

    /**
     * Executes the given {@link Runnable} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action the {@link Runnable} to run
     * @return a {@link PFuture} which will be completed when the {@link Runnable} has finished execution
     */
    public static PFuture<Void> runAsync(@NonNull Runnable action) {
        return runAsync(action, PExecutors.FORKJOINPOOL);
    }

    /**
     * Executes the given {@link Runnable} on the given {@link Executor}.
     *
     * @param action   the {@link Runnable} to run
     * @param executor the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed when the {@link Runnable} has finished execution
     */
    public static PFuture<Void> runAsync(@NonNull Runnable action, @NonNull Executor executor) {
        PRunnableFuture<Void> future = new RunnablePFutureTask(PExecutors.toNettyExecutor(executor), action);
        executor.execute(future);
        return future;
    }

    /**
     * Executes the given {@link Runnable} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action the {@link Runnable} to run
     * @param result the result to complete the returned future with upon completion of the {@link Runnable}
     * @return a {@link PFuture} which will be completed with the given result when the {@link Runnable} has been completed
     */
    public static <V> PFuture<V> runAsync(@NonNull Runnable action, V result) {
        return runAsync(action, result, PExecutors.FORKJOINPOOL);
    }

    /**
     * Executes the given {@link Runnable} on the given {@link Executor}.
     *
     * @param action   the {@link Runnable} to run
     * @param result   the result to complete the returned future with upon completion of the {@link Runnable}
     * @param executor the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed with the given result when the {@link Runnable} has been completed
     */
    public static <V> PFuture<V> runAsync(@NonNull Runnable action, V result, @NonNull Executor executor) {
        PRunnableFuture<V> future = new RunnableWithResultPFutureTask<>(PExecutors.toNettyExecutor(executor), action, result);
        executor.execute(future);
        return future;
    }

    /**
     * Computes a value using the given {@link Supplier} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action the {@link Supplier} to use to compute the value
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Supplier}
     */
    public static <V> PFuture<V> computeAsync(@NonNull Supplier<? extends V> action) {
        return computeAsync(action, PExecutors.FORKJOINPOOL);
    }

    /**
     * Computes a value using the given {@link Supplier} on the given {@link Executor}.
     *
     * @param action   the {@link Supplier} to use to compute the value
     * @param executor the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Supplier}
     */
    public static <V> PFuture<V> computeAsync(@NonNull Supplier<? extends V> action, @NonNull Executor executor) {
        PRunnableFuture<V> future = new SupplierPFutureTask<>(PExecutors.toNettyExecutor(executor), action);
        executor.execute(future);
        return future;
    }

    /**
     * Computes a value using the given {@link Callable} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action the {@link Callable} to use to compute the value
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Callable}
     */
    public static <V> PFuture<V> computeThrowableAsync(@NonNull Callable<? extends V> action) {
        return computeThrowableAsync(action, PExecutors.FORKJOINPOOL);
    }

    /**
     * Computes a value using the given {@link Callable} on the given {@link Executor}.
     *
     * @param action   the {@link Callable} to use to compute the value
     * @param executor the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Callable}
     */
    public static <V> PFuture<V> computeThrowableAsync(@NonNull Callable<? extends V> action, @NonNull Executor executor) {
        PRunnableFuture<V> future = new CallablePFutureTask<>(PExecutors.toNettyExecutor(executor), action);
        executor.execute(future);
        return future;
    }

    /**
     * Passes the given parameter to the given {@link Consumer} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action    the {@link Consumer} to pass the parameter to
     * @param parameter the parameter to pass to the {@link Consumer}
     * @return a {@link PFuture} which will be completed when the {@link Consumer} has finished execution
     */
    public static <P> PFuture<Void> acceptAsync(@NonNull Consumer<? super P> action, P parameter) {
        return acceptAsync(action, parameter, PExecutors.FORKJOINPOOL);
    }

    /**
     * Passes the given parameter to the given {@link Consumer} on the given {@link Executor}.
     *
     * @param action    the {@link Consumer} to pass the parameter to
     * @param parameter the parameter to pass to the {@link Consumer}
     * @param executor  the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed when the {@link Consumer} has finished execution
     */
    public static <P> PFuture<Void> acceptAsync(@NonNull Consumer<? super P> action, P parameter, @NonNull Executor executor) {
        PRunnableFuture<Void> future = new ConsumerPFutureTask<>(PExecutors.toNettyExecutor(executor), action, parameter);
        executor.execute(future);
        return future;
    }

    /**
     * Computes a value using the given {@link Function} asynchronously using {@link ForkJoinPool#commonPool()}.
     *
     * @param action    the {@link Function} to use to compute the value
     * @param parameter the parameter to pass to the {@link Function}
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Function}
     */
    public static <P, V> PFuture<V> applyAsync(@NonNull Function<? super P, ? extends V> action, P parameter) {
        return applyAsync(action, parameter, PExecutors.FORKJOINPOOL);
    }

    /**
     * Computes a value using the given {@link Function} on the given {@link Executor}.
     *
     * @param action    the {@link Function} to use to compute the value
     * @param parameter the parameter to pass to the {@link Function}
     * @param executor  the {@link Executor} to run the action on
     * @return a {@link PFuture} which will be completed with the value returned by the {@link Function}
     */
    public static <P, V> PFuture<V> applyAsync(@NonNull Function<? super P, ? extends V> action, P parameter, @NonNull Executor executor) {
        PRunnableFuture<V> future = new FunctionPFutureTask<>(PExecutors.toNettyExecutor(executor), action, parameter);
        executor.execute(future);
        return future;
    }

    /**
     * Combines the results of all of the given {@link PFuture}s into a list of results.
     *
     * @param futures the {@link PFuture}s to wait for
     * @return a {@link PFuture} which will be completed with the results of all of the given {@link PFuture}s
     */
    public static <V> PFuture<List<V>> mergeToList(@NonNull Collection<Future<V>> futures) {
        return mergeToList(futures, PExecutors.FORKJOINPOOL);
    }

    /**
     * Combines the results of all of the given {@link PFuture}s into a list of results.
     *
     * @param futures  the {@link PFuture}s to wait for
     * @param executor the {@link Executor} that the returned {@link PFuture} should execute tasks on
     * @return a {@link PFuture} which will be completed with the results of all of the given {@link PFuture}s
     */
    public static <V> PFuture<List<V>> mergeToList(@NonNull Collection<Future<V>> futures, @NonNull Executor executor) {
        if (!futures.isEmpty()) {
            return new CollectAllToListFuture<>(PExecutors.toNettyExecutor(executor), futures);
        } else {
            return new SucceededPFuture<>(PExecutors.toNettyExecutor(executor), new ArrayList<>());
        }
    }
}
