package org.jboss.cache.buddyreplication;

import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.commands.AbstractVisitor;
import org.jboss.cache.commands.ReversibleCommand;
import org.jboss.cache.commands.read.ExistsCommand;
import org.jboss.cache.commands.read.GetChildrenNamesCommand;
import org.jboss.cache.commands.read.GetDataMapCommand;
import org.jboss.cache.commands.read.GetKeyValueCommand;
import org.jboss.cache.commands.read.GetKeysCommand;
import org.jboss.cache.commands.read.GetNodeCommand;
import org.jboss.cache.commands.read.GravitateDataCommand;
import org.jboss.cache.commands.tx.CommitCommand;
import org.jboss.cache.commands.tx.OptimisticPrepareCommand;
import org.jboss.cache.commands.tx.PrepareCommand;
import org.jboss.cache.commands.tx.RollbackCommand;
import org.jboss.cache.commands.write.*;
import org.jboss.cache.factories.CommandsFactory;

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

/**
 * For each command the fqns are changed such that they are under the current buddy group's backup subtree
 * (e.g., /_buddy_backup_/my_host:7890/) rather than the root (/).
 *
 * @author Mircea.Markus@jboss.com
 * @since 2.2
 */
public class Fqn2BuddyFqnVisitor extends AbstractVisitor
{
   private BuddyFqnTransformer buddyFqnTransformer;

   private final String buddyGroupName;

   CommandsFactory factory;

   public Fqn2BuddyFqnVisitor(String buddyGroupName)
   {
      this.buddyGroupName = buddyGroupName == null ? "null" : buddyGroupName;
   }

   public Fqn2BuddyFqnVisitor(String buddyGroupName, CommandsFactory cf)
   {
      this.buddyGroupName = buddyGroupName == null ? "null" : buddyGroupName;
      this.factory = cf;
   }

   @Override
   public Object visitCommitCommand(InvocationContext ctx, CommitCommand commitCommand) throws Throwable
   {
      return commitCommand;
   }

   @Override
   public Object visitPutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildPutDataMapCommand(null, transformed, command.getData());
   }

   @Override
   public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildPutKeyValueCommand(null, transformed, command.getKey(), command.getValue());
   }

   @Override
   public Object visitPutForExternalReadCommand(InvocationContext ctx, PutForExternalReadCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildPutForExternalReadCommand(null, transformed, command.getKey(), command.getValue());
   }

   @Override
   public Object visitRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildRemoveNodeCommand(command.getGlobalTransaction(), transformed);
   }

   @Override
   public Object visitClearDataCommand(InvocationContext ctx, ClearDataCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildClearDataCommand(command.getGlobalTransaction(), transformed);
   }

   @Override
   public Object visitEvictFqnCommand(InvocationContext ctx, EvictCommand command) throws Throwable
   {
      Fqn fqn = getBackupFqn(command.getFqn());
      return factory.buildEvictFqnCommand(fqn);
   }

   @Override
   public Object visitInvalidateCommand(InvocationContext ctx, InvalidateCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildInvalidateCommand(transformed);
   }

   @Override
   public Object visitRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildRemoveKeyCommand(null, transformed, command.getKey());
   }

   @Override
   public Object visitGetDataMapCommand(InvocationContext ctx, GetDataMapCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGetDataMapCommand(transformed);
   }

   @Override
   public Object visitExistsNodeCommand(InvocationContext ctx, ExistsCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildEvictFqnCommand(transformed);
   }

   @Override
   public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGetKeyValueCommand(transformed, command.getKey(), command.isSendNodeEvent());
   }

   @Override
   public Object visitGetNodeCommand(InvocationContext ctx, GetNodeCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGetNodeCommand(transformed);
   }

   @Override
   public Object visitGetKeysCommand(InvocationContext ctx, GetKeysCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGetKeysCommand(transformed);
   }

   @Override
   public Object visitGetChildrenNamesCommand(InvocationContext ctx, GetChildrenNamesCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGetChildrenNamesCommand(transformed);
   }

   @Override
   public Object visitMoveCommand(InvocationContext ctx, MoveCommand command) throws Throwable
   {
      Fqn transformedFrom = getBackupFqn(command.getFqn());
      Fqn transformedTo = getBackupFqn(command.getTo());
      return factory.buildMoveCommand(transformedFrom, transformedTo);
   }

   @Override
   public Object visitGravitateDataCommand(InvocationContext ctx, GravitateDataCommand command) throws Throwable
   {
      Fqn transformed = getBackupFqn(command.getFqn());
      return factory.buildGravitateDataCommand(transformed, command.isSearchSubtrees());
   }

   @Override
   public Object visitPrepareCommand(InvocationContext ctx, PrepareCommand command) throws Throwable
   {
      List<ReversibleCommand> toTransform = command.getModifications();
      List<ReversibleCommand> transformedCommands = transformBatch(toTransform);
      return factory.buildPrepareCommand(command.getGlobalTransaction(), transformedCommands, command.getLocalAddress(), command.isOnePhaseCommit());
   }

   @Override
   public Object visitRollbackCommand(InvocationContext ctx, RollbackCommand command) throws Throwable
   {
      return factory.buildRollbackCommand(null);
   }

   @Override
   public Object visitOptimisticPrepareCommand(InvocationContext ctx, OptimisticPrepareCommand command) throws Throwable
   {
      List<ReversibleCommand> transformed = transformBatch(command.getModifications());
      return factory.buildOptimisticPrepareCommand(command.getGlobalTransaction(), transformed, command.getData(), command.getLocalAddress(), command.isOnePhaseCommit());
   }

   @Override
   public Object visitCreateNodeCommand(InvocationContext ctx, CreateNodeCommand command) throws Throwable
   {
      return factory.buildCreateNodeCommand(getBackupFqn(command.getFqn()));
   }

   public List<ReversibleCommand> transformBatch(List<ReversibleCommand> toTransform) throws Throwable
   {
      List<ReversibleCommand> transformedCommands = new ArrayList<ReversibleCommand>(toTransform.size());
      for (ReversibleCommand com : toTransform)
      {
         transformedCommands.add((ReversibleCommand) com.acceptVisitor(null, this));
      }
      return transformedCommands;
   }


   /**
    * Assumes the backup Fqn if the current instance is the data owner.
    */
   public Fqn getBackupFqn(Fqn originalFqn)
   {
      return buddyFqnTransformer.getBackupFqn(buddyGroupName, originalFqn);
   }

   public void setBuddyFqnTransformer(BuddyFqnTransformer buddyFqnTransformer)
   {
      this.buddyFqnTransformer = buddyFqnTransformer;
   }
}
