package org.infinispan.test.concurrent;

import java.util.List;

import org.infinispan.Cache;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.AsyncInterceptor;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.interceptors.BaseAsyncInterceptor;

/**
 * Replaces an {@link AsyncInterceptor} with a wrapper that can interact with a {@link StateSequencer} when a
 * command that matches a {@link CommandMatcher} is visited.
 *
 * @author Dan Berindei
 * @since 7.0
 */
public class InterceptorSequencerAction {
   private final StateSequencer stateSequencer;
   private final Cache<?, ?> cache;
   private final Class<? extends AsyncInterceptor> interceptorClass;
   private CommandMatcher matcher;
   private SequencerInterceptor ourInterceptor;

   public InterceptorSequencerAction(StateSequencer stateSequencer, Cache<?, ?> cache, Class<? extends AsyncInterceptor> interceptorClass, CommandMatcher matcher) {
      this.stateSequencer = stateSequencer;
      this.cache = cache;
      this.interceptorClass = interceptorClass;
      this.matcher = matcher;
   }

   /**
    * Set up a list of sequencer states before interceptor {@code interceptorClass} is called.
    */
   public InterceptorSequencerAction before(String state1, String... additionalStates) {
      initOurInterceptor();
      ourInterceptor.beforeStates(StateSequencerUtil.concat(state1, additionalStates));
      return this;
   }

   // TODO Should we add beforeInvokeNext() and afterInvokeNext()?

   private void initOurInterceptor() {
      if (ourInterceptor == null) {
         ourInterceptor = SequencerInterceptor.createUniqueInterceptor(cache.getAdvancedCache().getAsyncInterceptorChain());
         ourInterceptor.init(stateSequencer, matcher);
         cache.getAdvancedCache().getAsyncInterceptorChain().addInterceptorBefore(ourInterceptor, interceptorClass);
      }
   }

   /**
    * Set up a list of sequencer states after interceptor {@code interceptorClass} has returned.
    * <p/>
    * Each invocation accepted by {@code matcher} will enter/exit the next state from the list, and does nothing after the list is exhausted.
    */
   public InterceptorSequencerAction after(String state1, String... additionalStates) {
      initOurInterceptor();
      ourInterceptor.afterStates(StateSequencerUtil.concat(state1, additionalStates));
      return this;
   }

   public static class SequencerInterceptor extends BaseAsyncInterceptor {
      private static final Class[] uniqueInterceptorClasses = {
            U1.class, U2.class, U3.class, U4.class, U5.class, U6.class, U7.class, U8.class, U9.class
      };

      private StateSequencer stateSequencer;
      private CommandMatcher matcher;
      private volatile List<String> statesBefore;
      private volatile List<String> statesAfter;

      public static SequencerInterceptor createUniqueInterceptor(AsyncInterceptorChain chain) {
         Class uniqueClass = findUniqueClass(chain);
         try {
            return (SequencerInterceptor) uniqueClass.newInstance();
         } catch (Exception e) {
            throw new RuntimeException("Cannot instantiate unique interceptor", e);
         }
      }

      public static Class<?> findUniqueClass(AsyncInterceptorChain chain) {
         for (Class<? extends AsyncInterceptor> clazz : uniqueInterceptorClasses) {
            if (!chain.containsInterceptorType(clazz)) {
               return clazz;
            }
         }

         throw new IllegalStateException("Too many sequencer interceptors added to the same chain");
      }

      public void init(StateSequencer stateSequencer, CommandMatcher matcher) {
         this.stateSequencer = stateSequencer;
         this.matcher = matcher;
      }

      @Override
      public Object visitCommand(InvocationContext ctx, VisitableCommand command) throws Throwable {
         boolean commandAccepted = matcher.accept(command);
         StateSequencerUtil.advanceMultiple(stateSequencer, commandAccepted, statesBefore);
         return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) ->  {
            StateSequencerUtil.advanceMultiple(stateSequencer, commandAccepted, statesAfter);
         });
      }

      public void beforeStates(List<String> states) {
         this.statesBefore = StateSequencerUtil.listCopy(states);
      }

      public void afterStates(List<String> states) {
         this.statesAfter = StateSequencerUtil.listCopy(states);
      }

      public static class U1 extends SequencerInterceptor {
      }

      public static class U2 extends SequencerInterceptor {
      }

      public static class U3 extends SequencerInterceptor {
      }

      public static class U4 extends SequencerInterceptor {
      }

      public static class U5 extends SequencerInterceptor {
      }

      public static class U6 extends SequencerInterceptor {
      }

      public static class U7 extends SequencerInterceptor {
      }

      public static class U8 extends SequencerInterceptor {
      }

      public static class U9 extends SequencerInterceptor {
      }
   }
}
