package org.infinispan.interceptors.impl;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.InvocationCallback;
import org.infinispan.interceptors.InvocationStage;
import org.infinispan.util.concurrent.CompletableFutures;

/**
 * Invocation stage representing a computation that may or may not be done yet.
 *
 * It is only meant to support the simplest asynchronous invocation,
 * {@link org.infinispan.interceptors.BaseAsyncInterceptor#asyncValue(CompletableFuture)}.
 *
 * @author Dan Berindei
 * @since 9.0
 */
public class SimpleAsyncInvocationStage extends InvocationStage {
   protected final CompletableFuture<Object> future;

   @SuppressWarnings("unchecked")
   public SimpleAsyncInvocationStage(CompletionStage<?> future) {
      this.future = (CompletableFuture<Object>) future;
   }

   public SimpleAsyncInvocationStage(Throwable throwable) {
      this.future = CompletableFutures.completedExceptionFuture(throwable);
   }

   @Override
   public Object get() throws Throwable {
      try {
         return CompletableFutures.await(future);
      } catch (ExecutionException e) {
         throw e.getCause();
      }
   }

   @Override
   public boolean isDone() {
      return future.isDone();
   }

   @Override
   public CompletableFuture<Object> toCompletableFuture() {
      return future;
   }

   @Override
   public Object addCallback(InvocationContext ctx, VisitableCommand command,
                        InvocationCallback function) {
      if (future.isDone()) {
         Object rv;
         Throwable throwable;
         try {
            rv = future.getNow(null);
            throwable = null;
         } catch (Throwable t) {
            rv = null;
            throwable = CompletableFutures.extractException(t);
         }
         try {
            return function.apply(ctx, command, rv, throwable);
         } catch (Throwable t) {
            return new SimpleAsyncInvocationStage(t);
         }
      }
      return new QueueAsyncInvocationStage(ctx, command, future, function);
   }
}
