package org.infinispan.xsite;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import org.infinispan.commands.remote.BaseRpcCommand;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.util.ByteString;
import org.infinispan.util.concurrent.CompletableFutures;

/**
 * Command used for handling XSiteReplication administrative operations.
 *
 * @author Mircea Markus
 * @since 5.2
 */
public class XSiteAdminCommand extends BaseRpcCommand {

   public static final int COMMAND_ID = 32;

   public enum AdminOperation {
      SITE_STATUS,
      STATUS,
      TAKE_OFFLINE,
      BRING_ONLINE,
      AMEND_TAKE_OFFLINE;

      private static final AdminOperation[] CACHED_VALUES = values();

      private static AdminOperation valueOf(int index) {
         return CACHED_VALUES[index];
      }
   }

   public enum Status {
      OFFLINE, ONLINE
   }

   private String siteName;
   private Integer afterFailures;
   private Long minTimeToWait;
   private AdminOperation adminOperation;

   private BackupSender backupSender;

   @SuppressWarnings("unused")
   public XSiteAdminCommand() {
      super(null);// For command id uniqueness test
   }

   public XSiteAdminCommand(ByteString cacheName) {
      super(cacheName);// For command id uniqueness test
   }

   public XSiteAdminCommand(ByteString cacheName, String siteName, AdminOperation op, Integer afterFailures, Long minTimeToWait) {
      this(cacheName);
      this.siteName = siteName;
      this.adminOperation = op;
      this.afterFailures = afterFailures;
      this.minTimeToWait = minTimeToWait;
   }

   public void init(BackupSender backupSender) {
      this.backupSender = backupSender;
   }

   @Override
   public CompletableFuture<Object> invokeAsync() throws Throwable {
      switch (adminOperation) {
         case SITE_STATUS: {
            if (backupSender.getOfflineStatus(siteName).isOffline()) {
               return CompletableFuture.completedFuture(Status.OFFLINE);
            } else {
               return CompletableFuture.completedFuture(Status.ONLINE);
            }
         }
         case STATUS: {
            return CompletableFuture.completedFuture(backupSender.status());
         }
         case TAKE_OFFLINE: {
            return CompletableFuture.completedFuture(backupSender.takeSiteOffline(siteName));
         }
         case BRING_ONLINE: {
            return CompletableFuture.completedFuture(backupSender.bringSiteOnline(siteName));
         }
         case AMEND_TAKE_OFFLINE: {
            backupSender.getOfflineStatus(siteName).amend(afterFailures, minTimeToWait);
            return CompletableFutures.completedNull();
         }
         default: {
            throw new IllegalStateException("Unhandled admin operation " + adminOperation);
         }
      }
   }

   @Override
   public byte getCommandId() {
      return COMMAND_ID;
   }

   @Override
   public void writeTo(ObjectOutput output) throws IOException {
      MarshallUtil.marshallEnum(adminOperation, output);
      switch (adminOperation) {
         case SITE_STATUS:
         case TAKE_OFFLINE:
         case BRING_ONLINE:
            output.writeUTF(siteName);
            return;
         case AMEND_TAKE_OFFLINE:
            output.writeUTF(siteName);
            output.writeObject(afterFailures);
            output.writeObject(minTimeToWait);
            return;
         case STATUS:
            return;
         default:
            throw new IllegalStateException("Unknown admin operation " + adminOperation);
      }
   }

   @Override
   public void readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
      adminOperation = Objects.requireNonNull(MarshallUtil.unmarshallEnum(input, AdminOperation::valueOf));
      switch (adminOperation) {
         case SITE_STATUS:
         case TAKE_OFFLINE:
         case BRING_ONLINE:
            siteName = input.readUTF();
            return;
         case AMEND_TAKE_OFFLINE:
            siteName = input.readUTF();
            afterFailures = (Integer) input.readObject();
            minTimeToWait = (Long) input.readObject();
            return;
         case STATUS:
            return;
         default:
            throw new IllegalStateException("Unknown admin operation " + adminOperation);
      }
   }

   @Override
   public final boolean isReturnValueExpected() {
      return true;
   }

   @Override
   public String toString() {
      return "XSiteAdminCommand{" +
            "siteName='" + siteName + '\'' +
            ", afterFailures=" + afterFailures +
            ", minTimeToWait=" + minTimeToWait +
            ", adminOperation=" + adminOperation +
            ", backupSender=" + backupSender +
            '}';
   }
}
