package net.minecraft.network.protocol.common;

import io.netty.buffer.Unpooled;

import net.minecraft.network.protocol.Packet;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.PacketDataSerializer;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.BukkitCustomPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.world.entity.player.EnumChatVisibility;

import com.bergerkiller.bukkit.common.wrappers.ChatText;
import com.bergerkiller.bukkit.common.wrappers.HumanHand;

import com.bergerkiller.generated.net.minecraft.network.protocol.common.ClientboundKeepAlivePacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.common.ClientboundCustomPayloadPacketHandle;
import com.bergerkiller.generated.net.minecraft.resources.MinecraftKeyHandle;

class ServerboundKeepAlivePacket extends Packet {
#if version >= 1.12.2
  #if version >= 1.17
    #require net.minecraft.network.protocol.common.ServerboundKeepAlivePacket private final long key:id;
  #else
    #require net.minecraft.network.protocol.common.ServerboundKeepAlivePacket private long key:a;
  #endif
    public long getKey() {
        return instance#key;
    }
    public void setKey(long key) {
        instance#key = key;
    }
#else
    #require net.minecraft.network.protocol.common.ServerboundKeepAlivePacket private int key:a;
    public long getKey() {
        return (long) instance#key;
    }
    public void setKey(long key) {
        instance#key = (int)key;
    }
#endif
}

class ClientboundKeepAlivePacket extends Packet {
#if version >= 1.12.2
  #if version >= 1.17
    #require net.minecraft.network.protocol.common.ClientboundKeepAlivePacket private final long key:id;
  #else
    #require net.minecraft.network.protocol.common.ClientboundKeepAlivePacket private long key:a;
  #endif
    public long getKey() {
        return instance#key;
    }
    public void setKey(long key) {
        instance#key = key;
    }
#else
    #require net.minecraft.network.protocol.common.ClientboundKeepAlivePacket private int key:a;
    public long getKey() {
        return (long) instance#key;
    }
    public void setKey(long key) {
        instance#key = (int)key;
    }
#endif

    public static (ClientboundKeepAlivePacketHandle) ClientboundKeepAlivePacket createNew(long key) {
#if version >= 1.12.2
        return new ClientboundKeepAlivePacket(key);
#else
        return new ClientboundKeepAlivePacket((int) key);
#endif
    }
}

class ServerboundResourcePackPacket extends Packet {
    private optional String message:a;

#if version >= 1.17
    public (Object) ServerboundResourcePackPacket.EnumResourcePackStatus status:action;
#elseif version >= 1.9
    public (Object) ServerboundResourcePackPacket.EnumResourcePackStatus status;
#else
    public (Object) ServerboundResourcePackPacket.EnumResourcePackStatus status:b;
#endif
}

// Note: on 1.20.2 and before, this same packet SETS a resource pack, and doesn't support
//       pushing multiple resource packs. Everything else is identical.
class ClientboundResourcePackPushPacket extends Packet {
#if version >= 1.17
    private final String url;
    private final String hash;
#else
    private String url:a;
    private String hash:b;
#endif

#if version >= 1.20.5
    #require net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket private final boolean required;
    #require net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket private final java.util.Optional<IChatBaseComponent> prompt;

    public void setRequired(boolean required) {
        instance#required = required;
    }

    public boolean isRequired() {
        return instance#required;
    }

    public void setPrompt((ChatText) IChatBaseComponent prompt) {
        java.util.Optional opt = java.util.Optional.ofNullable(prompt);
        instance#prompt = opt;
    }

    public (ChatText) IChatBaseComponent getPrompt() {
        java.util.Optional opt = instance#prompt;
        return (IChatBaseComponent) opt.orElse(null);
    }
#elseif version >= 1.17
    #require net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket private final boolean required;
    #require net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket private final IChatBaseComponent prompt;

    public void setRequired(boolean required) {
        instance#required = required;
    }

    public boolean isRequired() {
        return instance#required;
    }

    public void setPrompt((ChatText) IChatBaseComponent prompt) {
        instance#prompt = prompt;
    }

    public (ChatText) IChatBaseComponent getPrompt() {
        return instance#prompt;
    }
#else
    public void setRequired(boolean required) {
    }

    public boolean isRequired() {
        return false;
    }

    public void setPrompt((ChatText) IChatBaseComponent prompt) {
    }

    public (ChatText) IChatBaseComponent getPrompt() {
        return null;
    }
#endif

#if version >= 1.20.3
    public java.util.UUID getId:id();
#else
    public java.util.UUID getId() {
        return null; // Unsupported
    }
#endif
}

// Since Minecraft 1.20.3
optional class ClientboundResourcePackPopPacket {
    public java.util.Optional<java.util.UUID> getId:id();
}

class ClientboundCustomPayloadPacket extends Packet {
    // Get channel name (Bukkit custom payload packet only)
    public String getChannel() {
#if version >= 1.20.5
        CustomPacketPayload payload = instance.payload();
        if (payload instanceof DiscardedPayload) {
            net.minecraft.resources.MinecraftKey channel = ((DiscardedPayload) payload).id();
            return (channel == null) ? null : channel.toString();
        }
        return null;
#elseif version >= 1.20.2
        net.minecraft.resources.MinecraftKey channel = instance.payload().id();
        return (channel == null) ? null : channel.toString();
#elseif version >= 1.13
    #select version >=
    #case 1.17:    #require ClientboundCustomPayloadPacket private final net.minecraft.resources.MinecraftKey channel:identifier;
    #case 1.15:    #require ClientboundCustomPayloadPacket private net.minecraft.resources.MinecraftKey channel:r;
    #case 1.14.1:  #require ClientboundCustomPayloadPacket private net.minecraft.resources.MinecraftKey channel:n;
    #case 1.14:    #require ClientboundCustomPayloadPacket private net.minecraft.resources.MinecraftKey channel:m;
    #case else:    #require ClientboundCustomPayloadPacket private net.minecraft.resources.MinecraftKey channel:i;
    #endselect
        net.minecraft.resources.MinecraftKey channel = instance#channel;
        return (channel == null) ? null : channel.toString();
#else
        #require ClientboundCustomPayloadPacket private String channel:a;
        return instance#channel;
#endif
    }

    // Read raw payload
    public byte[] getMessage() {
#if version >= 1.20.5
        CustomPacketPayload payload = instance.payload();
        if (payload instanceof DiscardedPayload) {
            DiscardedPayload discarded = (DiscardedPayload) payload;
  #if exists net.minecraft.network.protocol.common.custom.DiscardedPayload public byte[] data();
    #if forge && exists net.minecraft.network.protocol.common.custom.DiscardedPayload public byte[] bridge$getData();
            return discarded.bridge$getData();
    #else
            return discarded.data();
    #endif
  #else
    #if forge && exists net.minecraft.network.protocol.common.custom.DiscardedPayload public io.netty.buffer.ByteBuf bridge$getData();
            io.netty.buffer.ByteBuf payloadData = discarded.bridge$getData();
    #else
            io.netty.buffer.ByteBuf payloadData = discarded.data();
    #endif
            byte[] data = new byte[payloadData.readableBytes()];
            payloadData.readBytes(data);
            return data;
  #endif
        } else {
            return null; // Ew.
        }
#elseif version >= 1.20.2
        CustomPacketPayload payload = instance.payload();
        if (payload instanceof BukkitCustomPayload) {
            #require BukkitCustomPayload private final byte[] message:val$message;
            return payload#message;
        } else {
            // Use serializer with a byte buffer
            io.netty.buffer.ByteBuf buf = Unpooled.buffer();
            PacketDataSerializer serializer = new PacketDataSerializer(buf);
            payload.write(serializer);
            return java.util.Arrays.copyOf(buf.array(), buf.writerIndex());
        }
#else
    #select version >=
    #case 1.17:    #require ClientboundCustomPayloadPacket private final PacketDataSerializer payload_data:data;
    #case 1.15:    #require ClientboundCustomPayloadPacket private PacketDataSerializer payload_data:s;
    #case 1.14.1:  #require ClientboundCustomPayloadPacket private PacketDataSerializer payload_data:o;
    #case 1.14:    #require ClientboundCustomPayloadPacket private PacketDataSerializer payload_data:n;
    #case 1.13:    #require ClientboundCustomPayloadPacket private PacketDataSerializer payload_data:j;
    #case else:    #require ClientboundCustomPayloadPacket private PacketDataSerializer payload_data:b;
    #endselect
        PacketDataSerializer payload = instance#payload_data;
        return payload.array();
#endif
    }

    // Constructor
    public static (ClientboundCustomPayloadPacketHandle) ClientboundCustomPayloadPacket createNew(String channel, byte[] message) {
#if version >= 1.20.2
        net.minecraft.resources.MinecraftKey key;
        key = (net.minecraft.resources.MinecraftKey) MinecraftKeyHandle.createNew(channel).getRaw();

  #if version >= 1.20.5
    #if exists net.minecraft.network.protocol.common.custom.DiscardedPayload public DiscardedPayload(net.minecraft.resources.MinecraftKey id, byte[] data);
        CustomPacketPayload payload = new DiscardedPayload(key, message);
    #else
        CustomPacketPayload payload = new DiscardedPayload(key, Unpooled.wrappedBuffer(message));
    #endif
  #elseif exists net.minecraft.network.protocol.common.custom.BukkitCustomPayload BukkitCustomPayload(org.bukkit.craftbukkit.entity.CraftPlayer p, byte[] message, net.minecraft.resources.MinecraftKey channel);
        // Warning: this type is fake and is remapped in CommonBootstrap to an anonymous class in CraftPlayer!
        // If the server was built with a newer JDK the CraftPlayer argument may have been dropped as well - future-proof it!
        #require BukkitCustomPayload BukkitCustomPayload createCustom:<init>(org.bukkit.craftbukkit.entity.CraftPlayer p, byte[] message, net.minecraft.resources.MinecraftKey channel);
        CustomPacketPayload payload = BukkitCustomPayload#createCustom(null, message, key);
  #elseif exists net.minecraft.network.protocol.common.custom.BukkitCustomPayload BukkitCustomPayload(byte[] message, net.minecraft.resources.MinecraftKey channel);
        // Warning: this type is fake and is remapped in CommonBootstrap to an anonymous class in CraftPlayer!
        // If the server was built with a newer JDK the CraftPlayer argument may have been dropped as well - future-proof it!
        #require BukkitCustomPayload BukkitCustomPayload createCustom:<init>(byte[] message, net.minecraft.resources.MinecraftKey channel);
        CustomPacketPayload payload = BukkitCustomPayload#createCustom(message, key);
  #else
        #error Missing BukkitCustomPayload constructor we can use!
  #endif

        return new ClientboundCustomPayloadPacket(payload);
#else
        PacketDataSerializer serializer = new PacketDataSerializer(Unpooled.wrappedBuffer(message));

  #if version >= 1.13
        net.minecraft.resources.MinecraftKey key;
        key = (net.minecraft.resources.MinecraftKey) MinecraftKeyHandle.createNew(channel).getRaw();
        return new ClientboundCustomPayloadPacket(key, serializer);
  #else
        return new ClientboundCustomPayloadPacket(channel, serializer);
  #endif
#endif
    }

    <code>
    @Override
    public com.bergerkiller.bukkit.common.protocol.PacketType getPacketType() {
        return com.bergerkiller.bukkit.common.protocol.PacketType.OUT_CUSTOM_PAYLOAD;
    }
    </code>
}

class ClientboundDisconnectPacket extends Packet {
#if version >= 1.17
  private (ChatText) IChatBaseComponent reason;
#else
  private (ChatText) IChatBaseComponent reason:a;
#endif
}

class ServerboundClientInformationPacket extends Packet {
#if version >= 1.20.2
    public String getLocale() { return instance.information().language(); }
    public int getView() { return instance.information().viewDistance(); }
    public (Object) EnumChatVisibility getChatVisibility() { return instance.information().chatVisibility(); }
    public boolean getEnableColors() { return instance.information().chatColors(); }
    public int getModelPartFlags() { return instance.information().modelCustomisation(); }
#else
  #if version >= 1.17
    #require ServerboundClientInformationPacket private readonly String locale:language;
  #elseif version >= 1.16
    #require ServerboundClientInformationPacket private readonly String locale;
  #else
    #require ServerboundClientInformationPacket private readonly String locale:a;
  #endif
    public String getLocale() { return instance#locale; }

  #if version >= 1.17
    #require ServerboundClientInformationPacket private readonly int view:viewDistance;
  #elseif fieldexists net.minecraft.network.protocol.game.PacketPlayInSettings public int viewDistance
    #require ServerboundClientInformationPacket public readonly int view:viewDistance;
  #elseif fieldexists net.minecraft.network.protocol.game.PacketPlayInSettings private int viewDistance
    #require ServerboundClientInformationPacket private readonly int view:viewDistance;
  #else
    #require ServerboundClientInformationPacket private readonly int view:b;
  #endif
    public int getView() { return instance#view; }

  #if version >= 1.17
    #require ServerboundClientInformationPacket private readonly EnumChatVisibility chatVisibility;
    #require ServerboundClientInformationPacket private readonly boolean enableColors:chatColors;
    #require ServerboundClientInformationPacket private readonly int modelPartFlags:modelCustomisation;
  #else
    #require ServerboundClientInformationPacket private readonly EnumChatVisibility chatVisibility:c;
    #require ServerboundClientInformationPacket private readonly boolean enableColors:d;
    #require ServerboundClientInformationPacket private readonly int modelPartFlags:e;
  #endif
    public (Object) EnumChatVisibility getChatVisibility() { return instance#chatVisibility; }
    public boolean getEnableColors() { return instance#enableColors; }
    public int getModelPartFlags() { return instance#modelPartFlags; }

    //TODO: textFilteringEnabled
#endif

#if version >= 1.9
    public (HumanHand) net.minecraft.world.entity.EnumMainHand getMainHand() {
  #if version >= 1.17
        #require ServerboundClientInformationPacket private net.minecraft.world.entity.EnumMainHand mainHand;
  #else
        #require ServerboundClientInformationPacket private net.minecraft.world.entity.EnumMainHand mainHand:f;
  #endif
  #if version >= 1.20.2
        return instance.information().mainHand();
  #else
        return instance#mainHand;
  #endif
    }
#else
    public (HumanHand) HumanHand getMainHand() {
        return HumanHand.RIGHT;
    }
#endif
}
