content: package org.allaymc.api.utils.hash;
  
  import com.google.common.base.Preconditions;
  import lombok.extern.slf4j.Slf4j;
  import org.allaymc.api.block.property.type.BlockPropertyType;
  import org.allaymc.api.utils.identifier.Identifier;
  import org.allaymc.api.world.chunk.ChunkSection;
  import org.cloudburstmc.nbt.NbtMap;
  import org.cloudburstmc.nbt.NbtUtils;
  import org.jetbrains.annotations.Range;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.List;
  import java.util.TreeMap;
  
  /**
   * A utility class that contains hash related methods.
   *
   * @author Cool_Loong | daoge_cmd
   */
  @Slf4j
  public final class HashUtils {
  
      private static final Identifier UNKNOWN_ID = new Identifier("minecraft:unknown");
  
      //https://gist.github.com/Alemiz112/504d0f79feac7ef57eda174b668dd345
      private static final int FNV1_32_INIT = 0x811c9dc5;
      private static final int FNV1_PRIME_32 = 0x01000193;
  
      private HashUtils() {
          throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
      }
  
      /**
       * Compute block state hash from the given identifier and property values.
       *
       * @param identifier     the identifier
       * @param propertyValues the property values
       * @return the hash
       */
      public static int computeBlockStateHash(Identifier identifier, List<BlockPropertyType.BlockPropertyValue<?, ?, ?>> propertyValues) {
          if (identifier.equals(UNKNOWN_ID)) {
              return -2; // This is special case
          }
  
          var states = new TreeMap<String, Object>();
          for (var value : propertyValues) {
              states.put(value.getPropertyType().getName(), value.getSerializedValue());
          }
  
          var tag = NbtMap.builder()
                  .putString("name", identifier.toString())
                  .putCompound("states", NbtMap.fromMap(states))
                  .build();
  
          return fnv1a_32_nbt(tag);
      }
  
      /**
       * Compute block state hash from the given identifier and property values.
       *
       * @param identifier     the identifier
       * @param propertyValues the property values
       * @return the hash
       */
      public static int computeBlockStateHash(Identifier identifier, BlockPropertyType.BlockPropertyValue<?, ?, ?>[] propertyValues) {
          if (identifier.equals(UNKNOWN_ID)) {
              return -2; // This is special case
          }
  
          var states = new TreeMap<String, Object>();
          for (var value : propertyValues) {
              states.put(value.getPropertyType().getName(), value.getSerializedValue());
          }
  
          var tag = NbtMap.builder()
                  .putString("name", identifier.toString())
                  .putCompound("states", NbtMap.fromMap(states))
                  .build();
  
          return fnv1a_32_nbt(tag);
      }
  
      /**
       * FNV-1a 32-bit hash algorithm.
       *
       * @param tag the tag to hash
       * @return the hash
       */
      public static int fnv1a_32_nbt(NbtMap tag) {
          byte[] bytes;
          try (var stream = new ByteArrayOutputStream();
               var outputStream = NbtUtils.createWriterLE(stream)) {
              outputStream.writeTag(tag);
              bytes = stream.toByteArray();
          } catch (IOException e) {
              log.error("Failed to covert NBT into bytes", e);
              throw new HashException(e);
          }
  
          return fnv1a_32(bytes);
      }
  
      /**
       * FNV-1a 32-bit hash algorithm.
       *
       * @param data the data to hash
       * @return the hash
       */
      public static int fnv1a_32(byte[] data) {
          int hash = FNV1_32_INIT;
          for (byte datum : data) {
              hash ^= (datum & 0xff);
              hash *= FNV1_PRIME_32;
          }
          return hash;
      }
  
      /**
       * Shift int x to the left by 32 bits and int z to form a long value.
       *
       * @param x the int x
       * @param z the int z
       * @return the long
       */
      public static long hashXZ(int x, int z) {
          return ((long) x << 32) | (z & 0xffffffffL);
      }
  
      /**
       * Gets x from {@link #hashXZ(int, int)}.
       *
       * @param hashXZ a long value
       */
      public static int getXFromHashXZ(long hashXZ) {
          return (int) (hashXZ >> 32);
      }
  
      /**
       * Gets z from {@link #hashXZ(int, int)}.
       *
       * @param hashXZ a long value
       */
      public static int getZFromHashXZ(long hashXZ) {
          return (int) hashXZ;
      }
  
      /**
       * Calculate the hash of a pos in a chunk.
       *
       * @param x the x coordinate of the pos
       * @param z the z coordinate of the pos
       * @return the hash of a pos in a chunk
       */
      public static int hashChunkXYZ(@Range(from = 0, to = 15) int x, @Range(from = -8388608, to = 8388607) int y, @Range(from = 0, to = 15) int z) {
          Preconditions.checkArgument(x >= 0 && x <= 15);
          Preconditions.checkArgument(y >= -8388608 && y <= 8388607);
          Preconditions.checkArgument(z >= 0 && z <= 15);
          // Place x in the top 4 digits, y in the middle 24 bits, z in the lowest 4 digits
          return (x << 28) | (((y + 8388608) & 0xFFFFFF) << 4) | z;
      }
  
      /**
       * Extract the value of x from the hash chunk xyz.
       * x occupies the highest 4 bits.
       *
       * @param encoded Encoded int containing x, y, and z
       * @return The value of x
       */
      public static int getXFromHashChunkXYZ(int encoded) {
          return (encoded >>> 28);
      }
  
      /**
       * Extract the value of y from the hash chunk xyz.
       * y occupies the middle 24 bits.
       *
       * @param encoded Encoded int containing x, y, and z
       * @return The value of y
       */
      public static int getYFromHashChunkXYZ(int encoded) {
          return ((encoded >>> 4) & 0xFFFFFF) - 8388608;
      }
  
      /**
       * Extract the value of z from the hash chunk xyz.
       * z occupies the lowest 4 bits.
       *
       * @param encoded Encoded int containing x, y, and z
       * @return The value of z
       */
      public static int getZFromHashChunkXYZ(int encoded) {
          return encoded & 0xF;
      }
  
      /**
       * Calculate the hash of a pos in a {@link ChunkSection}.
       *
       * @param x the x coordinate of the pos
       * @param y the y coordinate of the pos
       * @param z the z coordinate of the pos
       * @return the hash of a pos in a {@link ChunkSection}
       */
      public static int hashChunkSectionXYZ(@Range(from = 0, to = 15) int x, @Range(from = 0, to = 15) int y, @Range(from = 0, to = 15) int z) {
          Preconditions.checkArgument(x >= 0 && x <= 15);
          Preconditions.checkArgument(y >= 0 && y <= 15);
          Preconditions.checkArgument(z >= 0 && z <= 15);
          return (x << 8) + (z << 4) + y;
      }
  }
  