package org.infinispan.remoting;

import java.util.concurrent.Callable;

import org.infinispan.Cache;
import org.infinispan.commands.CommandsFactory;
import org.infinispan.commands.remote.CacheRpcCommand;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.interceptors.locking.ClusteringDependentLogic;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.responses.ResponseGenerator;
import org.infinispan.remoting.transport.Address;

/**
 * Simulates a remote invocation on the local node. This is needed because the transport does not redirect to itself the
 * replicable commands.
 *
 * @author Pedro Ruivo
 * @since 7.0
 */
public class LocalInvocation implements Callable<Response> {

   private final ResponseGenerator responseGenerator;
   private final CacheRpcCommand command;
   private final CommandsFactory commandsFactory;
   private final Address self;

   private LocalInvocation(ResponseGenerator responseGenerator, CacheRpcCommand command,
                           CommandsFactory commandsFactory, Address self) {
      this.responseGenerator = responseGenerator;
      this.command = command;
      this.commandsFactory = commandsFactory;
      this.self = self;
   }

   @Override
   public Response call() throws Exception {
      try {
         commandsFactory.initializeReplicableCommand(command, false);
         command.setOrigin(self);
         Object returnValue = command.invoke();
         return responseGenerator.getResponse(command, returnValue);
      } catch (Throwable throwable) {
         throw new RemoteException("Problems invoking command locally.", throwable);
      }
   }

   public static LocalInvocation newInstanceFromCache(Cache<?, ?> cache, CacheRpcCommand command) {
      ComponentRegistry registry = cache.getAdvancedCache().getComponentRegistry();
      ResponseGenerator responseGenerator = registry.getResponseGenerator();
      CommandsFactory commandsFactory = registry.getCommandsFactory();
      Address self = registry.getComponent(ClusteringDependentLogic.class).getAddress();
      return newInstance(responseGenerator, command, commandsFactory, self);
   }

   public static LocalInvocation newInstance(ResponseGenerator responseGenerator, CacheRpcCommand command,
                                             CommandsFactory commandsFactory, Address self) {
      if (responseGenerator == null || command == null || commandsFactory == null || self == null) {
         throw new NullPointerException("Null arguments are not allowed.");
      }
      return new LocalInvocation(responseGenerator, command, commandsFactory, self);
   }

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      LocalInvocation that = (LocalInvocation) o;

      return command.equals(that.command);

   }

   @Override
   public int hashCode() {
      return command.hashCode();
   }
}
