content: package org.allaymc.server.datastruct.palette;
  
  import io.netty.buffer.ByteBuf;
  import lombok.SneakyThrows;
  import org.allaymc.api.utils.SemVersion;
  import org.allaymc.api.utils.hash.HashUtils;
  import org.allaymc.server.network.ProtocolInfo;
  import org.cloudburstmc.nbt.NbtType;
  import org.cloudburstmc.nbt.util.stream.LittleEndianDataInputStream;
  
  import java.io.IOException;
  
  /**
   * @author Cool_Loong
   */
  public class PaletteUtils {
  
      public static final int HASH_NOT_LATEST = Integer.MAX_VALUE;
  
      @SneakyThrows
      public static int fastReadBlockStateHash(LittleEndianDataInputStream input, ByteBuf byteBuf) {
          byteBuf.markReaderIndex();
          int typeId = input.readUnsignedByte();
          NbtType<?> type = NbtType.byId(typeId);
          input.skipBytes(input.readUnsignedShort()); // Root tag name
          return deserialize(input, byteBuf, type, 16);
      }
  
      private static int deserialize(LittleEndianDataInputStream input, ByteBuf byteBuf, NbtType<?> type, int maxDepth) throws IOException {
          if (maxDepth < 0) {
              throw new IllegalArgumentException("NBT compound is too deeply nested");
          }
          switch (type.getEnum()) {
              case END -> {
              }
              case BYTE -> input.skipBytes(1);
              case SHORT -> input.skipBytes(2);
              case INT, FLOAT -> input.skipBytes(4);
              case LONG, DOUBLE -> input.skipBytes(8);
              case BYTE_ARRAY -> input.skipBytes(input.readInt());
              case STRING -> input.skipBytes(input.readUnsignedShort());
              case COMPOUND -> {
                  NbtType<?> nbtType;
                  while ((nbtType = NbtType.byId(input.readUnsignedByte())) != NbtType.END) {
                      int end = byteBuf.readerIndex();
                      String name = input.readUTF();
                      if (name.equals("version")) {
                          int version = input.readInt();
                          byteBuf.resetReaderIndex();
                          if (version != ProtocolInfo.BLOCK_STATE_VERSION_NUM) {
                              return HASH_NOT_LATEST;
                          }
                          byte[] result = new byte[end - byteBuf.readerIndex()];
                          input.readFully(result);
                          result[result.length - 1] = 0;// because an End Tag be put when at the end serialize tag
  
                          // UTF
                          input.skipBytes(input.readUnsignedShort());
                          // Value
                          deserialize(input, byteBuf, nbtType, maxDepth - 1);
                          // End tag
                          input.skipBytes(1);
                          int hash = HashUtils.fnv1a_32(result);
                          if (hash == 147887818) {
                              // minecraft:unknown
                              hash = -2;
                          }
                          return hash;
                      }
                      deserialize(input, byteBuf, nbtType, maxDepth - 1);
                  }
              }
              case LIST -> {
                  int typeId = input.readUnsignedByte();
                  NbtType<?> listType = NbtType.byId(typeId);
                  int listLength = input.readInt();
                  for (int i = 0; i < listLength; i++) {
                      deserialize(input, byteBuf, listType, maxDepth - 1);
                  }
              }
              case INT_ARRAY -> input.skipBytes(input.readInt() * 4);
              case LONG_ARRAY -> input.skipBytes(input.readInt() * 8);
          }
          return HASH_NOT_LATEST;
      }
  
      private static SemVersion toSemVersion(int version) {
          int major = (version >> 24) & 0xFF;
          int minor = (version >> 16) & 0xFF;
          int patch = (version >> 8) & 0xFF;
          int revision = version & 0xFF;
          return new SemVersion(major, minor, patch, revision, 0);
      }
  }
  