package net.minecraft.network.syncher;

import net.minecraft.world.entity.Entity;

import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.network.syncher.DataWatcherHandle;
import com.bergerkiller.generated.net.minecraft.network.syncher.DataWatcherHandle.ItemHandle;
import com.bergerkiller.generated.net.minecraft.network.syncher.DataWatcherHandle.PackedItemHandle;
import com.bergerkiller.generated.net.minecraft.network.syncher.DataWatcherObjectHandle;

class DataWatcher {
#if version >= 1.14.4
    private final (EntityHandle) Entity owner:entity;
#elseif version >= 1.10.2
    private final (EntityHandle) Entity owner:c;
#elseif version >= 1.9
    private final (EntityHandle) Entity owner:b;
#else
    private final (EntityHandle) Entity owner:a;
#endif

    public (DataWatcherHandle) DataWatcher((EntityHandle) Entity owner);

    <code>
    public static DataWatcherHandle createNew(org.bukkit.entity.Entity owner) {
        return createHandle(T.constr_owner.raw.newInstance(com.bergerkiller.bukkit.common.conversion.type.HandleConversion.toEntityHandle(owner)));
    }
    </code>

#if version >= 1.18
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packChanges:packDirty();
#elseif version >= 1.9
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packChanges:b();
#else
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.WatchableObject> packChanges:b();
#endif

    // Note: only filters non-default elements on 1.19.3 and later, as this mechanism was absent prior
#if version >= 1.19.3
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packNonDefaults:getNonDefaultValues();
#elseif version >= 1.17
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packNonDefaults:getAll();
#elseif version >= 1.9
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packNonDefaults:c();
#else
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.WatchableObject> packNonDefaults:c();
#endif

#if version >= 1.19.3
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packAll() {
        // No method is available on this version, so use reflection to hack something together
        // Note: vanilla does some locking, doesn't appear to be used on spigot/paper
        #require DataWatcher private final it.unimi.dsi.fastutil.ints.Int2ObjectMap<DataWatcher.Item<?>> itemsById;
        it.unimi.dsi.fastutil.ints.Int2ObjectMap itemsById = instance#itemsById;
        java.util.ArrayList resultItems = new java.util.ArrayList(itemsById.size());
        it.unimi.dsi.fastutil.objects.ObjectIterator objectiterator = itemsById.values().iterator();
        while (objectiterator.hasNext()) {
            DataWatcher$Item item = (DataWatcher$Item) objectiterator.next();
            resultItems.add(item.value());
        }
        return resultItems;
    }

    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<?>>) List<DataWatcher.Item<?>> getCopyOfAllItems() {
        // No method is available on this version, so use reflection to hack something together
        // Note: vanilla does some locking, doesn't appear to be used on spigot/paper
        #require DataWatcher private final it.unimi.dsi.fastutil.ints.Int2ObjectMap<DataWatcher.Item<?>> itemsById;
        it.unimi.dsi.fastutil.ints.Int2ObjectMap itemsById = instance#itemsById;
        java.util.ArrayList resultItems = new java.util.ArrayList(itemsById.size());
        it.unimi.dsi.fastutil.objects.ObjectIterator objectiterator = itemsById.values().iterator();
        while (objectiterator.hasNext()) {
            DataWatcher$Item item = (DataWatcher$Item) objectiterator.next();
            #require DataWatcher.Item private final T itemInitialValue:initialValue;
            Object initialValue = item#itemInitialValue;

            DataWatcher$Item copy = new DataWatcher$Item(item.getAccessor(), initialValue);
            copy.setValue(item.getValue());
            copy.setDirty(item.isDirty());
            resultItems.add(copy);
        }
        return resultItems;
    }
#elseif version >= 1.17
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packAll:getAll();
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<?>>) List<DataWatcher.Item<?>> getCopyOfAllItems:getAll();
#elseif version >= 1.9
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.PackedItem<?>> packAll:c();
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<?>>) List<DataWatcher.Item<?>> getCopyOfAllItems:c();
#else
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<?>>) List<DataWatcher.WatchableObject> packAll:c();
    public (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<?>>) List<DataWatcher.WatchableObject> getCopyOfAllItems:c();
#endif

#if version >= 1.18
    private (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<T>) DataWatcher.Item<T> read:getItem((com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key);
#elseif version >= 1.13
    private (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<T>) DataWatcher.Item<T> read:b((com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key);
#elseif version >= 1.9
    private (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<T>) DataWatcher.Item<T> read:c((com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key);
#else
    private (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item<?>) DataWatcher.WatchableObject read:j((com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) int key);
#endif

    public void register(com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?> key, Object defaultValue) {
        if (key == null) {
            throw new IllegalArgumentException("key is null");
        }
#if version >= 1.18
        #require net.minecraft.network.syncher.DataWatcher private void register:define(DataWatcherObject<T> key, Object defaultValue);
#elseif version >= 1.9
        #require net.minecraft.network.syncher.DataWatcher private void register:registerObject(DataWatcherObject<T> key, Object defaultValue);
#else
        #require net.minecraft.network.syncher.DataWatcher private void register:a((DataWatcherObject<T>) int key, Object defaultValue);
#endif
        instance#register((DataWatcherObject) key.getRawHandle(), key.getType().getConverter().convertReverse(defaultValue));
    }

    public void set(com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?> key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key is null");
        }
        Object convertedValue = key.getType().getConverter().convertReverse(value);
#if version >= 1.9
        instance.set((DataWatcherObject) key.getRawHandle(), convertedValue);
#else
        int id = ((com.bergerkiller.bukkit.common.internal.proxy.DataWatcherObject) key.getRawHandle()).getId();
        instance.watch(id, convertedValue);
#endif
    }

    public Object get(com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?> key) {
        if (key == null) {
            throw new IllegalArgumentException("key is null");
        }
#if version >= 1.9
        Object rawValue = instance.get((DataWatcherObject) key.getRawHandle());
#else
        com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item item;
        Object itemRaw = com.bergerkiller.generated.net.minecraft.network.syncher.DataWatcherHandle.T.read.invoke(instance, key);
        item = (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item) itemRaw;
        Object rawValue = com.bergerkiller.bukkit.common.wrappers.DataWatcher.Item.getRawValue(item);
#endif
        return key.getType().getConverter().convert(rawValue);
    }

#if version >= 1.18
    public boolean isChanged:isDirty();
    public boolean isEmpty();
#else
    public boolean isChanged:a();
    public boolean isEmpty:d();
#endif

    class DataWatcher.Item {
#if version >= 1.17
        private optional final int typeId:###;
        private optional final int keyId:###;
        private optional final (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key:accessor;
        private optional T value;
        private boolean changed:dirty;
#elseif version >= 1.9
        private optional final int typeId:###;
        private optional final int keyId:###;
        private optional final (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key:a;
        private optional T value:b;
        private boolean changed:c;
#else
        private optional final int typeId:a;
        private optional final int keyId:b;
        private optional final (com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key:###;
        private optional Object value:c;
        private boolean changed:d;
#endif

        // Get/set value, use public methods for faster access
        // TODO: Verify it is actually faster than using reflection on the value field (!)
#if version >= 1.18
        public T getValue();
        public void setValue(T value);
#elseif version >= 1.9
        public T getValue:b();
        public void setValue:a(T value);
#else
        public Object getValue:b();
        public void setValue:a(Object value);
#endif

        // Creates a snapshot of the current value
#if version >= 1.19.3
        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem pack:value();
#elseif version >= 1.18
        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem pack:copy();
#elseif version >= 1.12
        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem pack:d();
#elseif version >= 1.9
        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem pack() {
            return new DataWatcher$PackedItem(instance.a(), instance.b());
        }
#else
        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem pack() {
            return new DataWatcher$PackedItem(instance.c(), instance.a(), instance.b());
        }
#endif
    }

    // Note: PackedItem class is the same as Item class internally on 1.19.2 and before
    class DataWatcher.PackedItem {
#select version >=
#case 1.19.3:  public T value();
#case 1.18:    public T value:getValue();
#case 1.9:     public T value:b();
#case else:    public Object value:b();
#endselect

        public (DataWatcherHandle.PackedItemHandle) DataWatcher.PackedItem cloneWithValue(T value) {
#select version >=
#case 1.19.3:  return new DataWatcher$PackedItem(instance.id(), instance.serializer(), value);
#case 1.18:    return new DataWatcher$PackedItem(instance.getAccessor(), value);
#case 1.9:     return new DataWatcher$PackedItem(instance.a(), value);
#case else:    return new DataWatcher$PackedItem(instance.c(), instance.a(), value);
#endselect
        }

        public boolean isForKey((com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key<?>) DataWatcherObject<T> key) {
#select version >=
#case 1.19.3:  return key != null && instance.id() == key.getId() && instance.serializer() == key.getSerializer();
#case 1.18:    return instance.getAccessor() == key;
#case 1.9:     return instance.a() == key;
#case else:    return key != null && instance.c() == key.getSerializerId() && instance.a() == key.getId();
#endselect
        }
    }
}

class DataWatcherObject {
#if version >= 1.18
    public int getId();
    public (Object) DataWatcherSerializer<T> getSerializer()
#elseif version >= 1.9
    public int getId:a();
    public (Object) DataWatcherSerializer<T> getSerializer:b()
#else
    public int getId();
    public (Object) Object getSerializer:getSerializer()
#endif
}

optional class DataWatcherRegistry {
#if version >= 1.18
    public static int getSerializerId:getSerializedId((Object) DataWatcherSerializer<?> paramDataWatcherSerializer);
#else
    public static int getSerializerId:b((Object) DataWatcherSerializer<?> paramDataWatcherSerializer);
#endif
}