package net.minecraft.world.level.levelgen.feature.structures;

import net.minecraft.core.Direction;
import java.util.Iterator;
import java.util.Collection;
import com.google.common.collect.Lists;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.block.JigsawBlock;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import com.google.common.collect.Queues;
import java.util.Deque;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.apache.commons.lang3.mutable.MutableObject;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import java.util.Random;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.structure.PillagerOutpostPieces;
import net.minecraft.world.level.levelgen.feature.VillagePieces;
import net.minecraft.world.level.levelgen.feature.BastionPieces;
import org.apache.logging.log4j.Logger;

public class JigsawPlacement {
    private static final Logger LOGGER;
    public static final StructureTemplatePools POOLS;
    
    public static void bootstrap() {
        BastionPieces.bootstrap();
        VillagePieces.bootstrap();
        PillagerOutpostPieces.bootstrap();
    }
    
    public static void addPieces(final ResourceLocation uh, final int integer, final PieceFactory a, final ChunkGenerator cha, final StructureManager cva, final BlockPos fu, final List<? super PoolElementStructurePiece> list, final Random random, final boolean boolean9, final boolean boolean10) {
        StructureFeature.bootstrap();
        final Rotation cap11 = Rotation.getRandom(random);
        final StructureTemplatePool cqf12 = JigsawPlacement.POOLS.getPool(uh);
        final StructurePoolElement cqd13 = cqf12.getRandomTemplate(random);
        final PoolElementStructurePiece cts14 = a.create(cva, cqd13, fu, cqd13.getGroundLevelDelta(), cap11, cqd13.getBoundingBox(cva, fu, cap11));
        final BoundingBox ctd15 = cts14.getBoundingBox();
        final int integer2 = (ctd15.x1 + ctd15.x0) / 2;
        final int integer3 = (ctd15.z1 + ctd15.z0) / 2;
        int integer4;
        if (boolean10) {
            integer4 = fu.getY() + cha.getFirstFreeHeight(integer2, integer3, Heightmap.Types.WORLD_SURFACE_WG);
        }
        else {
            integer4 = fu.getY();
        }
        final int integer5 = ctd15.y0 + cts14.getGroundLevelDelta();
        cts14.move(0, integer4 - integer5, 0);
        list.add(cts14);
        if (integer <= 0) {
            return;
        }
        final int integer6 = 80;
        final AABB deg21 = new AABB(integer2 - 80, integer4 - 80, integer3 - 80, integer2 + 80 + 1, integer4 + 80 + 1, integer3 + 80 + 1);
        final Placer c22 = new Placer(integer, a, cha, cva, (List)list, random);
        c22.placing.addLast(new PieceState(cts14, new MutableObject(Shapes.join(Shapes.create(deg21), Shapes.create(AABB.of(ctd15)), BooleanOp.ONLY_FIRST)), integer4 + 80, 0));
        while (!c22.placing.isEmpty()) {
            final PieceState b23 = c22.placing.removeFirst();
            c22.tryPlacingChildren(b23.piece, b23.free, b23.boundsTop, b23.depth, boolean9);
        }
    }
    
    public static void addPieces(final PoolElementStructurePiece cts, final int integer, final PieceFactory a, final ChunkGenerator cha, final StructureManager cva, final List<? super PoolElementStructurePiece> list, final Random random) {
        bootstrap();
        final Placer c8 = new Placer(integer, a, cha, cva, (List)list, random);
        c8.placing.addLast(new PieceState(cts, new MutableObject(Shapes.INFINITY), 0, 0));
        while (!c8.placing.isEmpty()) {
            final PieceState b9 = c8.placing.removeFirst();
            c8.tryPlacingChildren(b9.piece, b9.free, b9.boundsTop, b9.depth, false);
        }
    }
    
    static {
        LOGGER = LogManager.getLogger();
        (POOLS = new StructureTemplatePools()).register(StructureTemplatePool.EMPTY);
    }
    
    static final class PieceState {
        private final PoolElementStructurePiece piece;
        private final MutableObject<VoxelShape> free;
        private final int boundsTop;
        private final int depth;
        
        private PieceState(final PoolElementStructurePiece cts, final MutableObject<VoxelShape> mutableObject, final int integer3, final int integer4) {
            this.piece = cts;
            this.free = mutableObject;
            this.boundsTop = integer3;
            this.depth = integer4;
        }
    }
    
    static final class Placer {
        private final int maxDepth;
        private final PieceFactory factory;
        private final ChunkGenerator chunkGenerator;
        private final StructureManager structureManager;
        private final List<? super PoolElementStructurePiece> pieces;
        private final Random random;
        private final Deque<PieceState> placing;
        
        private Placer(final int integer, final PieceFactory a, final ChunkGenerator cha, final StructureManager cva, final List<? super PoolElementStructurePiece> list, final Random random) {
            this.placing = Queues.newArrayDeque();
            this.maxDepth = integer;
            this.factory = a;
            this.chunkGenerator = cha;
            this.structureManager = cva;
            this.pieces = list;
            this.random = random;
        }
        
        private void tryPlacingChildren(final PoolElementStructurePiece cts, final MutableObject<VoxelShape> mutableObject, final int integer3, final int integer4, final boolean boolean5) {
            final StructurePoolElement cqd7 = cts.getElement();
            final BlockPos fu8 = cts.getPosition();
            final Rotation cap9 = cts.getRotation();
            final StructureTemplatePool.Projection a10 = cqd7.getProjection();
            final boolean boolean6 = a10 == StructureTemplatePool.Projection.RIGID;
            final MutableObject<VoxelShape> mutableObject2 = (MutableObject<VoxelShape>)new MutableObject();
            final BoundingBox ctd13 = cts.getBoundingBox();
            final int integer5 = ctd13.y0;
            for (final StructureTemplate.StructureBlockInfo c2 : cqd7.getShuffledJigsawBlocks(this.structureManager, fu8, cap9, this.random)) {
                final Direction fz17 = JigsawBlock.getFrontFacing(c2.state);
                final BlockPos fu9 = c2.pos;
                final BlockPos fu10 = fu9.relative(fz17);
                final int integer6 = fu9.getY() - integer5;
                int integer7 = -1;
                final StructureTemplatePool cqf22 = JigsawPlacement.POOLS.getPool(new ResourceLocation(c2.nbt.getString("pool")));
                final StructureTemplatePool cqf23 = JigsawPlacement.POOLS.getPool(cqf22.getFallback());
                if (cqf22 == StructureTemplatePool.INVALID || (cqf22.size() == 0 && cqf22 != StructureTemplatePool.EMPTY)) {
                    JigsawPlacement.LOGGER.warn("Empty or none existent pool: {}", c2.nbt.getString("pool"));
                }
                else {
                    final boolean boolean7 = ctd13.isInside(fu10);
                    MutableObject<VoxelShape> mutableObject3;
                    int integer8;
                    if (boolean7) {
                        mutableObject3 = mutableObject2;
                        integer8 = integer5;
                        if (mutableObject2.getValue() == null) {
                            mutableObject2.setValue(Shapes.create(AABB.of(ctd13)));
                        }
                    }
                    else {
                        mutableObject3 = mutableObject;
                        integer8 = integer3;
                    }
                    final List<StructurePoolElement> list27 = Lists.newArrayList();
                    if (integer4 != this.maxDepth) {
                        list27.addAll(cqf22.getShuffledTemplates(this.random));
                    }
                    list27.addAll(cqf23.getShuffledTemplates(this.random));
                Label_1091:
                    for (final StructurePoolElement cqd8 : list27) {
                        if (cqd8 == EmptyPoolElement.INSTANCE) {
                            break;
                        }
                        for (final Rotation cap10 : Rotation.getShuffled(this.random)) {
                            final List<StructureTemplate.StructureBlockInfo> list28 = cqd8.getShuffledJigsawBlocks(this.structureManager, BlockPos.ZERO, cap10, this.random);
                            final BoundingBox ctd14 = cqd8.getBoundingBox(this.structureManager, BlockPos.ZERO, cap10);
                            int integer9;
                            if (!boolean5 || ctd14.getYSpan() > 16) {
                                integer9 = 0;
                            }
                            else {
                                ResourceLocation uh4;
                                StructureTemplatePool cqf24;
                                StructureTemplatePool cqf25;
                                integer9 = list28.stream().mapToInt(c -> {
                                    if (!ctd14.isInside(c.pos.relative(JigsawBlock.getFrontFacing(c.state)))) {
                                        return 0;
                                    }
                                    else {
                                        uh4 = new ResourceLocation(c.nbt.getString("pool"));
                                        cqf24 = JigsawPlacement.POOLS.getPool(uh4);
                                        cqf25 = JigsawPlacement.POOLS.getPool(cqf24.getFallback());
                                        return Math.max(cqf24.getMaxSize(this.structureManager), cqf25.getMaxSize(this.structureManager));
                                    }
                                }).max().orElse(0);
                            }
                            for (final StructureTemplate.StructureBlockInfo c3 : list28) {
                                if (!JigsawBlock.canAttach(c2, c3)) {
                                    continue;
                                }
                                final BlockPos fu11 = c3.pos;
                                final BlockPos fu12 = new BlockPos(fu10.getX() - fu11.getX(), fu10.getY() - fu11.getY(), fu10.getZ() - fu11.getZ());
                                final BoundingBox ctd15 = cqd8.getBoundingBox(this.structureManager, fu12, cap10);
                                final int integer10 = ctd15.y0;
                                final StructureTemplatePool.Projection a11 = cqd8.getProjection();
                                final boolean boolean8 = a11 == StructureTemplatePool.Projection.RIGID;
                                final int integer11 = fu11.getY();
                                final int integer12 = integer6 - integer11 + JigsawBlock.getFrontFacing(c2.state).getStepY();
                                int integer13;
                                if (boolean6 && boolean8) {
                                    integer13 = integer5 + integer12;
                                }
                                else {
                                    if (integer7 == -1) {
                                        integer7 = this.chunkGenerator.getFirstFreeHeight(fu9.getX(), fu9.getZ(), Heightmap.Types.WORLD_SURFACE_WG);
                                    }
                                    integer13 = integer7 - integer11;
                                }
                                final int integer14 = integer13 - integer10;
                                final BoundingBox ctd16 = ctd15.moved(0, integer14, 0);
                                final BlockPos fu13 = fu12.offset(0, integer14, 0);
                                if (integer9 > 0) {
                                    final int integer15 = Math.max(integer9 + 1, ctd16.y1 - ctd16.y0);
                                    ctd16.y1 = ctd16.y0 + integer15;
                                }
                                if (Shapes.joinIsNotEmpty((VoxelShape)mutableObject3.getValue(), Shapes.create(AABB.of(ctd16).deflate(0.25)), BooleanOp.ONLY_SECOND)) {
                                    continue;
                                }
                                mutableObject3.setValue(Shapes.joinUnoptimized((VoxelShape)mutableObject3.getValue(), Shapes.create(AABB.of(ctd16)), BooleanOp.ONLY_FIRST));
                                final int integer15 = cts.getGroundLevelDelta();
                                int integer16;
                                if (boolean8) {
                                    integer16 = integer15 - integer12;
                                }
                                else {
                                    integer16 = cqd8.getGroundLevelDelta();
                                }
                                final PoolElementStructurePiece cts2 = this.factory.create(this.structureManager, cqd8, fu13, integer16, cap10, ctd16);
                                int integer17;
                                if (boolean6) {
                                    integer17 = integer5 + integer6;
                                }
                                else if (boolean8) {
                                    integer17 = integer13 + integer11;
                                }
                                else {
                                    if (integer7 == -1) {
                                        integer7 = this.chunkGenerator.getFirstFreeHeight(fu9.getX(), fu9.getZ(), Heightmap.Types.WORLD_SURFACE_WG);
                                    }
                                    integer17 = integer7 + integer12 / 2;
                                }
                                cts.addJunction(new JigsawJunction(fu10.getX(), integer17 - integer6 + integer15, fu10.getZ(), integer12, a11));
                                cts2.addJunction(new JigsawJunction(fu9.getX(), integer17 - integer11 + integer16, fu9.getZ(), -integer12, a10));
                                this.pieces.add(cts2);
                                if (integer4 + 1 <= this.maxDepth) {
                                    this.placing.addLast(new PieceState(cts2, (MutableObject)mutableObject3, integer8, integer4 + 1));
                                    break Label_1091;
                                }
                                break Label_1091;
                            }
                        }
                    }
                }
            }
        }
    }
    
    public interface PieceFactory {
        PoolElementStructurePiece create(final StructureManager cva, final StructurePoolElement cqd, final BlockPos fu, final int integer, final Rotation cap, final BoundingBox ctd);
    }
}
