package org.bukkit.craftbukkit.block;

import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.block.entity.TileEntity;

import org.bukkit.craftbukkit.block.CraftBlockEntityState;

class CraftBlockState {
#if version >= 1.13
    protected unknown IBlockData data;
#else
    protected unknown int type;
    protected unknown org.bukkit.material.MaterialData data;
#endif

#if exists org.bukkit.craftbukkit.block.CraftBlockState protected int capturedFlags;
    protected int flag:capturedFlags;
#else
    protected int flag;
#endif

    public (org.bukkit.block.BlockState) CraftBlockState(org.bukkit.block.Block block);

    // Fakes the constructor of BlockState or derived types, an a BlockState that is already created on MC 1.13 and later
    // Not supported on earlier versions, instantiation worked differently there
#if version >= 1.13
    public optional void init(org.bukkit.block.Block block, org.bukkit.Chunk chunk, IBlockData blockData, TileEntity tileEntity) {
  #if exists org.bukkit.craftbukkit.block.CraftBlockState private final org.bukkit.craftbukkit.CraftWorld world;
        #require org.bukkit.craftbukkit.block.CraftBlockState private final org.bukkit.craftbukkit.CraftWorld blockState_world:world;
        instance#blockState_world = block.getWorld();
  #endif

  #if exists org.bukkit.craftbukkit.block.CraftBlockState private final org.bukkit.craftbukkit.CraftChunk chunk;
        #require org.bukkit.craftbukkit.block.CraftBlockState private final org.bukkit.craftbukkit.CraftChunk blockState_chunk:chunk;
        instance#blockState_chunk = chunk;
  #endif

  #if version >= 1.13.2
        #require org.bukkit.craftbukkit.block.CraftBlockState private final net.minecraft.core.BlockPosition blockState_position:position;
        instance#blockState_position = ((org.bukkit.craftbukkit.block.CraftBlock) block).getPosition();
  #else
        #require org.bukkit.craftbukkit.block.CraftBlockState private final int blockState_x:x;
        #require org.bukkit.craftbukkit.block.CraftBlockState private final int blockState_y:y;
        #require org.bukkit.craftbukkit.block.CraftBlockState private final int blockState_z:z;
        instance#blockState_x = block.getX();
        instance#blockState_y = block.getY();
        instance#blockState_z = block.getZ();
  #endif

        instance.setData(blockData);
  #if exists org.bukkit.craftbukkit.block.CraftBlockState public void setFlags(int flags);
        instance.setFlags(3);
  #else
        instance.setFlag(3);
  #endif

        if (instance instanceof CraftBlockEntityState) {
            CraftBlockEntityState entityState = (CraftBlockEntityState) instance;
  #if exists org.bukkit.craftbukkit.block.CraftBlockEntityState private final T extends net.minecraft.world.level.block.entity.TileEntity tileEntity;
            #require org.bukkit.craftbukkit.block.CraftBlockEntityState private final T extends TileEntity blockEntity:tileEntity;
  #else
            #require org.bukkit.craftbukkit.block.CraftBlockEntityState private final T extends TileEntity blockEntity;
  #endif
            #require org.bukkit.craftbukkit.block.CraftBlockEntityState private final T extends TileEntity snapshot;
            entityState#blockEntity = tileEntity;
            entityState#snapshot = tileEntity;

  #if exists org.bukkit.craftbukkit.block.CraftBlockEntityState private final Class<T> tileEntityClass;
            #require org.bukkit.craftbukkit.block.CraftBlockEntityState private final Class<T extends TileEntity> tileEntityClass;
            entityState#tileEntityClass = tileEntity.getClass();
  #endif

  #if version >= 1.20
            if (entityState instanceof org.bukkit.craftbukkit.block.CraftSign) {
                net.minecraft.world.level.block.entity.TileEntitySign tileEntitySign = (net.minecraft.world.level.block.entity.TileEntitySign) tileEntity;
                org.bukkit.craftbukkit.block.sign.CraftSignSide front = new org.bukkit.craftbukkit.block.sign.CraftSignSide(tileEntitySign.getFrontText());
                org.bukkit.craftbukkit.block.sign.CraftSignSide back = new org.bukkit.craftbukkit.block.sign.CraftSignSide(tileEntitySign.getBackText());
                #require org.bukkit.craftbukkit.block.CraftSign private final org.bukkit.craftbukkit.block.sign.CraftSignSide front;
                #require org.bukkit.craftbukkit.block.CraftSign private final org.bukkit.craftbukkit.block.sign.CraftSignSide back;
                entityState#front = front;
                entityState#back = back;
            }
  #elseif exists org.bukkit.craftbukkit.block.CraftSign private final org.bukkit.craftbukkit.block.sign.CraftSignSide front;
            if (entityState instanceof org.bukkit.craftbukkit.block.CraftSign) {
                net.minecraft.world.level.block.entity.TileEntitySign tileEntitySign = (net.minecraft.world.level.block.entity.TileEntitySign) tileEntity;
                org.bukkit.craftbukkit.block.sign.CraftSignSide front = new org.bukkit.craftbukkit.block.sign.CraftSignSide(tileEntitySign);
                #require org.bukkit.craftbukkit.block.CraftSign private final org.bukkit.craftbukkit.block.sign.CraftSignSide front;
                entityState#front = front;
            }
  #endif

            #require org.bukkit.craftbukkit.block.CraftBlockEntityState protected void load(T extends TileEntity tileEntity);
            entityState#load(tileEntity);
        }
    }
#else
    public optional void init:###(org.bukkit.block.Block block, org.bukkit.Chunk chunk, IBlockData blockData, TileEntity tileEntity);
#endif
}

class CraftBlock {

#if version >= 1.13.1
    public (com.bergerkiller.bukkit.common.wrappers.BlockData) IBlockData getBlockData:getNMS();
#elseif version >= 1.13
    protected (com.bergerkiller.bukkit.common.wrappers.BlockData) IBlockData getBlockData:getNMS();
#else
    // 1.8 -> 1.12.2 only, this code is unlikely to change
    public (com.bergerkiller.bukkit.common.wrappers.BlockData) IBlockData getBlockData() {
        net.minecraft.world.level.chunk.Chunk chunk = ((org.bukkit.craftbukkit.CraftChunk) instance.getChunk()).getHandle();

  #if version >= 1.9
        return chunk.a(instance.getX(), instance.getY(), instance.getZ());
  #else
        return chunk.getBlockData(new net.minecraft.core.BlockPosition(instance.getX(), instance.getY(), instance.getZ()));
  #endif
    }
#endif

    public static Object getBlockTileEntity(org.bukkit.block.Block block) {
        net.minecraft.world.level.World nmsWorld = (net.minecraft.world.level.World) ((org.bukkit.craftbukkit.CraftWorld) block.getWorld()).getHandle();
        net.minecraft.core.BlockPosition nmsBlockPos;

#if version >= 1.13.1
        if (block instanceof org.bukkit.craftbukkit.block.CraftBlock) {
            nmsBlockPos = ((org.bukkit.craftbukkit.block.CraftBlock) block).getPosition();
        } else
#endif
        {
            nmsBlockPos = new net.minecraft.core.BlockPosition(block.getX(), block.getY(), block.getZ());
        }

#if version >= 1.18
        return nmsWorld.getBlockEntity(nmsBlockPos);
#else
        return nmsWorld.getTileEntity(nmsBlockPos);
#endif
    }

    public static Object getBlockPosition(org.bukkit.block.Block block) {
#if version >= 1.13.1
        if (block instanceof org.bukkit.craftbukkit.block.CraftBlock) {
            return ((org.bukkit.craftbukkit.block.CraftBlock) block).getPosition();
        } else
#endif
        {
            return new net.minecraft.core.BlockPosition(block.getX(), block.getY(), block.getZ());
        }
    }

    public static org.bukkit.block.Block createBlockAtTileEntity(Object nmsTileEntity) {
        TileEntity tileEntity = (TileEntity) nmsTileEntity;
#if version >= 1.18
        net.minecraft.world.level.World world = tileEntity.getLevel();
#else
        net.minecraft.world.level.World world = tileEntity.getWorld();
#endif
        if (world == null) {
            throw new IllegalArgumentException("Tile Entity has no world set");
        }
#if version >= 1.18
        return org.bukkit.craftbukkit.block.CraftBlock.at(world, tileEntity.getBlockPos());
#elseif version >= 1.13
        return org.bukkit.craftbukkit.block.CraftBlock.at(world, tileEntity.getPosition());
#else
        net.minecraft.core.BlockPosition position = tileEntity.getPosition();
        return world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ());
#endif
    }
}
