package org.infinispan.server.hotrod;

import java.util.EnumSet;

/**
 * Enumeration defining all of the possible hotrod operations
 *
 * @author wburns
 * @since 8.3
 */
public enum HotRodOperation {
   // Puts
   PUT(0x01, 0x02, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_VALUE, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.VALUE),
   PUT_IF_ABSENT(0x05, 0x06, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_VALUE, OpReqs.REQUIRES_AUTH, OpReqs.IS_CONDITIONAL, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE), DecoderRequirements.VALUE),
   // Replace
   REPLACE(0x07, 0x08, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_VALUE, OpReqs.REQUIRES_AUTH, OpReqs.IS_CONDITIONAL, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE), DecoderRequirements.VALUE),
   REPLACE_IF_UNMODIFIED(0x09, 0x0A, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_VALUE, OpReqs.REQUIRES_AUTH, OpReqs.IS_CONDITIONAL, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE), DecoderRequirements.VALUE),
   // Contains
   CONTAINS_KEY(0x0F, 0x10, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY),
   // Gets
   GET(0x03, 0x04, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY),
   GET_WITH_VERSION(0x11, 0x12, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY),
   GET_WITH_METADATA(0x1B, 0x1C, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY),
   // Removes
   REMOVE(0x0B, 0x0C, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY),
   REMOVE_IF_UNMODIFIED(0x0D, 0x0E, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.IS_CONDITIONAL, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_RETURN_PREVIOUS_VALUE), DecoderRequirements.PARAMETERS),

   // Operation(s) that end after Header is read
   PING(0x17, 0x18, EnumSet.noneOf(OpReqs.class), DecoderRequirements.HEADER),
   STATS(0x15, 0x16, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.HEADER),
   CLEAR(0x13, 0x14, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.HEADER),
   SIZE(0x29, 0x2A, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.HEADER),
   AUTH_MECH_LIST(0x21, 0x22, EnumSet.noneOf(OpReqs.class), DecoderRequirements.HEADER),

   // Operation(s) that end after Custom Header is read
   AUTH(0x23, 0x24, EnumSet.noneOf(OpReqs.class), DecoderRequirements.HEADER_CUSTOM),
   EXEC(0x2B, 0x2C, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.HEADER_CUSTOM),

   // Operations that end after a Custom Key is read
   BULK_GET(0x19, 0x1A, EnumSet.of(OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY_CUSTOM),
   BULK_GET_KEYS(0x1D, 0x1E, EnumSet.of(OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY_CUSTOM),
   QUERY(0x1F, 0x20, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),
   ADD_CLIENT_LISTENER(0x25, 0x26, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),
   REMOVE_CLIENT_LISTENER(0x27, 0x28, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),
   ITERATION_START(0x31, 0x32, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),
   ITERATION_NEXT(0x33, 0x34, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),
   ITERATION_END(0x35, 0x36, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.KEY_CUSTOM),

   // Operations that end after a Custom Value is read
   PUT_ALL(0x2D, 0x2E, EnumSet.of(OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.VALUE_CUSTOM),
   GET_ALL(0x2F, 0x30, EnumSet.of(OpReqs.REQUIRES_AUTH), DecoderRequirements.VALUE_CUSTOM),

   // Stream operations
   GET_STREAM(0x37, 0x38, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.KEY_CUSTOM),
   PUT_STREAM(0x39, 0x3A, EnumSet.of(OpReqs.REQUIRES_KEY, OpReqs.REQUIRES_AUTH, OpReqs.CAN_SKIP_INDEXING, OpReqs.CAN_SKIP_CACHE_LOAD), DecoderRequirements.VALUE_CUSTOM),

   // Responses
   ERROR(0x50),
   CACHE_ENTRY_CREATED_EVENT(0x60),
   CACHE_ENTRY_MODIFIED_EVENT(0x61),
   CACHE_ENTRY_REMOVED_EVENT(0x62),
   CACHE_ENTRY_EXPIRED_EVENT(0x63);

   private final int requestOpCode;
   private final int responseOpCode;
   private final boolean requiresKey;
   private final boolean requiresValue;
   private final DecoderRequirements decodeRequirements;
   private final boolean requiresAuthentication;
   private final boolean canSkipIndexing;
   private final boolean canSkipCacheLoading;
   private final boolean canReturnPreviousValue;
   private final boolean isConditional;
   private static final HotRodOperation REQUEST_OPCODES[];
   private static final HotRodOperation RESPONSE_OPCODES[];

   HotRodOperation(int requestOpCode, int responseOpCode, EnumSet<OpReqs> opRequirements, DecoderRequirements decoderRequirements) {
      this.requestOpCode = requestOpCode;
      this.responseOpCode = responseOpCode;
      this.decodeRequirements = decoderRequirements;
      this.requiresKey = opRequirements.contains(OpReqs.REQUIRES_KEY);
      this.requiresValue = opRequirements.contains(OpReqs.REQUIRES_VALUE);
      this.requiresAuthentication = opRequirements.contains(OpReqs.REQUIRES_AUTH);
      this.canSkipIndexing = opRequirements.contains(OpReqs.CAN_SKIP_INDEXING);
      this.canSkipCacheLoading = opRequirements.contains(OpReqs.CAN_SKIP_CACHE_LOAD);
      this.canReturnPreviousValue = opRequirements.contains(OpReqs.CAN_RETURN_PREVIOUS_VALUE);
      this.isConditional = opRequirements.contains(OpReqs.IS_CONDITIONAL);
   }

   HotRodOperation(int responseOpCode) {
      this(0, responseOpCode, EnumSet.noneOf(OpReqs.class), DecoderRequirements.HEADER);
   }

   public int getResponseOpCode() {
      return responseOpCode;
   }

   DecoderRequirements getDecoderRequirements() {
      return decodeRequirements;
   }

   boolean requiresKey() {
      return requiresKey;
   }

   boolean requireValue() {
      return requiresValue;
   }

   boolean requiresAuthentication() {
      return requiresAuthentication;
   }

   boolean canSkipIndexing() {
      return canSkipIndexing;
   }

   boolean canSkipCacheLoading() {
      return canSkipCacheLoading;
   }

   boolean isNotConditionalAndCanReturnPrevious() {
      return this == PUT;
   }

   boolean canReturnPreviousValue() {
      return canReturnPreviousValue;
   }

   boolean isConditional() {
      return isConditional;
   }

   static {
      REQUEST_OPCODES = new HotRodOperation[255];
      RESPONSE_OPCODES = new HotRodOperation[255];
      for(HotRodOperation op : HotRodOperation.values()) {
         if (op.requestOpCode > 0)
           REQUEST_OPCODES[op.requestOpCode] = op;
         if (op.responseOpCode > 0)
            RESPONSE_OPCODES[op.responseOpCode] = op;
      }
   }

   public static HotRodOperation fromRequestOpCode(byte op) {
      return REQUEST_OPCODES[op];
   }

   public static HotRodOperation fromResponseOpCode(byte op) {
      return RESPONSE_OPCODES[op];
   }
}

enum DecoderRequirements {
   HEADER,
   HEADER_CUSTOM,
   KEY,
   KEY_CUSTOM,
   PARAMETERS,
   VALUE,
   VALUE_CUSTOM
}

enum OpReqs {
   REQUIRES_KEY,
   REQUIRES_VALUE,
   REQUIRES_AUTH,
   CAN_SKIP_INDEXING,
   CAN_SKIP_CACHE_LOAD,
   CAN_RETURN_PREVIOUS_VALUE,
   IS_CONDITIONAL
}
