package org.infinispan.interceptors;

import java.util.ArrayList;
import java.util.List;

import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.interceptors.base.CommandInterceptor;

/**
 * Knows how to build and manage an chain of interceptors. Also in charge with invoking methods on the chain.
 *
 * <p>Note that since 8.4, most interceptors in the chain do not implement {@link CommandInterceptor}, and
 * {@code InterceptorChain} does not see the new interceptors, e.g.
 * {@code getInterceptorsWhichExtend(org.infinispan.interceptors.CallInterceptor.class)}
 * returns an empty collection.
 * The old interceptor classes extending {@link CommandInterceptor} still exist, and referencing them when
 * adding a new interceptor still works:
 * {@code chain.addInterceptorBefore(i, org.infinispan.interceptors.CallInterceptor.class}.
 *
 * @author Mircea.Markus@jboss.com
 * @author Galder Zamarreño
 * @deprecated Since 8.3, will be removed in 9.0.
 */
@Deprecated
@Scope(Scopes.NAMED_CACHE)
public class InterceptorChain {
   private AsyncInterceptorChain asyncInterceptorChain;

   public InterceptorChain(AsyncInterceptorChain asyncInterceptorChain) {
      this.asyncInterceptorChain = asyncInterceptorChain;
   }

   /**
    * Inserts the given interceptor at the specified position in the chain (o based indexing).
    *
    * @throws IllegalArgumentException if the position is invalid (e.g. 5 and there are only 2 interceptors in the
    *                                  chain)
    */
   public void addInterceptor(CommandInterceptor interceptor, int position) {
      asyncInterceptorChain.addInterceptor(interceptor, position);
   }

   /**
    * Removes the interceptor at the given postion.
    *
    * @throws IllegalArgumentException if the position is invalid (e.g. 5 and there are only 2 interceptors in the
    *                                  chain)
    */
   public void removeInterceptor(int position) {
      asyncInterceptorChain.removeInterceptor(position);
   }

   /**
    * Returns the number of interceptors in the chain.
    */
   public int size() {
      return asyncInterceptorChain.size();
   }

   /**
    * Returns an unmofiable list with all the interceptors in sequence. If first in chain is null an empty list is
    * returned.
    */
   public List<CommandInterceptor> asList() {
      ArrayList<CommandInterceptor> list =
            new ArrayList<>(asyncInterceptorChain.getInterceptors().size());
      asyncInterceptorChain.getInterceptors().forEach(ci -> {
         if (ci instanceof CommandInterceptor) {
            list.add((CommandInterceptor) ci);
         }
      });
      return list;
   }

   /**
    * Removes all the occurences of supplied interceptor type from the chain.
    */
   public void removeInterceptor(Class<? extends CommandInterceptor> clazz) {
      asyncInterceptorChain.removeInterceptor(clazz);
   }

   /**
    * Adds a new interceptor in list after an interceptor of a given type.
    *
    * @return true if the interceptor was added; i.e. the afterInterceptor exists
    */
   public boolean addInterceptorAfter(CommandInterceptor toAdd,
                                      Class<? extends CommandInterceptor> afterInterceptor) {
      return asyncInterceptorChain.addInterceptorAfter(toAdd, afterInterceptor);
   }

   /**
    * @deprecated Use {@link #addInterceptorBefore(CommandInterceptor, Class)} instead.
    */
   @Deprecated
   public boolean addInterceptorBefore(CommandInterceptor toAdd,
                                       Class<? extends CommandInterceptor> beforeInterceptor,
                                       boolean isCustom) {
      return asyncInterceptorChain.addInterceptorBefore(toAdd, beforeInterceptor);
   }

   /**
    * Adds a new interceptor in list after an interceptor of a given type.
    *
    * @return true if the interceptor was added; i.e. the afterInterceptor exists
    */
   public boolean addInterceptorBefore(CommandInterceptor toAdd,
                                       Class<? extends CommandInterceptor> beforeInterceptor) {
      return asyncInterceptorChain.addInterceptorBefore(toAdd, beforeInterceptor);
   }

   /**
    * Replaces an existing interceptor of the given type in the interceptor chain with a new interceptor instance passed as parameter.
    *
    * @param replacingInterceptor        the interceptor to add to the interceptor chain
    * @param toBeReplacedInterceptorType the type of interceptor that should be swapped with the new one
    * @return true if the interceptor was replaced
    */
   public boolean replaceInterceptor(CommandInterceptor replacingInterceptor,
                                     Class<? extends CommandInterceptor> toBeReplacedInterceptorType) {
      return asyncInterceptorChain.replaceInterceptor(replacingInterceptor, toBeReplacedInterceptorType);
   }

   /**
    * Appends at the end.
    */
   public void appendInterceptor(CommandInterceptor ci, boolean isCustom) {
      asyncInterceptorChain.appendInterceptor(ci, isCustom);
   }

   /**
    * Walks the command through the interceptor chain. The received ctx is being passed in.
    *
    * <p>Note: Reusing the context for multiple invocations is allowed. However, the two invocations
    * must not overlap, so calling {@code invoke(ctx, command)} from an interceptor is not allowed.
    * If an interceptor needs to invoke a new command through the entire chain, it must first
    * copy the invocation context with {@link InvocationContext#clone()}.</p>
    */
   public Object invoke(InvocationContext ctx, VisitableCommand command) {
      return asyncInterceptorChain.invoke(ctx, command);
   }

   /**
    * @return the first {@code CommandInterceptor} in the chain.
    * Since 9.0, there will likely be other {@link AsyncInterceptor}s before it.
    */
   @Deprecated
   public CommandInterceptor getFirstInChain() {
      return asyncInterceptorChain
            .findInterceptorExtending(CommandInterceptor.class);
   }

   /**
    * Mainly used by unit tests to replace the interceptor chain with the starting point passed in.
    *
    * @param interceptor interceptor to be used as the first interceptor in the chain.
    */
   public void setFirstInChain(CommandInterceptor interceptor) {
      addInterceptor(interceptor, 0);
   }

   /**
    * Returns all interceptors which extend the given command interceptor.
    */
   public List<CommandInterceptor> getInterceptorsWhichExtend(
         Class<? extends CommandInterceptor> interceptorClass) {
      ArrayList<CommandInterceptor> list =
            new ArrayList<>(asyncInterceptorChain.getInterceptors().size());
      asyncInterceptorChain.getInterceptors().forEach(ci -> {
         if (interceptorClass.isInstance(ci)) {
            list.add((CommandInterceptor) ci);
         }
      });
      return list;
   }

   /**
    * Returns all the interceptors that have the fully qualified name of their class equal with the supplied class
    * name.
    */
   public List<CommandInterceptor> getInterceptorsWithClass(Class clazz) {
      ArrayList<CommandInterceptor> list =
            new ArrayList<>(asyncInterceptorChain.getInterceptors().size());
      asyncInterceptorChain.getInterceptors().forEach(ci -> {
         if (clazz == ci.getClass()) {
            list.add((CommandInterceptor) ci);
         }
      });
      return list;
   }

   /**
    * Checks whether the chain contains the supplied interceptor instance.
    */
   public boolean containsInstance(CommandInterceptor interceptor) {
      return asyncInterceptorChain.containsInstance(interceptor);
   }

   public boolean containsInterceptorType(Class<? extends CommandInterceptor> interceptorType) {
      return asyncInterceptorChain.containsInterceptorType(interceptorType);
   }

   public boolean containsInterceptorType(Class<? extends CommandInterceptor> interceptorType,
                                          boolean alsoMatchSubClasses) {
      return asyncInterceptorChain.containsInterceptorType(interceptorType, alsoMatchSubClasses);
   }
}
