package org.allaymc.server.block.hash;

import lombok.SneakyThrows;
import org.cloudburstmc.nbt.NBTOutputStream;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtUtils;

import java.io.ByteArrayOutputStream;
import java.util.TreeMap;

/**
 * @author daoge_cmd
 */
public class PaletteHashTest {

    private static final int FNV1_32_INIT = 0x811c9dc5;
    private static final int FNV1_PRIME_32 = 0x01000193;

    /**
     * Some info:
     * <p>
     * The compute method "Block#computeRawSerializationIdHashForNetwork()" does not account for collision and special cases.
     * That's why you should use instead the property I mentioned.
     * <p>
     * The special cases are just hardcoded as I wrote.
     * Other thing to account for is that when there is a collision one of the collided hashes is increased by 1.
     * <p>
     * Special cases:
     * <p>
     * minecraft:coral_fan;coral_color=blue;coral_fan_direction=0
     * minecraft:coral_fan_dead;coral_color=blue;coral_fan_direction=0
     * minecraft:unknown
     */

    public static void main(String[] args) {
        NbtMap b1 = NbtMap.builder()
                .putString("name", "minecraft:polished_basalt")
                .putCompound("states", NbtMap.builder()
                        .putString("pillar_axis", "x")
                        .build())
                .build();

        System.out.printf("b1 hash: %s%n", createHash(b1));

        NbtMap b2 = NbtMap.builder()
                .putString("name", "minecraft:blue_candle")
                .putCompound("states", NbtMap.builder()
                        .putInt("candles", 2)
                        .putBoolean("lit", false)
                        .build())
                .build();

        System.out.printf("b2 hash: %s%n", Integer.toUnsignedLong(createHash(b2)));

        NbtMap b3 = NbtMap.builder()
                .putString("name", "minecraft:coral_fan")
                .putCompound("states", NbtMap.builder()
                        .putString("coral_color", "blue")
                        .putInt("coral_fan_direction", 0)
                        .build())
                .build();

        //781710940
        System.out.printf("b3 hash: %s%n", Integer.toUnsignedLong(createHash(b3)));

        NbtMap b4 = NbtMap.builder()
                .putString("name", "minecraft:unknown")
                .putCompound("states", NbtMap.fromMap(new TreeMap<>()))
                .build();

        System.out.printf("b4 hash: %s%n", Integer.toUnsignedLong(createHash(b4)));
    }

    @SneakyThrows
    public static int createHash(NbtMap block) {
        if (block.getString("name").equals("minecraft:unknown")) {
            return -2; // This is special case
        }

        var states = new TreeMap<>(block.getCompound("states"));

        NbtMap tag = NbtMap.builder()
                .putString("name", block.getString("name"))
                .putCompound("states", NbtMap.fromMap(states))
                .build();

        byte[] bytes;
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
             NBTOutputStream outputStream = NbtUtils.createWriterLE(stream)) {
            outputStream.writeTag(tag);
            bytes = stream.toByteArray();
        }

        return fnv1a_32(bytes);
    }

    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;
    }
}
