package net.minecraft.gametest.framework;

import net.minecraft.world.phys.Vec3;
import net.minecraft.commands.arguments.blocks.BlockInput;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.Collections;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import java.io.BufferedReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.io.IOException;
import net.minecraft.nbt.TagParser;
import java.io.Reader;
import org.apache.commons.io.IOUtils;
import java.nio.file.Files;
import net.minecraft.nbt.CompoundTag;
import java.nio.file.Path;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import java.nio.file.Paths;
import net.minecraft.world.level.block.state.BlockState;
import com.google.common.collect.Lists;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Comparator;
import java.util.Optional;
import java.util.List;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.state.properties.StructureMode;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import net.minecraft.world.level.block.Rotation;

public class StructureUtils {
    public static String testStructuresDir;
    
    public static Rotation getRotationForRotationSteps(final int integer) {
        switch (integer) {
            case 0: {
                return Rotation.NONE;
            }
            case 1: {
                return Rotation.CLOCKWISE_90;
            }
            case 2: {
                return Rotation.CLOCKWISE_180;
            }
            case 3: {
                return Rotation.COUNTERCLOCKWISE_90;
            }
            default: {
                throw new IllegalArgumentException("rotationSteps must be a value from 0-3. Got value " + integer);
            }
        }
    }
    
    public static AABB getStructureBounds(final StructureBlockEntity cel) {
        final BlockPos fu2 = cel.getBlockPos();
        final BlockPos fu3 = fu2.offset(cel.getStructureSize().offset(-1, -1, -1));
        final BlockPos fu4 = StructureTemplate.transform(fu3, Mirror.NONE, cel.getRotation(), fu2);
        return new AABB(fu2, fu4);
    }
    
    public static BoundingBox getStructureBoundingBox(final StructureBlockEntity cel) {
        final BlockPos fu2 = cel.getBlockPos();
        final BlockPos fu3 = fu2.offset(cel.getStructureSize().offset(-1, -1, -1));
        final BlockPos fu4 = StructureTemplate.transform(fu3, Mirror.NONE, cel.getRotation(), fu2);
        return new BoundingBox(fu2, fu4);
    }
    
    public static void addCommandBlockAndButtonToStartTest(final BlockPos fu1, final BlockPos fu2, final Rotation cap, final ServerLevel zd) {
        final BlockPos fu3 = StructureTemplate.transform(fu1.offset(fu2), Mirror.NONE, cap, fu1);
        zd.setBlockAndUpdate(fu3, Blocks.COMMAND_BLOCK.defaultBlockState());
        final CommandBlockEntity cdq6 = (CommandBlockEntity)zd.getBlockEntity(fu3);
        cdq6.getCommandBlock().setCommand("test runthis");
        final BlockPos fu4 = StructureTemplate.transform(fu3.offset(0, 0, -1), Mirror.NONE, cap, fu3);
        zd.setBlockAndUpdate(fu4, Blocks.STONE_BUTTON.defaultBlockState().rotate(cap));
    }
    
    public static void createNewEmptyStructureBlock(final String string, final BlockPos fu2, final BlockPos fu3, final Rotation cap, final ServerLevel zd) {
        final BoundingBox ctd6 = getStructureBoundingBox(fu2, fu3, cap);
        clearSpaceForStructure(ctd6, fu2.getY(), zd);
        zd.setBlockAndUpdate(fu2, Blocks.STRUCTURE_BLOCK.defaultBlockState());
        final StructureBlockEntity cel7 = (StructureBlockEntity)zd.getBlockEntity(fu2);
        cel7.setIgnoreEntities(false);
        cel7.setStructureName(new ResourceLocation(string));
        cel7.setStructureSize(fu3);
        cel7.setMode(StructureMode.SAVE);
        cel7.setShowBoundingBox(true);
    }
    
    public static StructureBlockEntity spawnStructure(final String string, final BlockPos fu, final Rotation cap, final int integer, final ServerLevel zd, final boolean boolean6) {
        final BlockPos fu2 = getStructureTemplate(string, zd).getSize();
        final BoundingBox ctd8 = getStructureBoundingBox(fu, fu2, cap);
        BlockPos fu3;
        if (cap == Rotation.NONE) {
            fu3 = fu;
        }
        else if (cap == Rotation.CLOCKWISE_90) {
            fu3 = fu.offset(fu2.getZ() - 1, 0, 0);
        }
        else if (cap == Rotation.CLOCKWISE_180) {
            fu3 = fu.offset(fu2.getX() - 1, 0, fu2.getZ() - 1);
        }
        else {
            if (cap != Rotation.COUNTERCLOCKWISE_90) {
                throw new IllegalArgumentException("Invalid rotation: " + cap);
            }
            fu3 = fu.offset(0, 0, fu2.getX() - 1);
        }
        forceLoadChunks(fu, zd);
        clearSpaceForStructure(ctd8, fu.getY(), zd);
        final StructureBlockEntity cel10 = createStructureBlock(string, fu3, cap, zd, boolean6);
        zd.getBlockTicks().fetchTicksInArea(ctd8, true, false);
        zd.clearBlockEvents(ctd8);
        return cel10;
    }
    
    private static void forceLoadChunks(final BlockPos fu, final ServerLevel zd) {
        final ChunkPos bph3 = new ChunkPos(fu);
        for (int integer4 = -1; integer4 < 4; ++integer4) {
            for (int integer5 = -1; integer5 < 4; ++integer5) {
                final int integer6 = bph3.x + integer4;
                final int integer7 = bph3.z + integer5;
                zd.setChunkForced(integer6, integer7, true);
            }
        }
    }
    
    public static void clearSpaceForStructure(final BoundingBox ctd, final int integer, final ServerLevel zd) {
        final BoundingBox ctd2 = new BoundingBox(ctd.x0 - 2, ctd.y0 - 3, ctd.z0 - 3, ctd.x1 + 3, ctd.y1 + 20, ctd.z1 + 3);
        BlockPos.betweenClosedStream(ctd2).forEach(fu -> clearBlock(integer, fu, zd));
        zd.getBlockTicks().fetchTicksInArea(ctd2, true, false);
        zd.clearBlockEvents(ctd2);
        final AABB deg5 = new AABB(ctd2.x0, ctd2.y0, ctd2.z0, ctd2.x1, ctd2.y1, ctd2.z1);
        final List<Entity> list6 = zd.<Entity>getEntitiesOfClass(Entity.class, deg5, aom -> !(aom instanceof Player));
        list6.forEach(Entity::remove);
    }
    
    public static BoundingBox getStructureBoundingBox(final BlockPos fu1, final BlockPos fu2, final Rotation cap) {
        final BlockPos fu3 = fu1.offset(fu2).offset(-1, -1, -1);
        final BlockPos fu4 = StructureTemplate.transform(fu3, Mirror.NONE, cap, fu1);
        final BoundingBox ctd6 = BoundingBox.createProper(fu1.getX(), fu1.getY(), fu1.getZ(), fu4.getX(), fu4.getY(), fu4.getZ());
        final int integer7 = Math.min(ctd6.x0, ctd6.x1);
        final int integer8 = Math.min(ctd6.z0, ctd6.z1);
        final BlockPos fu5 = new BlockPos(fu1.getX() - integer7, 0, fu1.getZ() - integer8);
        ctd6.move(fu5);
        return ctd6;
    }
    
    public static Optional<BlockPos> findStructureBlockContainingPos(final BlockPos fu, final int integer, final ServerLevel zd) {
        return findStructureBlocks(fu, integer, zd).stream().filter(fu3 -> doesStructureContain(fu3, fu, zd)).findFirst();
    }
    
    @Nullable
    public static BlockPos findNearestStructureBlock(final BlockPos fu, final int integer, final ServerLevel zd) {
        final Comparator<BlockPos> comparator4 = Comparator.<BlockPos>comparingInt(fu2 -> fu2.distManhattan(fu));
        final Collection<BlockPos> collection5 = findStructureBlocks(fu, integer, zd);
        final Optional<BlockPos> optional6 = collection5.stream().min(comparator4);
        return optional6.orElse(null);
    }
    
    public static Collection<BlockPos> findStructureBlocks(final BlockPos fu, final int integer, final ServerLevel zd) {
        final Collection<BlockPos> collection4 = Lists.newArrayList();
        AABB deg5 = new AABB(fu);
        deg5 = deg5.inflate(integer);
        for (int integer2 = (int)deg5.minX; integer2 <= (int)deg5.maxX; ++integer2) {
            for (int integer3 = (int)deg5.minY; integer3 <= (int)deg5.maxY; ++integer3) {
                for (int integer4 = (int)deg5.minZ; integer4 <= (int)deg5.maxZ; ++integer4) {
                    final BlockPos fu2 = new BlockPos(integer2, integer3, integer4);
                    final BlockState cfj10 = zd.getBlockState(fu2);
                    if (cfj10.is(Blocks.STRUCTURE_BLOCK)) {
                        collection4.add(fu2);
                    }
                }
            }
        }
        return collection4;
    }
    
    private static StructureTemplate getStructureTemplate(final String string, final ServerLevel zd) {
        final StructureManager cva3 = zd.getStructureManager();
        final StructureTemplate cve4 = cva3.get(new ResourceLocation(string));
        if (cve4 != null) {
            return cve4;
        }
        final String string2 = string + ".snbt";
        final Path path6 = Paths.get(StructureUtils.testStructuresDir, string2);
        final CompoundTag le7 = tryLoadStructure(path6);
        if (le7 == null) {
            throw new RuntimeException("Could not find structure file " + path6 + ", and the structure is not available in the world structures either.");
        }
        return cva3.readStructure(le7);
    }
    
    private static StructureBlockEntity createStructureBlock(final String string, final BlockPos fu, final Rotation cap, final ServerLevel zd, final boolean boolean5) {
        zd.setBlockAndUpdate(fu, Blocks.STRUCTURE_BLOCK.defaultBlockState());
        final StructureBlockEntity cel6 = (StructureBlockEntity)zd.getBlockEntity(fu);
        cel6.setMode(StructureMode.LOAD);
        cel6.setRotation(cap);
        cel6.setIgnoreEntities(false);
        cel6.setStructureName(new ResourceLocation(string));
        cel6.loadStructure(boolean5);
        if (cel6.getStructureSize() != BlockPos.ZERO) {
            return cel6;
        }
        final StructureTemplate cve7 = getStructureTemplate(string, zd);
        cel6.loadStructure(boolean5, cve7);
        if (cel6.getStructureSize() == BlockPos.ZERO) {
            throw new RuntimeException("Failed to load structure " + string);
        }
        return cel6;
    }
    
    @Nullable
    private static CompoundTag tryLoadStructure(final Path path) {
        try {
            final BufferedReader bufferedReader2 = Files.newBufferedReader(path);
            final String string3 = IOUtils.toString((Reader)bufferedReader2);
            return TagParser.parseTag(string3);
        }
        catch (IOException iOException2) {
            return null;
        }
        catch (CommandSyntaxException commandSyntaxException2) {
            throw new RuntimeException("Error while trying to load structure " + path, (Throwable)commandSyntaxException2);
        }
    }
    
    private static void clearBlock(final int integer, final BlockPos fu, final ServerLevel zd) {
        BlockState cfj4 = null;
        final FlatLevelGeneratorSettings cra5 = FlatLevelGeneratorSettings.getDefault();
        if (cra5 instanceof FlatLevelGeneratorSettings) {
            final BlockState[] arr6 = cra5.getLayers();
            if (fu.getY() < integer && fu.getY() <= arr6.length) {
                cfj4 = arr6[fu.getY() - 1];
            }
        }
        else if (fu.getY() == integer - 1) {
            cfj4 = zd.getBiome(fu).getSurfaceBuilderConfig().getTopMaterial();
        }
        else if (fu.getY() < integer - 1) {
            cfj4 = zd.getBiome(fu).getSurfaceBuilderConfig().getUnderMaterial();
        }
        if (cfj4 == null) {
            cfj4 = Blocks.AIR.defaultBlockState();
        }
        final BlockInput ec6 = new BlockInput(cfj4, Collections.<Property<?>>emptySet(), null);
        ec6.place(zd, fu, 2);
        zd.blockUpdated(fu, cfj4.getBlock());
    }
    
    private static boolean doesStructureContain(final BlockPos fu1, final BlockPos fu2, final ServerLevel zd) {
        final StructureBlockEntity cel4 = (StructureBlockEntity)zd.getBlockEntity(fu1);
        final AABB deg5 = getStructureBounds(cel4).inflate(1.0);
        return deg5.contains(Vec3.atCenterOf(fu2));
    }
    
    static {
        StructureUtils.testStructuresDir = "gameteststructures";
    }
}
