package net.minecraft.gametest.framework;

import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.levelgen.ChunkGeneratorSettings;
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.levelgen.structure.templatesystem.StructureTemplate;
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.levelgen.structure.BoundingBox;
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.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.block.entity.StructureBlockEntity;

public class StructureUtils {
    public static String testStructuresDir;
    
    public static AABB getStructureBounds(final StructureBlockEntity bxl) {
        final BlockPos fk2 = bxl.getBlockPos().offset(bxl.getStructurePos());
        return new AABB(fk2, fk2.offset(bxl.getStructureSize()));
    }
    
    public static void addCommandBlockAndButtonToStartTest(final BlockPos fk, final ServerLevel xd) {
        xd.setBlockAndUpdate(fk, Blocks.COMMAND_BLOCK.defaultBlockState());
        final CommandBlockEntity bwq3 = (CommandBlockEntity)xd.getBlockEntity(fk);
        bwq3.getCommandBlock().setCommand("test runthis");
        xd.setBlockAndUpdate(fk.offset(0, 0, -1), Blocks.STONE_BUTTON.defaultBlockState());
    }
    
    public static void createNewEmptyStructureBlock(final String string, final BlockPos fk2, final BlockPos fk3, final int integer, final ServerLevel xd) {
        final BoundingBox clb6 = createStructureBoundingBox(fk2, fk3, integer);
        clearSpaceForStructure(clb6, fk2.getY(), xd);
        xd.setBlockAndUpdate(fk2, Blocks.STRUCTURE_BLOCK.defaultBlockState());
        final StructureBlockEntity bxl7 = (StructureBlockEntity)xd.getBlockEntity(fk2);
        bxl7.setIgnoreEntities(false);
        bxl7.setStructureName(new ResourceLocation(string));
        bxl7.setStructureSize(fk3);
        bxl7.setMode(StructureMode.SAVE);
        bxl7.setShowBoundingBox(true);
    }
    
    public static StructureBlockEntity spawnStructure(final String string, final BlockPos fk, final int integer, final ServerLevel xd, final boolean boolean5) {
        final BoundingBox clb6 = createStructureBoundingBox(fk, getStructureTemplate(string, xd).getSize(), integer);
        forceLoadChunks(fk, xd);
        clearSpaceForStructure(clb6, fk.getY(), xd);
        final StructureBlockEntity bxl7 = createStructureBlock(string, fk, xd, boolean5);
        xd.getBlockTicks().fetchTicksInArea(clb6, true, false);
        xd.clearBlockEvents(clb6);
        return bxl7;
    }
    
    private static void forceLoadChunks(final BlockPos fk, final ServerLevel xd) {
        final ChunkPos bjh3 = new ChunkPos(fk);
        for (int integer4 = -1; integer4 < 4; ++integer4) {
            for (int integer5 = -1; integer5 < 4; ++integer5) {
                final int integer6 = bjh3.x + integer4;
                final int integer7 = bjh3.z + integer5;
                xd.setChunkForced(integer6, integer7, true);
            }
        }
    }
    
    public static void clearSpaceForStructure(final BoundingBox clb, final int integer, final ServerLevel xd) {
        BlockPos.betweenClosedStream(clb).forEach(fk -> clearBlock(integer, fk, xd));
        xd.getBlockTicks().fetchTicksInArea(clb, true, false);
        xd.clearBlockEvents(clb);
        final AABB cvf4 = new AABB(clb.x0, clb.y0, clb.z0, clb.x1, clb.y1, clb.z1);
        final List<Entity> list5 = xd.<Entity>getEntitiesOfClass(Entity.class, cvf4, akq -> !(akq instanceof Player));
        list5.forEach(Entity::remove);
    }
    
    public static BoundingBox createStructureBoundingBox(final BlockPos fk1, final BlockPos fk2, final int integer) {
        final BlockPos fk3 = fk1.offset(-integer, -3, -integer);
        final BlockPos fk4 = fk1.offset(fk2).offset(integer - 1, 30, integer - 1);
        return BoundingBox.createProper(fk3.getX(), fk3.getY(), fk3.getZ(), fk4.getX(), fk4.getY(), fk4.getZ());
    }
    
    public static Optional<BlockPos> findStructureBlockContainingPos(final BlockPos fk, final int integer, final ServerLevel xd) {
        return findStructureBlocks(fk, integer, xd).stream().filter(fk3 -> doesStructureContain(fk3, fk, xd)).findFirst();
    }
    
    @Nullable
    public static BlockPos findNearestStructureBlock(final BlockPos fk, final int integer, final ServerLevel xd) {
        final Comparator<BlockPos> comparator4 = Comparator.<BlockPos>comparingInt(fk2 -> fk2.distManhattan(fk));
        final Collection<BlockPos> collection5 = findStructureBlocks(fk, integer, xd);
        final Optional<BlockPos> optional6 = collection5.stream().min(comparator4);
        return optional6.orElse(null);
    }
    
    public static Collection<BlockPos> findStructureBlocks(final BlockPos fk, final int integer, final ServerLevel xd) {
        final Collection<BlockPos> collection4 = Lists.newArrayList();
        AABB cvf5 = new AABB(fk);
        cvf5 = cvf5.inflate(integer);
        for (int integer2 = (int)cvf5.minX; integer2 <= (int)cvf5.maxX; ++integer2) {
            for (int integer3 = (int)cvf5.minY; integer3 <= (int)cvf5.maxY; ++integer3) {
                for (int integer4 = (int)cvf5.minZ; integer4 <= (int)cvf5.maxZ; ++integer4) {
                    final BlockPos fk2 = new BlockPos(integer2, integer3, integer4);
                    final BlockState byj10 = xd.getBlockState(fk2);
                    if (byj10.getBlock() == Blocks.STRUCTURE_BLOCK) {
                        collection4.add(fk2);
                    }
                }
            }
        }
        return collection4;
    }
    
    private static StructureTemplate getStructureTemplate(final String string, final ServerLevel xd) {
        final StructureManager cmo3 = xd.getStructureManager();
        final StructureTemplate cms4 = cmo3.get(new ResourceLocation(string));
        if (cms4 != null) {
            return cms4;
        }
        final String string2 = string + ".snbt";
        final Path path6 = Paths.get(StructureUtils.testStructuresDir, string2);
        final CompoundTag jt7 = tryLoadStructure(path6);
        if (jt7 == null) {
            throw new RuntimeException("Could not find structure file " + path6 + ", and the structure is not available in the world structures either.");
        }
        return cmo3.readStructure(jt7);
    }
    
    private static StructureBlockEntity createStructureBlock(final String string, final BlockPos fk, final ServerLevel xd, final boolean boolean4) {
        xd.setBlockAndUpdate(fk, Blocks.STRUCTURE_BLOCK.defaultBlockState());
        final StructureBlockEntity bxl5 = (StructureBlockEntity)xd.getBlockEntity(fk);
        bxl5.setMode(StructureMode.LOAD);
        bxl5.setIgnoreEntities(false);
        bxl5.setStructureName(new ResourceLocation(string));
        bxl5.loadStructure(boolean4);
        if (bxl5.getStructureSize() != BlockPos.ZERO) {
            return bxl5;
        }
        final StructureTemplate cms6 = getStructureTemplate(string, xd);
        bxl5.loadStructure(boolean4, cms6);
        if (bxl5.getStructureSize() == BlockPos.ZERO) {
            throw new RuntimeException("Failed to load structure " + string);
        }
        return bxl5;
    }
    
    @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 fk, final ServerLevel xd) {
        final ChunkGeneratorSettings cbq5 = (ChunkGeneratorSettings)xd.getChunkSource().getGenerator().getSettings();
        BlockState byj4;
        if (cbq5 instanceof FlatLevelGeneratorSettings) {
            final BlockState[] arr6 = ((FlatLevelGeneratorSettings)cbq5).getLayers();
            if (fk.getY() < integer) {
                byj4 = arr6[fk.getY() - 1];
            }
            else {
                byj4 = Blocks.AIR.defaultBlockState();
            }
        }
        else if (fk.getY() == integer - 1) {
            byj4 = xd.getBiome(fk).getSurfaceBuilderConfig().getTopMaterial();
        }
        else if (fk.getY() < integer - 1) {
            byj4 = xd.getBiome(fk).getSurfaceBuilderConfig().getUnderMaterial();
        }
        else {
            byj4 = Blocks.AIR.defaultBlockState();
        }
        final BlockInput ds6 = new BlockInput(byj4, Collections.<Property<?>>emptySet(), null);
        ds6.place(xd, fk, 2);
        xd.blockUpdated(fk, byj4.getBlock());
    }
    
    private static boolean doesStructureContain(final BlockPos fk1, final BlockPos fk2, final ServerLevel xd) {
        final StructureBlockEntity bxl4 = (StructureBlockEntity)xd.getBlockEntity(fk1);
        final AABB cvf5 = getStructureBounds(bxl4);
        return cvf5.contains(new Vec3(fk2));
    }
    
    static {
        StructureUtils.testStructuresDir = "gameteststructures";
    }
}
