package net.minecraft.world.level.block;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootParams$Builder;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootContext$Builder;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParameterSet;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.Vec3;

import net.minecraft.world.level.block.EntityBlock;

import com.bergerkiller.bukkit.common.wrappers.BlockData;
import com.bergerkiller.bukkit.common.bases.IntVector3

import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.world.level.block.state.BlockStateHandle;
import com.bergerkiller.generated.net.minecraft.world.level.LevelHandle;

class Block {
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();

    public static final Iterable<?> getRegistry() {
#select version >=
#case 1.14:   #require net.minecraft.core.registries.BuiltInRegistries public static final net.minecraft.core.DefaultedRegistry<net.minecraft.world.level.block.Block> REGISTRY:BLOCK;
#case 1.13.1: #require net.minecraft.core.Registry public static final net.minecraft.core.Registry<net.minecraft.world.level.block.Block> REGISTRY:BLOCK;
#case else:   #require net.minecraft.world.level.block.Block public static final net.minecraft.core.DefaultedRegistry REGISTRY;
#endselect
        return #REGISTRY;
    }

#if version >= 1.17
    public static final rawtype (Iterable) net.minecraft.core.IdMapper<BlockState> REGISTRY_ID:BLOCK_STATE_REGISTRY;
#elseif version >= 1.9
    public static final rawtype (Iterable) net.minecraft.core.IdMapper<BlockState> REGISTRY_ID;
#else
    public static final rawtype (Iterable) net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap<BlockState> REGISTRY_ID:d;
#endif

#select version >=
#case 1.18: public String getTitle:getDescriptionId();
#case 1.17: public String getTitle:h();
#case 1.16: public String getTitle:i();
#case 1.15: public String getTitle:k();
#case 1.14: public String getTitle:l();
#case 1.13: public String getTitle:m();
#case else
    public String getTitle() {
        StringBuilder str = new StringBuilder();
        str.append(instance.a());
        str.append(".name");
        return str.toString();
    }
#endselect

    public boolean isFaceOpaque((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z, (org.bukkit.block.BlockFace) Direction direction) {
  #if version >= 1.18
        // Note: uses this.p(), which is protected. More efficient to just call the method on the iblockdata
        return iblockdata.isFaceSturdy((BlockGetter)world, new BlockPos(x, y, z), direction);
  #elseif version >= 1.16
        // Note: uses this.p(), which is protected. More efficient to just call the method on the iblockdata
        return iblockdata.d((BlockGetter)world, new BlockPos(x, y, z), direction);
  #elseif version >= 1.14
        // Since 1.14 individual faces can be opaque or not
        return Block.d(iblockdata, (BlockGetter)world, new BlockPos(x, y, z), direction);
  #elseif version >= 1.9
        // 1.13.2 and earlier, either all faces are opaque, or none are
        return instance.isOccluding(iblockdata);
  #else
        return instance.isOccluding();
  #endif
    }

#if version >= 1.18
    public int getOpacity((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
  #if version >= 26.1
        // Note: not really called, as the getCachedOpacity() always succeeds
        return iblockdata.getLightDampening();
  #elseif version >= 1.21.2
        // Note: not really called, as the getCachedOpacity() always succeeds
        return iblockdata.getLightBlock();
  #else
        return iblockdata.getLightBlock((BlockGetter) world, new BlockPos(x, y, z));
  #endif
    }

    public int getEmission((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        // Forge
  #if exists net.minecraft.world.level.block.Block public int getLightValue(BlockState data, BlockGetter world, BlockPos pos);
        if (world != null) {
            return instance.getLightValue(iblockdata, world, new BlockPos(x, y, z));
        }
  #endif

        return iblockdata.getLightEmission();
    }

    public boolean isOccluding_at((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        //TODO: This has been broken for quite a while it seems. By chance it works (most redstone conductors are solid...)
        //      What to do with this mess?
        return iblockdata.isRedstoneConductor(world, new BlockPos(x, y, z));
    }

    public boolean isOccluding((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block) {
        BlockGetter iblockaccess = block.getCraftWorld().getHandle();
        BlockPos blockposition = block.getPosition();
        //TODO: See above
        return iblockdata.isRedstoneConductor(iblockaccess, blockposition);
    }

#elseif version >= 1.14
    public int getOpacity((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
  #if version >= 1.17
        return instance.g(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
  #elseif version >= 1.16
        return instance.f(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
  #elseif version >= 1.15
        return instance.l(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
  #else
        return instance.k(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
  #endif
    }

    public int getEmission((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        // Forge
  #if exists net.minecraft.world.level.block.Block public int getLightValue(BlockState data, BlockGetter world, BlockPos pos);
        if (world != null) {
            return instance.getLightValue(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
        }
  #endif

  #if version >= 1.16
        return iblockdata.f();
  #else
        return instance.a(iblockdata);
  #endif
    }

    public boolean isOccluding_at((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
    #if version >= 1.16
        return iblockdata.isOccluding((BlockGetter) world, new BlockPos(x, y, z));
    #else
        return instance.isOccluding(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
    #endif
    }

    public boolean isOccluding((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block) {
        BlockGetter iblockaccess = block.getCraftWorld().getHandle();
        BlockPos blockposition = block.getPosition();
    #if version >= 1.16
        return iblockdata.isOccluding(iblockaccess, blockposition);
    #else
        return instance.isOccluding(iblockdata, iblockaccess, blockposition);
    #endif
    }

#elseif version >= 1.13
    // Since 1.13 getOpacity has a BlockPos argument
    public int getOpacity((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        return iblockdata.b((BlockGetter) world, new BlockPos(x, y, z));
    }

    public int getEmission((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        // Forge
    #if exists net.minecraft.world.level.block.Block public int getLightValue(BlockState data, BlockGetter world, BlockPos pos);
        if (world != null) {
            return instance.getLightValue(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
        }
    #endif

    #if pre <= 7
        return instance.l(iblockdata);
    #else
        return instance.m(iblockdata);
    #endif
    }

    public boolean isOccluding_at((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        return instance.isOccluding(iblockdata);
    }

    public boolean isOccluding((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block) {
        return instance.isOccluding(iblockdata);
    }

#elseif version >= 1.9
    // On later versions, it has an BlockState argument
    public int getOpacity((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
  #if exists net.minecraft.world.level.block.Block public int getLightOpacity(BlockState data, BlockGetter world, BlockPos pos);
        // Forge
        return Math.min(15, instance.getLightOpacity(iblockdata, (BlockGetter) world, new BlockPos(x, y, z)));
  #else
        return Math.min(15, instance.m(iblockdata));
  #endif
    }
    public int getEmission((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        // Forge
    #if exists net.minecraft.world.level.block.Block public int getLightValue(BlockState data, BlockGetter world, BlockPos pos);
        if (world != null) {
            return instance.getLightValue(iblockdata, world, new BlockPos(x, y, z));
        }
    #endif

        return instance.o(iblockdata);
    }

    public boolean isOccluding_at((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        return instance.isOccluding(iblockdata);
    }

    public boolean isOccluding((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block) {
        return instance.isOccluding(iblockdata);
    }

#else
    // By BlockState does not exist; proxy to the empty args version
    public int getOpacity((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
  #if exists net.minecraft.world.level.block.Block public int getLightOpacity(BlockState data, BlockGetter world, BlockPos pos);
        // Forge
        return Math.min(15, instance.getLightOpacity(iblockdata, (BlockGetter) world, new BlockPos(x, y, z)));
  #elseif version >= 1.8.3
        return Math.min(15, instance.p());
  #else
        return Math.min(15, instance.n());
  #endif
    }

    public int getEmission((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        // Forge
  #if exists net.minecraft.world.level.block.Block public int getLightValue(BlockState data, BlockGetter world, BlockPos pos);
        if (world != null) {
            return instance.getLightValue(iblockdata, (BlockGetter) world, new BlockPos(x, y, z));
        }
  #endif

  #if version >= 1.8.3
        return instance.r();
  #else
        return instance.p();
  #endif
    }

    public boolean isOccluding_at((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, int x, int y, int z) {
        return instance.isOccluding();
    }

    public boolean isOccluding((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block) {
        return instance.isOccluding();
    }
#endif

#if version >= 1.18
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block, (org.bukkit.block.BlockFace) Direction direction) {
        BlockGetter iblockaccess = block.getCraftWorld().getHandle();
        BlockPos blockposition = block.getPosition();
        return iblockaccess.getBlockState(blockposition).isFaceSturdy(iblockaccess, blockposition, direction, SupportType.RIGID);
    }
#elseif version >= 1.16.2
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block, (org.bukkit.block.BlockFace) Direction direction) {
        BlockGetter iblockaccess = block.getCraftWorld().getHandle();
        BlockPos blockposition = block.getPosition();
        return iblockaccess.getType(blockposition).a(iblockaccess, blockposition, direction, SupportType.RIGID);
    }
#elseif version >= 1.14
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block, (org.bukkit.block.BlockFace) Direction direction) {
        if (iblockdata.a(TagsBlock.LEAVES)) {
            return false;
        }

  #if version >= 1.16
        #require net.minecraft.world.level.block.Block private static final net.minecraft.world.phys.shapes.VoxelShape checkShape:b;
  #else
        #require net.minecraft.world.level.block.Block private static final net.minecraft.world.phys.shapes.VoxelShape checkShape:c;
  #endif
        VoxelShape checkShape = Block#checkShape;

        BlockGetter iblockaccess = block.getCraftWorld().getHandle();
        BlockPos blockposition = block.getPosition();
        VoxelShape shape = iblockdata.getCollisionShape(iblockaccess, blockposition);
        return !Shapes.c(shape.a(direction), checkShape, BooleanOp.ONLY_SECOND);
    }

#elseif version >= 1.12
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block, (org.bukkit.block.BlockFace) Direction direction) {
        if (direction == Direction.UP) {
  #if version >= 1.13
            return instance.r(iblockdata);
  #else
            return instance.k(iblockdata);
  #endif
        } else {
  #if version >= 1.13
            BlockGetter iblockaccess = block.getCraftWorld().getHandle();
  #else
            BlockGetter iblockaccess = ((org.bukkit.craftbukkit.CraftWorld) block.getWorld()).getHandle();
  #endif
  #if version >= 1.13.1
            BlockPos blockposition = block.getPosition();
  #else
            BlockPos blockposition = new BlockPos(block.getX(), block.getY(), block.getZ());
  #endif
            EnumBlockFaceShape shape = instance.a(iblockaccess, iblockdata, blockposition, direction);
            return shape ==  EnumBlockFaceShape.SOLID;
        }
    }

#elseif version >= 1.9
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, (org.bukkit.block.Block) org.bukkit.craftbukkit.block.CraftBlock block, (org.bukkit.block.BlockFace) Direction direction) {
        // See also BlockButtonAbstract for why this is
        if (direction == Direction.UP) {
            return instance.k(iblockdata);
        } else {
            return instance.isOccluding(iblockdata);
        }
    }

#else
    public boolean canSupportOnFace((BlockStateHandle) BlockState iblockdata, org.bukkit.block.Block block, (org.bukkit.block.BlockFace) Direction direction) {
        if (direction != Direction.UP) {
            // See also BlockButtonAbstract for why this is
            return instance.isOccluding();
        }

        Block nms_block = iblockdata.getBlock();
        if (nms_block.getMaterial().k() && nms_block.d()) {
            return true;
        }

        // Enums changed location as of 1.8.3
  #if version >= 1.8.3
        if (nms_block instanceof BlockStairs) {
            return iblockdata.get(BlockStairs.HALF) == BlockStairs.EnumHalf.TOP;
        }
        if (nms_block instanceof BlockStepAbstract) {
            return iblockdata.get(BlockStepAbstract.HALF) == BlockStepAbstract.EnumSlabHalf.TOP;
        }
  #else
        if (block instanceof BlockStairs) {
            return iblockdata.get(BlockStairs.HALF) == EnumHalf.TOP;
        }
        if (block instanceof BlockStepAbstract) {
            return iblockdata.get(BlockStepAbstract.HALF) == EnumSlabHalf.TOP;
        }
  #endif
        if (block instanceof BlockHopper) {
            return true;
        }
        if (block instanceof BlockSnow) {
            return ((Integer) iblockdata.get(BlockSnow.LAYERS)).intValue() == 7;
        }
        return false;
    }
#endif

#if version >= 1.18
    public float getDamageResillience:getExplosionResistance();
#elseif version >= 1.13.1
    public float getDamageResillience:getDurability();
#elseif version >= 1.13
    #if pre <= 7
        public float getDamageResillience:j();
    #else
        public float getDamageResillience:k();
    #endif
#else
    public float getDamageResillience() {
        return instance.a((net.minecraft.world.entity.Entity) null);
    }
#endif

#if version >= 1.14
    // Since 1.14 it uses a LootContext to store all the properties for the dropping of items
    // Call that one. See Explosion.java for details
    public void dropNaturally((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) net.minecraft.server.level.ServerLevel world, (IntVector3) BlockPos blockposition, float yield, int chance) {
        Block block = iblockdata.getBlock();
  #if version >= 1.17
        ItemStack EMPTY_ITEM = ItemStack.EMPTY;
  #elseif version >= 1.16
        ItemStack EMPTY_ITEM = ItemStack.b;
  #else
        ItemStack EMPTY_ITEM = ItemStack.a;
  #endif

        boolean isTileEntity;
  #if version >= 1.17
        isTileEntity = (block instanceof EntityBlock);
  #else
        isTileEntity = block.isTileEntity();
  #endif

  #if version >= 1.20
        net.minecraft.world.level.block.entity.BlockEntity tileentity = isTileEntity ? world.getBlockEntity(blockposition) : null;
        LootParams$Builder lootparams_builder = new LootParams$Builder(world);
        lootparams_builder.withParameter(LootContextParams.ORIGIN, (Object) Vec3.atCenterOf(blockposition));
        lootparams_builder.withParameter(LootContextParams.TOOL, (Object) EMPTY_ITEM);
        lootparams_builder.withOptionalParameter(LootContextParams.BLOCK_ENTITY, (Object) tileentity);
        lootparams_builder.withParameter(LootContextParams.EXPLOSION_RADIUS, (Object) Float.valueOf(1.0F / yield));

        java.util.Iterator items_iter = iblockdata.getDrops(lootparams_builder).iterator();
        while (items_iter.hasNext()) {
            ItemStack item = (ItemStack) items_iter.next();
            Block.popResource((Level) world, blockposition, item);
        }
        iblockdata.spawnAfterBreak(world, blockposition, EMPTY_ITEM, true);
  #elseif version >= 1.18
        net.minecraft.world.level.block.entity.BlockEntity tileentity = isTileEntity ? world.getBlockEntity(blockposition) : null;
        LootContext$Builder loottableinfo_builder = new LootContext$Builder(world);
        loottableinfo_builder = loottableinfo_builder.withRandom(((Level) world).random);
        loottableinfo_builder.withParameter(LootContextParams.ORIGIN, (Object) Vec3.atCenterOf(blockposition));
        loottableinfo_builder.withParameter(LootContextParams.TOOL, (Object) EMPTY_ITEM);
        loottableinfo_builder.withOptionalParameter(LootContextParams.BLOCK_ENTITY, (Object) tileentity);
        loottableinfo_builder.withParameter(LootContextParams.EXPLOSION_RADIUS, (Object) Float.valueOf(1.0F / yield));
        java.util.Iterator items_iter = iblockdata.getDrops(loottableinfo_builder).iterator();
        while (items_iter.hasNext()) {
            ItemStack item = (ItemStack) items_iter.next();
            Block.popResource((Level) world, blockposition, item);
        }
    #if version >= 1.19
        iblockdata.spawnAfterBreak(world, blockposition, EMPTY_ITEM, true);
    #else
        iblockdata.spawnAfterBreak(world, blockposition, EMPTY_ITEM);
    #endif
  #else
        net.minecraft.world.level.block.entity.BlockEntity tileentity = isTileEntity ? world.getTileEntity(blockposition) : null;
        LootContext$Builder loottableinfo_builder = new LootContext$Builder(world);
        loottableinfo_builder = loottableinfo_builder.a(((Level) world).random);
    #if version >= 1.16.2
        loottableinfo_builder.set(LootContextParams.ORIGIN, (Object) Vec3.a((net.minecraft.core.Vec3i) blockposition));
    #else
        loottableinfo_builder.set(LootContextParams.POSITION, (Object) blockposition);
    #endif
        loottableinfo_builder.set(LootContextParams.TOOL, (Object) EMPTY_ITEM);
        loottableinfo_builder.setOptional(LootContextParams.BLOCK_ENTITY, (Object) tileentity);
        loottableinfo_builder.set(LootContextParams.EXPLOSION_RADIUS, (Object) Float.valueOf(1.0F / yield));
    #if version >= 1.15
        java.util.Iterator items_iter = iblockdata.a(loottableinfo_builder).iterator();
        while (items_iter.hasNext()) {
            ItemStack item = (ItemStack) items_iter.next();
            Block.a((Level) world, blockposition, item);
        }
      #if version >= 1.16.2
        iblockdata.dropNaturally(world, blockposition, EMPTY_ITEM);
      #else
        iblockdata.dropNaturally((Level) world, blockposition, EMPTY_ITEM);
      #endif
    #else
        Block.b(iblockdata, loottableinfo_builder);
    #endif
  #endif
    }
#elseif version >= 1.13
    public void dropNaturally((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, float yield, int chance);
#else

    // In 1.13 they changed parameter order for some stupid reason. Just proxy it.
    // public void dropNaturally((org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, (BlockStateHandle) BlockState iblockdata, float yield, int chance);
    public void dropNaturally((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, float yield, int chance) {
        instance.dropNaturally(world, blockposition, iblockdata, yield, chance);
    }
#endif

#if version >= 1.17
    public void stepOn((org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, (BlockStateHandle) BlockState iblockdata, (org.bukkit.entity.Entity) Entity entity);
#elseif version >= 1.9
    public void stepOn((org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, (BlockStateHandle) BlockState iblockdata, (org.bukkit.entity.Entity) Entity entity) {
        return instance.stepOn(world, blockposition, entity);
    }
#else
    public void stepOn((org.bukkit.World) Level world, (IntVector3) BlockPos blockposition, (BlockStateHandle) BlockState iblockdata, (org.bukkit.entity.Entity) Entity entity) {
        return instance.a(world, blockposition, entity);
    }
#endif

    public (BlockStateHandle) BlockState updateState((BlockStateHandle) BlockState iblockdata, (org.bukkit.World) Level world, (IntVector3) BlockPos blockposition) {
#if version >= 1.18
        return Block.updateFromNeighbourShapes(iblockdata, (net.minecraft.world.level.LevelAccessor) world, blockposition);
#elseif version >= 1.13
        return Block.b(iblockdata, (net.minecraft.world.level.LevelAccessor) world, blockposition);
#else
        return instance.updateState(iblockdata, (BlockGetter) world, blockposition);
#endif
    }

#if version >= 1.18
    public final (BlockStateHandle) BlockState getBlockData:defaultBlockState();
#else
    public final (BlockStateHandle) BlockState getBlockData();
#endif

#if version >= 1.18
    public static (BlockStateHandle) BlockState getByCombinedId:stateById(int combinedId);
    public static int getCombinedId:getId((BlockStateHandle) BlockState iblockdata);
#else
    public static (BlockStateHandle) BlockState getByCombinedId(int combinedId);
    public static int getCombinedId((BlockStateHandle) BlockState iblockdata);
#endif
}

class WallBlock {
}

class FenceBlock {
}

class FenceGateBlock {
}

class Blocks {
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();

    public static final (Object) Block AIR;
    public static final (Object) Block LADDER;
}
