package org.infinispan.interceptors.impl;

import java.util.Iterator;

import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.BaseAsyncInterceptor;
import org.infinispan.interceptors.InvocationSuccessFunction;

/**
 * Invoke a sequence of sub-commands.
 *
 * @author Dan Berindei
 * @since 9.0
 */
public class MultiSubCommandInvoker implements InvocationSuccessFunction<VisitableCommand> {
   private final BaseAsyncInterceptor interceptor;
   private final Object finalStage;
   private final Iterator<VisitableCommand> subCommands;

   private MultiSubCommandInvoker(BaseAsyncInterceptor interceptor, Object finalReturnValue,
                                  Iterator<VisitableCommand> subCommands) {
      this.interceptor = interceptor;
      this.finalStage = finalReturnValue;
      this.subCommands = subCommands;
   }

   /**
    * Call {@link BaseAsyncInterceptor#invokeNext(InvocationContext, VisitableCommand)} on a sequence of sub-commands.
    * <p>
    * Stop when one of the sub-commands throws an exception, and return an invocation stage with that exception. If all
    * the sub-commands are successful, return the {@code finalStage}. If {@code finalStage} has and exception, skip all
    * the sub-commands and just return the {@code finalStage}.
    */
   public static Object invokeEach(InvocationContext ctx, Iterator<VisitableCommand> subCommands,
                                   BaseAsyncInterceptor interceptor, Object finalReturnValue) {
      if (!subCommands.hasNext())
         return finalReturnValue;

      MultiSubCommandInvoker invoker = new MultiSubCommandInvoker(interceptor, finalReturnValue, subCommands);
      VisitableCommand newCommand = subCommands.next();
      return interceptor.invokeNextThenApply(ctx, newCommand, invoker);
   }

   @Override
   public Object apply(InvocationContext rCtx, VisitableCommand rCommand, Object rv) throws Throwable {
      if (subCommands.hasNext()) {
         VisitableCommand newCommand = subCommands.next();
         return interceptor.invokeNextThenApply(rCtx, newCommand, this);
      } else {
         return finalStage;
      }
   }
}
