package net.minecraft.world.level.levelgen.structure;

import net.minecraft.world.level.block.state.AbstractStateHolder;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.level.block.entity.DispenserBlockEntity;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.core.Vec3i;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import java.util.Iterator;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.LevelAccessor;
import java.util.Random;
import java.util.List;
import net.minecraft.nbt.Tag;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.Block;
import java.util.Set;
import net.minecraft.world.level.levelgen.feature.StructurePieceType;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.Mirror;
import javax.annotation.Nullable;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;

public abstract class StructurePiece {
    protected static final BlockState CAVE_AIR;
    protected BoundingBox boundingBox;
    @Nullable
    private Direction orientation;
    private Mirror mirror;
    private Rotation rotation;
    protected int genDepth;
    private final StructurePieceType type;
    private static final Set<Block> SHAPE_CHECK_BLOCKS;
    
    protected StructurePiece(final StructurePieceType cfm, final int integer) {
        this.type = cfm;
        this.genDepth = integer;
    }
    
    public StructurePiece(final StructurePieceType cfm, final CompoundTag jt) {
        this(cfm, jt.getInt("GD"));
        if (jt.contains("BB")) {
            this.boundingBox = new BoundingBox(jt.getIntArray("BB"));
        }
        final int integer4 = jt.getInt("O");
        this.setOrientation((integer4 == -1) ? null : Direction.from2DDataValue(integer4));
    }
    
    public final CompoundTag createTag() {
        final CompoundTag jt2 = new CompoundTag();
        jt2.putString("id", Registry.STRUCTURE_PIECE.getKey(this.getType()).toString());
        jt2.put("BB", this.boundingBox.createTag());
        final Direction fp3 = this.getOrientation();
        jt2.putInt("O", (fp3 == null) ? -1 : fp3.get2DDataValue());
        jt2.putInt("GD", this.genDepth);
        this.addAdditionalSaveData(jt2);
        return jt2;
    }
    
    protected abstract void addAdditionalSaveData(final CompoundTag jt);
    
    public void addChildren(final StructurePiece clu, final List<StructurePiece> list, final Random random) {
    }
    
    public abstract boolean postProcess(final LevelAccessor bjx, final ChunkGenerator<?> caa, final Random random, final BoundingBox clb, final ChunkPos bjh);
    
    public BoundingBox getBoundingBox() {
        return this.boundingBox;
    }
    
    public int getGenDepth() {
        return this.genDepth;
    }
    
    public boolean isCloseToChunk(final ChunkPos bjh, final int integer) {
        final int integer2 = bjh.x << 4;
        final int integer3 = bjh.z << 4;
        return this.boundingBox.intersects(integer2 - integer, integer3 - integer, integer2 + 15 + integer, integer3 + 15 + integer);
    }
    
    public static StructurePiece findCollisionPiece(final List<StructurePiece> list, final BoundingBox clb) {
        for (final StructurePiece clu4 : list) {
            if (clu4.getBoundingBox() != null && clu4.getBoundingBox().intersects(clb)) {
                return clu4;
            }
        }
        return null;
    }
    
    protected boolean edgesLiquid(final BlockGetter bjg, final BoundingBox clb) {
        final int integer4 = Math.max(this.boundingBox.x0 - 1, clb.x0);
        final int integer5 = Math.max(this.boundingBox.y0 - 1, clb.y0);
        final int integer6 = Math.max(this.boundingBox.z0 - 1, clb.z0);
        final int integer7 = Math.min(this.boundingBox.x1 + 1, clb.x1);
        final int integer8 = Math.min(this.boundingBox.y1 + 1, clb.y1);
        final int integer9 = Math.min(this.boundingBox.z1 + 1, clb.z1);
        final BlockPos.MutableBlockPos a10 = new BlockPos.MutableBlockPos();
        for (int integer10 = integer4; integer10 <= integer7; ++integer10) {
            for (int integer11 = integer6; integer11 <= integer9; ++integer11) {
                if (bjg.getBlockState(a10.set(integer10, integer5, integer11)).getMaterial().isLiquid()) {
                    return true;
                }
                if (bjg.getBlockState(a10.set(integer10, integer8, integer11)).getMaterial().isLiquid()) {
                    return true;
                }
            }
        }
        for (int integer10 = integer4; integer10 <= integer7; ++integer10) {
            for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                if (bjg.getBlockState(a10.set(integer10, integer11, integer6)).getMaterial().isLiquid()) {
                    return true;
                }
                if (bjg.getBlockState(a10.set(integer10, integer11, integer9)).getMaterial().isLiquid()) {
                    return true;
                }
            }
        }
        for (int integer10 = integer6; integer10 <= integer9; ++integer10) {
            for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                if (bjg.getBlockState(a10.set(integer4, integer11, integer10)).getMaterial().isLiquid()) {
                    return true;
                }
                if (bjg.getBlockState(a10.set(integer7, integer11, integer10)).getMaterial().isLiquid()) {
                    return true;
                }
            }
        }
        return false;
    }
    
    protected int getWorldX(final int integer1, final int integer2) {
        final Direction fp4 = this.getOrientation();
        if (fp4 == null) {
            return integer1;
        }
        switch (fp4) {
            case NORTH:
            case SOUTH: {
                return this.boundingBox.x0 + integer1;
            }
            case WEST: {
                return this.boundingBox.x1 - integer2;
            }
            case EAST: {
                return this.boundingBox.x0 + integer2;
            }
            default: {
                return integer1;
            }
        }
    }
    
    protected int getWorldY(final int integer) {
        if (this.getOrientation() == null) {
            return integer;
        }
        return integer + this.boundingBox.y0;
    }
    
    protected int getWorldZ(final int integer1, final int integer2) {
        final Direction fp4 = this.getOrientation();
        if (fp4 == null) {
            return integer2;
        }
        switch (fp4) {
            case NORTH: {
                return this.boundingBox.z1 - integer2;
            }
            case SOUTH: {
                return this.boundingBox.z0 + integer2;
            }
            case WEST:
            case EAST: {
                return this.boundingBox.z0 + integer1;
            }
            default: {
                return integer2;
            }
        }
    }
    
    protected void placeBlock(final LevelAccessor bjx, BlockState byj, final int integer3, final int integer4, final int integer5, final BoundingBox clb) {
        final BlockPos fk8 = new BlockPos(this.getWorldX(integer3, integer5), this.getWorldY(integer4), this.getWorldZ(integer3, integer5));
        if (!clb.isInside(fk8)) {
            return;
        }
        if (this.mirror != Mirror.NONE) {
            byj = byj.mirror(this.mirror);
        }
        if (this.rotation != Rotation.NONE) {
            byj = byj.rotate(this.rotation);
        }
        bjx.setBlock(fk8, byj, 2);
        final FluidState coj9 = bjx.getFluidState(fk8);
        if (!coj9.isEmpty()) {
            bjx.getLiquidTicks().scheduleTick(fk8, coj9.getType(), 0);
        }
        if (StructurePiece.SHAPE_CHECK_BLOCKS.contains(byj.getBlock())) {
            bjx.getChunk(fk8).markPosForPostprocessing(fk8);
        }
    }
    
    protected BlockState getBlock(final BlockGetter bjg, final int integer2, final int integer3, final int integer4, final BoundingBox clb) {
        final int integer5 = this.getWorldX(integer2, integer4);
        final int integer6 = this.getWorldY(integer3);
        final int integer7 = this.getWorldZ(integer2, integer4);
        final BlockPos fk10 = new BlockPos(integer5, integer6, integer7);
        if (!clb.isInside(fk10)) {
            return Blocks.AIR.defaultBlockState();
        }
        return bjg.getBlockState(fk10);
    }
    
    protected boolean isInterior(final LevelReader bjz, final int integer2, final int integer3, final int integer4, final BoundingBox clb) {
        final int integer5 = this.getWorldX(integer2, integer4);
        final int integer6 = this.getWorldY(integer3 + 1);
        final int integer7 = this.getWorldZ(integer2, integer4);
        final BlockPos fk10 = new BlockPos(integer5, integer6, integer7);
        return clb.isInside(fk10) && integer6 < bjz.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, integer5, integer7);
    }
    
    protected void generateAirBox(final LevelAccessor bjx, final BoundingBox clb, final int integer3, final int integer4, final int integer5, final int integer6, final int integer7, final int integer8) {
        for (int integer9 = integer4; integer9 <= integer7; ++integer9) {
            for (int integer10 = integer3; integer10 <= integer6; ++integer10) {
                for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                    this.placeBlock(bjx, Blocks.AIR.defaultBlockState(), integer10, integer9, integer11, clb);
                }
            }
        }
    }
    
    protected void generateBox(final LevelAccessor bjx, final BoundingBox clb, final int integer3, final int integer4, final int integer5, final int integer6, final int integer7, final int integer8, final BlockState byj9, final BlockState byj10, final boolean boolean11) {
        for (int integer9 = integer4; integer9 <= integer7; ++integer9) {
            for (int integer10 = integer3; integer10 <= integer6; ++integer10) {
                for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                    if (!boolean11 || !this.getBlock(bjx, integer10, integer9, integer11, clb).isAir()) {
                        if (integer9 == integer4 || integer9 == integer7 || integer10 == integer3 || integer10 == integer6 || integer11 == integer5 || integer11 == integer8) {
                            this.placeBlock(bjx, byj9, integer10, integer9, integer11, clb);
                        }
                        else {
                            this.placeBlock(bjx, byj10, integer10, integer9, integer11, clb);
                        }
                    }
                }
            }
        }
    }
    
    protected void generateBox(final LevelAccessor bjx, final BoundingBox clb, final int integer3, final int integer4, final int integer5, final int integer6, final int integer7, final int integer8, final boolean boolean9, final Random random, final BlockSelector a) {
        for (int integer9 = integer4; integer9 <= integer7; ++integer9) {
            for (int integer10 = integer3; integer10 <= integer6; ++integer10) {
                for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                    if (!boolean9 || !this.getBlock(bjx, integer10, integer9, integer11, clb).isAir()) {
                        a.next(random, integer10, integer9, integer11, integer9 == integer4 || integer9 == integer7 || integer10 == integer3 || integer10 == integer6 || integer11 == integer5 || integer11 == integer8);
                        this.placeBlock(bjx, a.getNext(), integer10, integer9, integer11, clb);
                    }
                }
            }
        }
    }
    
    protected void generateMaybeBox(final LevelAccessor bjx, final BoundingBox clb, final Random random, final float float4, final int integer5, final int integer6, final int integer7, final int integer8, final int integer9, final int integer10, final BlockState byj11, final BlockState byj12, final boolean boolean13, final boolean boolean14) {
        for (int integer11 = integer6; integer11 <= integer9; ++integer11) {
            for (int integer12 = integer5; integer12 <= integer8; ++integer12) {
                for (int integer13 = integer7; integer13 <= integer10; ++integer13) {
                    if (random.nextFloat() <= float4) {
                        if (!boolean13 || !this.getBlock(bjx, integer12, integer11, integer13, clb).isAir()) {
                            if (!boolean14 || this.isInterior(bjx, integer12, integer11, integer13, clb)) {
                                if (integer11 == integer6 || integer11 == integer9 || integer12 == integer5 || integer12 == integer8 || integer13 == integer7 || integer13 == integer10) {
                                    this.placeBlock(bjx, byj11, integer12, integer11, integer13, clb);
                                }
                                else {
                                    this.placeBlock(bjx, byj12, integer12, integer11, integer13, clb);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    protected void maybeGenerateBlock(final LevelAccessor bjx, final BoundingBox clb, final Random random, final float float4, final int integer5, final int integer6, final int integer7, final BlockState byj) {
        if (random.nextFloat() < float4) {
            this.placeBlock(bjx, byj, integer5, integer6, integer7, clb);
        }
    }
    
    protected void generateUpperHalfSphere(final LevelAccessor bjx, final BoundingBox clb, final int integer3, final int integer4, final int integer5, final int integer6, final int integer7, final int integer8, final BlockState byj, final boolean boolean10) {
        final float float12 = (float)(integer6 - integer3 + 1);
        final float float13 = (float)(integer7 - integer4 + 1);
        final float float14 = (float)(integer8 - integer5 + 1);
        final float float15 = integer3 + float12 / 2.0f;
        final float float16 = integer5 + float14 / 2.0f;
        for (int integer9 = integer4; integer9 <= integer7; ++integer9) {
            final float float17 = (integer9 - integer4) / float13;
            for (int integer10 = integer3; integer10 <= integer6; ++integer10) {
                final float float18 = (integer10 - float15) / (float12 * 0.5f);
                for (int integer11 = integer5; integer11 <= integer8; ++integer11) {
                    final float float19 = (integer11 - float16) / (float14 * 0.5f);
                    if (!boolean10 || !this.getBlock(bjx, integer10, integer9, integer11, clb).isAir()) {
                        final float float20 = float18 * float18 + float17 * float17 + float19 * float19;
                        if (float20 <= 1.05f) {
                            this.placeBlock(bjx, byj, integer10, integer9, integer11, clb);
                        }
                    }
                }
            }
        }
    }
    
    protected void fillColumnDown(final LevelAccessor bjx, final BlockState byj, final int integer3, final int integer4, final int integer5, final BoundingBox clb) {
        final int integer6 = this.getWorldX(integer3, integer5);
        int integer7 = this.getWorldY(integer4);
        final int integer8 = this.getWorldZ(integer3, integer5);
        if (!clb.isInside(new BlockPos(integer6, integer7, integer8))) {
            return;
        }
        while ((bjx.isEmptyBlock(new BlockPos(integer6, integer7, integer8)) || bjx.getBlockState(new BlockPos(integer6, integer7, integer8)).getMaterial().isLiquid()) && integer7 > 1) {
            bjx.setBlock(new BlockPos(integer6, integer7, integer8), byj, 2);
            --integer7;
        }
    }
    
    protected boolean createChest(final LevelAccessor bjx, final BoundingBox clb, final Random random, final int integer4, final int integer5, final int integer6, final ResourceLocation sm) {
        final BlockPos fk9 = new BlockPos(this.getWorldX(integer4, integer6), this.getWorldY(integer5), this.getWorldZ(integer4, integer6));
        return this.createChest(bjx, clb, random, fk9, sm, null);
    }
    
    public static BlockState reorient(final BlockGetter bjg, final BlockPos fk, final BlockState byj) {
        Direction fp4 = null;
        for (final Direction fp5 : Direction.Plane.HORIZONTAL) {
            final BlockPos fk2 = fk.relative(fp5);
            final BlockState byj2 = bjg.getBlockState(fk2);
            if (byj2.getBlock() == Blocks.CHEST) {
                return byj;
            }
            if (!byj2.isSolidRender(bjg, fk2)) {
                continue;
            }
            if (fp4 != null) {
                fp4 = null;
                break;
            }
            fp4 = fp5;
        }
        if (fp4 != null) {
            return ((AbstractStateHolder<O, BlockState>)byj).<Comparable, Direction>setValue((Property<Comparable>)HorizontalDirectionalBlock.FACING, fp4.getOpposite());
        }
        Direction fp6 = byj.<Direction>getValue((Property<Direction>)HorizontalDirectionalBlock.FACING);
        BlockPos fk3 = fk.relative(fp6);
        if (bjg.getBlockState(fk3).isSolidRender(bjg, fk3)) {
            fp6 = fp6.getOpposite();
            fk3 = fk.relative(fp6);
        }
        if (bjg.getBlockState(fk3).isSolidRender(bjg, fk3)) {
            fp6 = fp6.getClockWise();
            fk3 = fk.relative(fp6);
        }
        if (bjg.getBlockState(fk3).isSolidRender(bjg, fk3)) {
            fp6 = fp6.getOpposite();
            fk3 = fk.relative(fp6);
        }
        return ((AbstractStateHolder<O, BlockState>)byj).<Comparable, Direction>setValue((Property<Comparable>)HorizontalDirectionalBlock.FACING, fp6);
    }
    
    protected boolean createChest(final LevelAccessor bjx, final BoundingBox clb, final Random random, final BlockPos fk, final ResourceLocation sm, @Nullable BlockState byj) {
        if (!clb.isInside(fk) || bjx.getBlockState(fk).getBlock() == Blocks.CHEST) {
            return false;
        }
        if (byj == null) {
            byj = reorient(bjx, fk, Blocks.CHEST.defaultBlockState());
        }
        bjx.setBlock(fk, byj, 2);
        final BlockEntity bwl8 = bjx.getBlockEntity(fk);
        if (bwl8 instanceof ChestBlockEntity) {
            ((ChestBlockEntity)bwl8).setLootTable(sm, random.nextLong());
        }
        return true;
    }
    
    protected boolean createDispenser(final LevelAccessor bjx, final BoundingBox clb, final Random random, final int integer4, final int integer5, final int integer6, final Direction fp, final ResourceLocation sm) {
        final BlockPos fk10 = new BlockPos(this.getWorldX(integer4, integer6), this.getWorldY(integer5), this.getWorldZ(integer4, integer6));
        if (clb.isInside(fk10) && bjx.getBlockState(fk10).getBlock() != Blocks.DISPENSER) {
            this.placeBlock(bjx, ((AbstractStateHolder<O, BlockState>)Blocks.DISPENSER.defaultBlockState()).<Comparable, Direction>setValue((Property<Comparable>)DispenserBlock.FACING, fp), integer4, integer5, integer6, clb);
            final BlockEntity bwl11 = bjx.getBlockEntity(fk10);
            if (bwl11 instanceof DispenserBlockEntity) {
                ((DispenserBlockEntity)bwl11).setLootTable(sm, random.nextLong());
            }
            return true;
        }
        return false;
    }
    
    public void move(final int integer1, final int integer2, final int integer3) {
        this.boundingBox.move(integer1, integer2, integer3);
    }
    
    @Nullable
    public Direction getOrientation() {
        return this.orientation;
    }
    
    public void setOrientation(@Nullable final Direction fp) {
        this.orientation = fp;
        if (fp == null) {
            this.rotation = Rotation.NONE;
            this.mirror = Mirror.NONE;
        }
        else {
            switch (fp) {
                case SOUTH: {
                    this.mirror = Mirror.LEFT_RIGHT;
                    this.rotation = Rotation.NONE;
                    break;
                }
                case WEST: {
                    this.mirror = Mirror.LEFT_RIGHT;
                    this.rotation = Rotation.CLOCKWISE_90;
                    break;
                }
                case EAST: {
                    this.mirror = Mirror.NONE;
                    this.rotation = Rotation.CLOCKWISE_90;
                    break;
                }
                default: {
                    this.mirror = Mirror.NONE;
                    this.rotation = Rotation.NONE;
                    break;
                }
            }
        }
    }
    
    public Rotation getRotation() {
        return this.rotation;
    }
    
    public StructurePieceType getType() {
        return this.type;
    }
    
    static {
        CAVE_AIR = Blocks.CAVE_AIR.defaultBlockState();
        SHAPE_CHECK_BLOCKS = ImmutableSet.<Block>builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build();
    }
    
    public abstract static class BlockSelector {
        protected BlockState next;
        
        protected BlockSelector() {
            this.next = Blocks.AIR.defaultBlockState();
        }
        
        public abstract void next(final Random random, final int integer2, final int integer3, final int integer4, final boolean boolean5);
        
        public BlockState getNext() {
            return this.next;
        }
    }
}
