package net.minecraft.server.level;

import net.minecraft.core.BlockPosition;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.chat.ChatMessage;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutOpenWindow;
import net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditor;
import net.minecraft.network.syncher.DataWatcherObject;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.network.PlayerConnection;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityLiving;
import net.minecraft.world.entity.EntityInsentient;
import net.minecraft.world.entity.player.EntityHuman;
import net.minecraft.world.entity.player.PlayerInventory;
import net.minecraft.world.inventory.ContainerAccess;
import net.minecraft.world.inventory.ContainerAnvil;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.World;

import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.wrappers.DataWatcher;
import com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key;
import com.bergerkiller.bukkit.common.wrappers.ChatText;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;

import com.bergerkiller.generated.com.mojang.authlib.GameProfileHandle;

import com.bergerkiller.generated.net.minecraft.server.level.EntityPlayerHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityPlayerHandle.RespawnConfigHandle;
import com.bergerkiller.generated.net.minecraft.server.network.PlayerConnectionHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.decoration.EntityItemFrameHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.item.EntityItemHandle;
import com.bergerkiller.generated.net.minecraft.world.inventory.ContainerHandle;
import com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle;
import com.bergerkiller.generated.net.minecraft.world.level.WorldHandle;

class EntityPlayer extends net.minecraft.world.entity.player.EntityHuman {

#if version >= 1.17
    public (PlayerConnectionHandle) PlayerConnection playerConnection:connection;
#else
    public (PlayerConnectionHandle) PlayerConnection playerConnection;
#endif

#if version >= 1.18
    public boolean hasDisconnected();
#elseif version >= 1.16
    public boolean hasDisconnected:q();
#elseif version >= 1.13
    public boolean hasDisconnected:o();
#elseif version >= 1.12
    public boolean hasDisconnected:t();
#else
    public boolean hasDisconnected() {
        PlayerConnection playerConnection = instance.playerConnection;
        return playerConnection != null && playerConnection.isDisconnected();
    }
#endif

#if version >= 1.21.5
    public (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig getRespawnConfig();

    //TODO: Add an API that also fires the new spawn change event?
    // The "cause" enum would have to be proxied as it doesn't exist pre-1.21.5
    public void setRespawnConfigSilent((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig respawnConfig) {
        #require net.minecraft.server.level.EntityPlayer private EntityPlayer.RespawnConfig respawnConfig;
        instance#respawnConfig = respawnConfig;
    }
#else
  #if version >= 1.17
      #require net.minecraft.server.level.EntityPlayer private net.minecraft.core.BlockPosition spawnCoord:respawnPosition;
      #require net.minecraft.server.level.EntityPlayer private boolean spawnForced:respawnForced;
  #elseif version >= 1.16
      #require net.minecraft.server.level.EntityPlayer private net.minecraft.core.BlockPosition spawnCoord:spawn;
      #require net.minecraft.server.level.EntityPlayer private boolean spawnForced;
  #elseif version >= 1.15
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:g;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:bR;
  #elseif version >= 1.14.4
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:g;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:bU;
  #elseif version >= 1.14
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:f;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:g;
  #elseif version >= 1.13
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:e;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:f;
  #elseif version >= 1.12
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:d;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:e;
  #elseif version >= 1.9
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:e;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:f;
  #else
      #require net.minecraft.world.entity.player.EntityHuman private net.minecraft.core.BlockPosition spawnCoord:c;
      #require net.minecraft.world.entity.player.EntityHuman private boolean spawnForced:d;
  #endif

  #if version >= 1.17
      #require net.minecraft.server.level.EntityPlayer private float spawnAngle:respawnAngle;
  #elseif version >= 1.16.2
      #require net.minecraft.server.level.EntityPlayer private float spawnAngle;
  #endif

    public (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig getRespawnConfig() {
        // World dimension on 1.16 and later
        // Before
        ResourceKey dimension;
        String worldName;
  #if version >= 1.18
        dimension = instance.getRespawnDimension();
        worldName = null;
  #elseif version >= 1.16
        dimension = instance.getSpawnDimension();
        worldName = null;
  #else
        //TODO: Do this conversion without first looking up the World?
        dimension = null;
        worldName = instance.spawnWorld;
        if (instance.spawnWorld != null) {
            org.bukkit.World bukkitSpawnWorld = org.bukkit.Bukkit.getWorld(instance.spawnWorld);
            if (bukkitSpawnWorld != null) {
                WorldServer spawnWorld = (WorldServer) ((org.bukkit.craftbukkit.CraftWorld) bukkitSpawnWorld).getHandle();
                dimension = (ResourceKey) com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.T.getDimensionKey.raw().invoke(spawnWorld);
            }
        }
  #endif

        if (dimension == null && worldName == null) return null;

        BlockPosition position = instance#spawnCoord;
        if (position == null) return null;

  #if version >= 1.16.2
        float angle = instance#spawnAngle;
  #else
        float angle = 0.0f;
  #endif

        boolean forced = instance#spawnForced;

        return new EntityPlayer$RespawnConfig(dimension, worldName, position, angle, forced);
    }

    public void setRespawnConfigSilent((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig respawnConfig) {
  #if version >= 1.17
        #require net.minecraft.server.level.EntityPlayer private net.minecraft.resources.ResourceKey<net.minecraft.world.level.World> spawnDimension:respawnDimension;
  #elseif version >= 1.16
        #require net.minecraft.server.level.EntityPlayer private net.minecraft.resources.ResourceKey<net.minecraft.world.level.World> spawnDimension;
  #endif

        boolean hasSpawnConfig;
  #if version >= 1.16
        ResourceKey dimension = null;
        if (respawnConfig != null) {
            dimension = (ResourceKey) respawnConfig.dimension();
        }

        hasSpawnConfig = (dimension != null);
  #else
        // Convert to spawn world name
        //TODO: Convert to world name without first looking up the World?
        String spawnWorldName = null;
        if (respawnConfig != null && respawnConfig.dimension() != null) {
            WorldServer world = (WorldServer) com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.T.getByDimensionKey.raw().invoke(respawnConfig.dimension());
            if (world != null) {
                spawnWorldName = world.getWorld().getName();
            }
        }

        hasSpawnConfig = (spawnWorldName != null);
  #endif

        if (!hasSpawnConfig) {
  #if version >= 1.16
            instance#spawnDimension = null;
  #else
            instance.spawnWorld = null;
  #endif
            instance#spawnCoord = null;
            instance#spawnForced = false;
  #if version >= 1.16.2
            instance#spawnAngle = 0.0f;
  #endif
            return;
        }

  #if version >= 1.16
        instance#spawnDimension = dimension;
  #else
        instance.spawnWorld = spawnWorldName;
  #endif

        instance#spawnCoord = (BlockPosition)respawnConfig.pos();
        instance#spawnForced = respawnConfig.forced();
  #if version >= 1.16.2
        instance#spawnAngle = respawnConfig.angle();
  #endif
    }
#endif

    <code>
    @Deprecated
    public void setSpawnForced(boolean forced) {
    }
    </code>

    public int getPing() {
#if version >= 1.20.2
        return instance.connection.latency();
#elseif version >= 1.17
        return instance.latency;
#else
        return instance.ping;
#endif
    }

#if version >= 1.17
    public readonly boolean viewingCredits:wonGame;
#else
    public readonly boolean viewingCredits;
#endif

    // Seen credits getter/setter
#if version >= 1.12
    // Since Minecraft 1.12 this is just a boolean property of the player
  #if version >= 1.17
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits;
  #elseif version >= 1.16.2
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cd;
  #elseif version >= 1.16.1
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:ck;
  #elseif version >= 1.15
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cm;
  #elseif version >= 1.14
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cp;
  #elseif version >= 1.13.1
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cx;
  #elseif version >= 1.13
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cy;
  #else
    #require net.minecraft.server.level.EntityPlayer private boolean seenCredits:cq;
  #endif

    public boolean hasSeenCredits() {
        return instance#seenCredits;
    }

    public void setHasSeenCredits(boolean hasSeen) {
        instance#seenCredits = hasSeen;
    }
#else
    // Older versions of Minecraft used the achievement list for this
    public boolean hasSeenCredits() {
  #if version >= 1.9
        return instance.a(AchievementList.D);
  #else
        return instance.getStatisticManager().hasAchievement(AchievementList.D);
  #endif
    }

    public void setHasSeenCredits(boolean hasSeen) {
        instance.getStatisticManager().setStatistic(instance, (Statistic) AchievementList.D, hasSeen ? 1 : 0);
    }
#endif

#if forge_nms_obfuscated
    public void sendMessage:a((ChatText) IChatBaseComponent ichatbasecomponent);
#elseif version >= 1.19.1
    public void sendMessage:sendSystemMessage((ChatText) IChatBaseComponent ichatbasecomponent);
#elseif version >= 1.19
    public void sendMessage((ChatText) IChatBaseComponent ichatbasecomponent) {
        net.minecraft.network.chat.PlayerChatMessage playerchatmessage = net.minecraft.network.chat.PlayerChatMessage.unsigned(ichatbasecomponent);
        IChatBaseComponent systemDisplayName = IChatBaseComponent.literal("");
        net.minecraft.network.chat.ChatSender chatsender = net.minecraft.network.chat.ChatSender.system(systemDisplayName);
        net.minecraft.resources.ResourceKey chatMessageType = net.minecraft.network.chat.ChatMessageType.SYSTEM;
        instance.sendChatMessage(playerchatmessage, chatsender, chatMessageType);
    }
#elseif version >= 1.17
    public void sendMessage((ChatText) IChatBaseComponent ichatbasecomponent) {
        instance.sendMessage(ichatbasecomponent, net.minecraft.SystemUtils.NIL_UUID);
    }
#elseif version >= 1.16
    public void sendMessage((ChatText) IChatBaseComponent ichatbasecomponent) {
        instance.sendMessage(ichatbasecomponent, SystemUtils.b);
    }
#else
    public void sendMessage((ChatText) IChatBaseComponent ichatbasecomponent);
#endif

#if exists net.minecraft.server.level.EntityPlayer public final java.util.List<Integer> removeQueue;
    public optional Collection<Integer> getRemoveQueue() {
        return instance.removeQueue;
    }
#elseif exists net.minecraft.server.level.EntityPlayer public final java.util.Deque<Integer> removeQueue;
    public optional Collection<Integer> getRemoveQueue() {
        return instance.removeQueue;
    }
#else
    public optional Collection<Integer> getRemoveQueue:###();
#endif

    public int getCurrentWindowId() {
#if version >= 1.17
        return ((EntityHuman) instance).containerMenu.containerId;
#else
        return instance.activeContainer.windowId;
#endif
    }

    public org.bukkit.inventory.InventoryView openAnvilWindow(ChatText titleText) {
        IChatBaseComponent title;
        if (titleText != null) {
            title = (IChatBaseComponent) titleText.getRawHandle();
        } else {
            String titleStr = (String) com.bergerkiller.generated.net.minecraft.world.level.block.BlockHandle.T.getTitle.invoke(Blocks.ANVIL);
#if version >= 1.19
            title = (IChatBaseComponent) IChatBaseComponent.translatable(titleStr);
#else
            title = (IChatBaseComponent) (new ChatMessage(titleStr, new Object[0]));
#endif
        }

#if version >= 1.17
        PlayerInventory playerInventory = instance.getInventory();
#else
        PlayerInventory playerInventory = instance.inventory;
#endif
#if version >= 1.20
        World world = instance.level();
        BlockPosition position = instance.blockPosition();
#elseif version >= 1.18
        World world = instance.getLevel();
        BlockPosition position = instance.blockPosition();
#else
        World world = instance.getWorld();
        BlockPosition position = instance.getChunkCoordinates();
#endif

#if exists net.minecraft.server.level.EntityPlayer public int nextContainerCounter();
        int windowId = instance.nextContainerCounter();
#elseif exists net.minecraft.server.level.EntityPlayer public int nextContainerCounterInt();
        int windowId = instance.nextContainerCounterInt();
#elseif exists net.minecraft.server.level.EntityPlayer public void nextContainerCounter();
        // void on forge, requires field reflection to get the counter value
        instance.nextContainerCounter();
        #require net.minecraft.server.level.EntityPlayer private int containerCounter;
        int windowId = instance#containerCounter;
#else
        // use #require so it logs the methods that do exist
        #require net.minecraft.server.level.EntityPlayer public int nextContainerCounter();
        int windowId;
        windowId = instance#nextContainerCounter();
#endif

#if version >= 1.18
        ContainerAccess access = ContainerAccess.create(world, position);
        ContainerAnvil container = new ContainerAnvil(windowId, playerInventory, access);
        container.setTitle(title);
#elseif version >= 1.14
        ContainerAccess access = ContainerAccess.at(world, position);
        ContainerAnvil container = new ContainerAnvil(windowId, playerInventory, access);
        container.setTitle(title);
#else
        ContainerAnvil container = new ContainerAnvil(playerInventory, world, position, (EntityHuman) instance);
        container.windowId = windowId;
#endif
        container.checkReachable = false;

        // Hook required so we can track when the text changes
#if version < 1.9
        com.bergerkiller.bukkit.common.internal.hooks.LegacyContainerAnvilHook hook;
        hook = new com.bergerkiller.bukkit.common.internal.hooks.LegacyContainerAnvilHook();
        container = (ContainerAnvil) hook.hook(container);
#endif

#if version >= 1.17
        ((EntityHuman) instance).containerMenu = container;
#else
        instance.activeContainer = container;
#endif

#if version >= 1.18
        instance.connection.send((Packet) new PacketPlayOutOpenWindow(windowId, net.minecraft.world.inventory.Containers.ANVIL, title));
#elseif version >= 1.17
        instance.connection.sendPacket((Packet) new PacketPlayOutOpenWindow(windowId, net.minecraft.world.inventory.Containers.ANVIL, title));
#elseif version >= 1.14
        instance.playerConnection.sendPacket((Packet) new PacketPlayOutOpenWindow(windowId, net.minecraft.world.inventory.Containers.ANVIL, title));
#else
        instance.playerConnection.sendPacket((Packet) new PacketPlayOutOpenWindow(windowId, "minecraft:anvil", title));
#endif

#if version >= 1.17
        instance.initMenu((net.minecraft.world.inventory.Container) container);
#else
        container.addSlotListener((net.minecraft.world.inventory.ICrafting) instance);
#endif

        return container.getBukkitView();
    }

    <code>
    public void closeSignEditWindow() {
        // Tell client to close the dialog by editing a sign far away in a chunk that 100% guaranteed is not loaded
        openSignEditWindow(IntVector3.of(Integer.MAX_VALUE, 0, Integer.MAX_VALUE));
    }

    public void openSignEditWindow(IntVector3 signPosition) {
        openSignEditWindow(signPosition, true);
    }
    </code>

    public void openSignEditWindow((IntVector3) BlockPosition signPosition, boolean isFrontText) {
#if version >= 1.20
        instance.connection.send((Packet) new PacketPlayOutOpenSignEditor(signPosition, isFrontText));
#else
        if (!isFrontText) {
            throw new UnsupportedOperationException("Back text not supported on this version of Minecraft");
        }

  #if version >= 1.18
        instance.connection.send((Packet) new PacketPlayOutOpenSignEditor(signPosition));
  #elseif version >= 1.17
        instance.connection.sendPacket((Packet) new PacketPlayOutOpenSignEditor(signPosition));
  #else
        instance.playerConnection.sendPacket((Packet) new PacketPlayOutOpenSignEditor(signPosition));
  #endif
#endif
    }

    <code>
    public static EntityPlayerHandle fromBukkit(org.bukkit.entity.Player player) {
        return createHandle(com.bergerkiller.bukkit.common.conversion.type.HandleConversion.toEntityHandle(player));
    }
    </code>

    // Since 1.21.5, before it uses a proxy class
    public class EntityPlayer.RespawnConfig {
#if version >= 1.21.9
        // Some layers of abstraction were introduced with 1.21.9

        public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> dimension() {
            return instance.respawnData().dimension();
        }
        public (IntVector3) BlockPosition position() {
            return instance.respawnData().pos();
        }
        public String worldName() {
            return null; // Not available
        }

        public float yaw() {
            return instance.respawnData().yaw();
        }
        public float pitch() {
            return instance.respawnData().pitch();
        }
        public boolean forced();

        public static (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig of((com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> dimension, String worldName, (IntVector3) BlockPosition position, float yaw, float pitch, boolean forced) {
            return new EntityPlayer$RespawnConfig(
                net.minecraft.world.level.storage.WorldData$RespawnData.of(dimension, position, yaw, pitch),
                forced );
        }

        public static (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig codecFromNBT((CommonTagCompound) NBTTagCompound nbt) {
            return (EntityPlayer$RespawnConfig) nbt.read("respawn", EntityPlayer$RespawnConfig.CODEC).orElse(null);
        }

        public static void codecToNBT((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig respawnConfig, (CommonTagCompound) NBTTagCompound nbt) {
            if (respawnConfig != null) {
                nbt.store("respawn", EntityPlayer$RespawnConfig.CODEC, respawnConfig);
            } else {
                nbt.remove("respawn");
            }
        }
#else
  #if version >= 1.21.5
        public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> dimension();
        public (IntVector3) BlockPosition position:pos();
        public String worldName() {
            return null; // Not available
        }
  #else
        public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> dimension() {
            return (ResourceKey) instance.dimension();
        }

        public (IntVector3) BlockPosition position() {
            return (BlockPosition) instance.pos();
        }

        public String worldName();
  #endif

        public float yaw:angle();
        public float pitch() {
            return 0.0f; // Not available
        }
        public boolean forced();

        public static (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig of((com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> dimension, String worldName, (IntVector3) BlockPosition position, float yaw, float pitch, boolean forced) {
  #if version >= 1.21.5
            return new EntityPlayer$RespawnConfig(dimension, position, yaw, forced);
  #else
            return new EntityPlayer$RespawnConfig(dimension, worldName, position, yaw, forced);
  #endif
        }

        public static (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig codecFromNBT((CommonTagCompound) NBTTagCompound nbt) {
  #if version >= 1.21.5
            return (EntityPlayer$RespawnConfig) nbt.read("respawn", EntityPlayer$RespawnConfig.CODEC).orElse(null);
  #else
            return null;
  #endif
        }

        public static void codecToNBT((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig respawnConfig, (CommonTagCompound) NBTTagCompound nbt) {
  #if version >= 1.21.5
            if (respawnConfig != null) {
                nbt.store("respawn", EntityPlayer$RespawnConfig.CODEC, respawnConfig);
            } else {
                nbt.remove("respawn");
            }
  #endif
        }
#endif

        <code>
        @Deprecated
        public static EntityPlayerHandle.RespawnConfigHandle of(com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World> dimension, String worldName, IntVector3 position, float yaw, boolean forced) {
            return of(dimension, worldName, position, yaw, 0.0f, forced);
        }

        public org.bukkit.World world() {
            return com.bergerkiller.bukkit.common.utils.WorldUtil.getWorldByDimensionKey(this.dimension());
        }

        public static EntityPlayerHandle.RespawnConfigHandle of(org.bukkit.World world, IntVector3 position, float angle, boolean forced) {
            return of(com.bergerkiller.bukkit.common.utils.WorldUtil.getDimensionKey(world), world.getName(), position, angle, forced);
        }
        </code>
    }
}
