package net.minecraft.world.level.block.entity;

import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.level.storage.ValueInput;

import net.minecraft.world.level.block.entity.Hopper;

import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;
import com.bergerkiller.bukkit.common.protocol.CommonPacket;
import com.bergerkiller.bukkit.common.wrappers.BlockData;
import com.bergerkiller.bukkit.common.wrappers.ChatText;

import com.bergerkiller.generated.net.minecraft.core.BlockPosHandle;
import com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle;
import com.bergerkiller.generated.net.minecraft.world.level.LevelHandle;
import com.bergerkiller.generated.net.minecraft.world.level.block.entity.SignBlockEntityHandle;

class BlockEntity {
#if version >= 1.17
    protected (org.bukkit.World) Level world_field:level;
#else
    protected (org.bukkit.World) Level world_field:world;
#endif

#if version >= 1.17
    protected (IntVector3) BlockPos position_field:worldPosition;
#else
    protected (IntVector3) BlockPos position_field:position;
#endif

#if version >= 1.18
    public (LevelHandle) Level getWorld:getLevel();
    public (BlockPosHandle) BlockPos getPosition:getBlockPos();
#else
    public (LevelHandle) Level getWorld();
    public (BlockPosHandle) BlockPos getPosition();
#endif

    public (BlockData) BlockState getBlockDataIfCached() {
#if version >= 1.17
        #require net.minecraft.world.level.block.entity.BlockEntity private BlockState cachedBlockData:blockState;
        return instance#cachedBlockData;
#elseif version >= 1.14
        #require net.minecraft.world.level.block.entity.BlockEntity private BlockState cachedBlockData:c;
        return instance#cachedBlockData;
#elseif version >= 1.13
        #require net.minecraft.world.level.block.entity.BlockEntity private BlockState cachedBlockData:f;
        return instance#cachedBlockData;
#else
  #if version >= 1.11
        #require net.minecraft.world.level.block.entity.BlockEntity private int cachedBlockDataValue:g;
        #require net.minecraft.world.level.block.entity.BlockEntity protected Block cachedBlockDataBlock:e;
  #else
        #require net.minecraft.world.level.block.entity.BlockEntity private int cachedBlockDataValue:h;
        #require net.minecraft.world.level.block.entity.BlockEntity protected Block cachedBlockDataBlock:e;
  #endif

        Block block = instance#cachedBlockDataBlock;
        if (block == null) {
            return null;
        }
        int value = instance#cachedBlockDataValue;
        if (value == -1) {
            return null;
        }
        return block.fromLegacyData(value);
#endif
    }

#if version >= 1.21.6
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound) {
        com.bergerkiller.bukkit.common.internal.logic.ScopedProblemReporter reporter = #createScopedProblemReporter();
        try {
            ValueInput input = #createTagValueInput(reporter, instance.getLevel().registryAccess(), nbttagcompound);
            instance.loadWithComponents(input);
        } finally {
            reporter.close();
        }
    }
#elseif version >= 1.20.5
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound) {
        instance.loadWithComponents(nbttagcompound, instance.getLevel().registryAccess());
    }
#elseif version >= 1.17
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound) {
        instance.load(nbttagcompound);
    }
#elseif version >= 1.16
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound);
#elseif version >= 1.12.1
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound) {
        instance.load(nbttagcompound);
    }
#else
    public void load((BlockData) BlockState blockData, (CommonTagCompound) CompoundTag nbttagcompound) {
        instance.a(nbttagcompound);
    }
#endif

#if version >= 1.21.6
    public (CommonTagCompound) CompoundTag save() {
        // Note: if ever removed, the same ProblemReporter crap as load()
        return instance.saveWithFullMetadata(instance.getLevel().registryAccess());
    }
#elseif version >= 1.20.5
    public (CommonTagCompound) CompoundTag save() {
        return instance.saveWithFullMetadata(instance.getLevel().registryAccess());
    }
#elseif version >= 1.18
    public final (CommonTagCompound) CompoundTag save:saveWithFullMetadata();
#else
    public (CommonTagCompound) CompoundTag save() {
        CompoundTag nbt = new CompoundTag();
  #if version >= 1.9.4
        return instance.save(nbt);
  #elseif version >= 1.9
        instance.save(nbt);
        return nbt;
  #else
        instance.b(nbt);
        return nbt;
  #endif
    }
#endif

#if version >= 1.18
    public (BlockData) BlockState getBlockData:getBlockState();
    public (Object) BlockState getRawBlockData:getBlockState();

    public (org.bukkit.Material) Block getType() {
        return instance.getBlockState().getBlock();
    }

    public optional int getLegacyData:###();

    public (CommonPacket) Packet<net.minecraft.network.protocol.game.ClientGamePacketListener> getUpdatePacket();
#elseif version >= 1.13
    public (BlockData) BlockState getBlockData:getBlock();
    public (Object) BlockState getRawBlockData:getBlock();

    public (org.bukkit.Material) Block getType() {
        return instance.getBlock().getBlock();
    }

    public optional int getLegacyData:###();

    public (CommonPacket) ClientboundBlockEntityDataPacket getUpdatePacket();
#else

    #require BlockEntity public BlockState getBlockDataImpl() {
        Block block;
        int rawData;
  #if version >= 1.11
        rawData = instance.v();
  #else
        rawData = instance.u();
  #endif
  #if version >= 1.9
        block = instance.getBlock();
  #else
        block = instance.w();
  #endif
        return block.fromLegacyData(rawData);
    }

    public (BlockData) BlockState getBlockData() {
        return instance#getBlockDataImpl();
    }
    public (Object) BlockState getRawBlockData() {
        return instance#getBlockDataImpl();
    }

    #if version >= 1.11
        public optional int getLegacyData:v();
    #else
        public optional int getLegacyData:u();
    #endif

    #if version >= 1.12.1
        public (org.bukkit.Material) Block getType:getBlock();
        public (CommonPacket) ClientboundBlockEntityDataPacket getUpdatePacket();
    #elseif version >= 1.9.4
        public (org.bukkit.Material) Block getType:getBlock();
        public (CommonPacket) ClientboundBlockEntityDataPacket getUpdatePacket();
    #elseif version >= 1.9
        public (org.bukkit.Material) Block getType:getBlock();
        public (CommonPacket) net.minecraft.network.protocol.Packet<?> getUpdatePacket();
    #else
        public (org.bukkit.Material) Block getType:w();
        public (CommonPacket) net.minecraft.network.protocol.Packet getUpdatePacket();
    #endif
#endif

#select version >=
#case 1.14:  public boolean isRemoved();
#case 1.13:  public boolean isRemoved:x();
#case 1.11:  public boolean isRemoved() { return instance.y() || !instance.u(); } // Must also validate world isn't null
#case else:  public boolean isRemoved() { return instance.x() || !instance.t(); } // Must also validate world isn't null
#endselect

    <code>
    @SuppressWarnings("deprecation")
    public org.bukkit.block.BlockState toBukkit() {
        return com.bergerkiller.bukkit.common.conversion.blockstate.BlockStateConversion.INSTANCE.tileEntityToBlockState(getRaw());
    }

    public static BlockEntityHandle fromBukkit(org.bukkit.block.BlockState blockState) {
        return createHandle(com.bergerkiller.bukkit.common.conversion.blockstate.BlockStateConversion.INSTANCE.blockStateToTileEntity(blockState));
    }
    </code>
}

class AbstractFurnaceBlockEntity extends BlockEntity {
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();

#if version >= 1.21.2
    public static int fuelTime((ItemStackHandle) ItemStack itemstack) {
        return net.minecraft.server.MinecraftServer.getServer().fuelValues().burnDuration(itemstack);
    }
#elseif version >= 1.14
    public static int fuelTime((ItemStackHandle) ItemStack itemstack) {
        if (itemstack.isEmpty()) {
            return 0;
        } else {
            Item item = itemstack.getItem();
  #if version >= 1.18
            Integer fuelTime = (Integer) AbstractFurnaceBlockEntity.getFuel().get(item);
  #else
            Integer fuelTime = (Integer) AbstractFurnaceBlockEntity.f().get(item);
  #endif
            if (fuelTime == null) {
                return 0;
            } else {
                return fuelTime.intValue();
            }
        }
    }
#elseif version >= 1.13
    private static int fuelTime((ItemStackHandle) ItemStack itemstack);
#else
    public static int fuelTime((ItemStackHandle) ItemStack itemstack);
#endif
}

class HopperBlockEntity extends BlockEntity {
#if version >= 1.18
    public static boolean suckItems:suckInItems((org.bukkit.World) Level world, (Object) Hopper ihopper);
#elseif version >= 1.17
    public static boolean suckItems:a((org.bukkit.World) Level world, (Object) Hopper ihopper);
#else
    public static boolean suckItems((org.bukkit.World) Level world, (Object) Hopper ihopper) {
        return HopperBlockEntity.a(ihopper);
    }
#endif
}

class SignBlockEntity extends BlockEntity {
    public Object[] getRawFrontLines() {
#if version >= 1.20
        return instance.getFrontText().getMessages(false);
#elseif version >= 1.17
        return (Object[]) instance.messages;
#else
        return (Object[]) instance.lines;
#endif
    }

    public String[] getMessageFrontLines() {
#if version >= 1.20
        Component[] lines = instance.getFrontText().getMessages(false);
#elseif version >= 1.17
        Component[] lines = instance.messages;
#else
        Component[] lines = instance.lines;
#endif
        return #formatLinesAsLegacyStrings(lines);
    }

<code>
    public static final Object[] ALL_EMPTY_RAW_LINES;
    public static final String[] ALL_EMPTY_STRING_LINES;
    static {
        Object raw_empty = ChatText.empty().getRawHandle();
        ALL_EMPTY_RAW_LINES = new Object[] {raw_empty, raw_empty, raw_empty, raw_empty};
        ALL_EMPTY_STRING_LINES = new String[] { "", "", "", "" };
    }
</code>

    public Object[] getRawBackLines() {
#if version >= 1.20
        return instance.getBackText().getMessages(false);
#else
        return SignBlockEntityHandle.ALL_EMPTY_RAW_LINES;
#endif
    }

    public String[] getMessageBackLines() {
#if version >= 1.20
        Component[] lines = instance.getBackText().getMessages(false);
        return #formatLinesAsLegacyStrings(lines);
#else
        return SignBlockEntityHandle.ALL_EMPTY_STRING_LINES;
#endif
    }

    public void setFormattedFrontLine(int index, (ChatText) Component text) {
#if version >= 1.20
        instance.setText(instance.getFrontText().setMessage(index, text), true);
#elseif version >= 1.18
        instance.setMessage(index, text);
        instance.setChanged();
#elseif version >= 1.17
        instance.a(index, text);
        instance.update();
#else
        instance.lines[index] = text;
        instance.update();
#endif
    }

    public void setFormattedBackLine(int index, (ChatText) Component text) {
#if version >= 1.20
        instance.setText(instance.getBackText().setMessage(index, text), false);
#endif
    }

    <code>
    @Override
    public org.bukkit.block.Sign toBukkit() {
        return (org.bukkit.block.Sign) super.toBukkit();
    }

    public static SignBlockEntityHandle fromBukkit(org.bukkit.block.Sign sign) {
        return createHandle(com.bergerkiller.bukkit.common.conversion.blockstate.BlockStateConversion.INSTANCE.blockStateToTileEntity(sign));
    }
    </code>
}