package net.minecraft.world.level.block.entity;

import net.minecraft.world.level.block.state.AbstractStateHolder;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockRotProcessor;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.Util;
import java.util.Random;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.ResourceLocationException;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.Vec3i;
import java.util.Iterator;
import com.google.common.collect.Lists;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.Predicate;
import java.util.Objects;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import java.util.List;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.util.StringUtil;
import net.minecraft.world.entity.player.Player;
import javax.annotation.Nullable;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.StructureBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.util.Mth;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.properties.StructureMode;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;

public class StructureBlockEntity extends BlockEntity {
    private ResourceLocation structureName;
    private String author;
    private String metaData;
    private BlockPos structurePos;
    private BlockPos structureSize;
    private Mirror mirror;
    private Rotation rotation;
    private StructureMode mode;
    private boolean ignoreEntities;
    private boolean powered;
    private boolean showAir;
    private boolean showBoundingBox;
    private float integrity;
    private long seed;
    
    public StructureBlockEntity() {
        super(BlockEntityType.STRUCTURE_BLOCK);
        this.author = "";
        this.metaData = "";
        this.structurePos = new BlockPos(0, 1, 0);
        this.structureSize = BlockPos.ZERO;
        this.mirror = Mirror.NONE;
        this.rotation = Rotation.NONE;
        this.mode = StructureMode.DATA;
        this.ignoreEntities = true;
        this.showBoundingBox = true;
        this.integrity = 1.0f;
    }
    
    @Override
    public CompoundTag save(final CompoundTag jt) {
        super.save(jt);
        jt.putString("name", this.getStructureName());
        jt.putString("author", this.author);
        jt.putString("metadata", this.metaData);
        jt.putInt("posX", this.structurePos.getX());
        jt.putInt("posY", this.structurePos.getY());
        jt.putInt("posZ", this.structurePos.getZ());
        jt.putInt("sizeX", this.structureSize.getX());
        jt.putInt("sizeY", this.structureSize.getY());
        jt.putInt("sizeZ", this.structureSize.getZ());
        jt.putString("rotation", this.rotation.toString());
        jt.putString("mirror", this.mirror.toString());
        jt.putString("mode", this.mode.toString());
        jt.putBoolean("ignoreEntities", this.ignoreEntities);
        jt.putBoolean("powered", this.powered);
        jt.putBoolean("showair", this.showAir);
        jt.putBoolean("showboundingbox", this.showBoundingBox);
        jt.putFloat("integrity", this.integrity);
        jt.putLong("seed", this.seed);
        return jt;
    }
    
    @Override
    public void load(final CompoundTag jt) {
        super.load(jt);
        this.setStructureName(jt.getString("name"));
        this.author = jt.getString("author");
        this.metaData = jt.getString("metadata");
        final int integer3 = Mth.clamp(jt.getInt("posX"), -32, 32);
        final int integer4 = Mth.clamp(jt.getInt("posY"), -32, 32);
        final int integer5 = Mth.clamp(jt.getInt("posZ"), -32, 32);
        this.structurePos = new BlockPos(integer3, integer4, integer5);
        final int integer6 = Mth.clamp(jt.getInt("sizeX"), 0, 32);
        final int integer7 = Mth.clamp(jt.getInt("sizeY"), 0, 32);
        final int integer8 = Mth.clamp(jt.getInt("sizeZ"), 0, 32);
        this.structureSize = new BlockPos(integer6, integer7, integer8);
        try {
            this.rotation = Rotation.valueOf(jt.getString("rotation"));
        }
        catch (IllegalArgumentException illegalArgumentException9) {
            this.rotation = Rotation.NONE;
        }
        try {
            this.mirror = Mirror.valueOf(jt.getString("mirror"));
        }
        catch (IllegalArgumentException illegalArgumentException9) {
            this.mirror = Mirror.NONE;
        }
        try {
            this.mode = StructureMode.valueOf(jt.getString("mode"));
        }
        catch (IllegalArgumentException illegalArgumentException9) {
            this.mode = StructureMode.DATA;
        }
        this.ignoreEntities = jt.getBoolean("ignoreEntities");
        this.powered = jt.getBoolean("powered");
        this.showAir = jt.getBoolean("showair");
        this.showBoundingBox = jt.getBoolean("showboundingbox");
        if (jt.contains("integrity")) {
            this.integrity = jt.getFloat("integrity");
        }
        else {
            this.integrity = 1.0f;
        }
        this.seed = jt.getLong("seed");
        this.updateBlockState();
    }
    
    private void updateBlockState() {
        if (this.level == null) {
            return;
        }
        final BlockPos fk2 = this.getBlockPos();
        final BlockState byj3 = this.level.getBlockState(fk2);
        if (byj3.getBlock() == Blocks.STRUCTURE_BLOCK) {
            this.level.setBlock(fk2, ((AbstractStateHolder<O, BlockState>)byj3).<StructureMode, StructureMode>setValue(StructureBlock.MODE, this.mode), 2);
        }
    }
    
    @Nullable
    @Override
    public ClientboundBlockEntityDataPacket getUpdatePacket() {
        return new ClientboundBlockEntityDataPacket(this.worldPosition, 7, this.getUpdateTag());
    }
    
    @Override
    public CompoundTag getUpdateTag() {
        return this.save(new CompoundTag());
    }
    
    public boolean usedBy(final Player ayj) {
        if (!ayj.canUseGameMasterBlocks()) {
            return false;
        }
        if (ayj.getCommandSenderWorld().isClientSide) {
            ayj.openStructureBlock(this);
        }
        return true;
    }
    
    public String getStructureName() {
        return (this.structureName == null) ? "" : this.structureName.toString();
    }
    
    public String getStructurePath() {
        return (this.structureName == null) ? "" : this.structureName.getPath();
    }
    
    public boolean hasStructureName() {
        return this.structureName != null;
    }
    
    public void setStructureName(@Nullable final String string) {
        this.setStructureName(StringUtil.isNullOrEmpty(string) ? null : ResourceLocation.tryParse(string));
    }
    
    public void setStructureName(@Nullable final ResourceLocation sm) {
        this.structureName = sm;
    }
    
    public void createdBy(final LivingEntity akz) {
        this.author = akz.getName().getString();
    }
    
    public BlockPos getStructurePos() {
        return this.structurePos;
    }
    
    public void setStructurePos(final BlockPos fk) {
        this.structurePos = fk;
    }
    
    public BlockPos getStructureSize() {
        return this.structureSize;
    }
    
    public void setStructureSize(final BlockPos fk) {
        this.structureSize = fk;
    }
    
    public Mirror getMirror() {
        return this.mirror;
    }
    
    public void setMirror(final Mirror bsu) {
        this.mirror = bsu;
    }
    
    public Rotation getRotation() {
        return this.rotation;
    }
    
    public void setRotation(final Rotation btu) {
        this.rotation = btu;
    }
    
    public String getMetaData() {
        return this.metaData;
    }
    
    public void setMetaData(final String string) {
        this.metaData = string;
    }
    
    public StructureMode getMode() {
        return this.mode;
    }
    
    public void setMode(final StructureMode bzr) {
        this.mode = bzr;
        final BlockState byj3 = this.level.getBlockState(this.getBlockPos());
        if (byj3.getBlock() == Blocks.STRUCTURE_BLOCK) {
            this.level.setBlock(this.getBlockPos(), ((AbstractStateHolder<O, BlockState>)byj3).<StructureMode, StructureMode>setValue(StructureBlock.MODE, bzr), 2);
        }
    }
    
    public void nextMode() {
        switch (this.getMode()) {
            case SAVE: {
                this.setMode(StructureMode.LOAD);
                break;
            }
            case LOAD: {
                this.setMode(StructureMode.CORNER);
                break;
            }
            case CORNER: {
                this.setMode(StructureMode.DATA);
                break;
            }
            case DATA: {
                this.setMode(StructureMode.SAVE);
                break;
            }
        }
    }
    
    public boolean isIgnoreEntities() {
        return this.ignoreEntities;
    }
    
    public void setIgnoreEntities(final boolean boolean1) {
        this.ignoreEntities = boolean1;
    }
    
    public float getIntegrity() {
        return this.integrity;
    }
    
    public void setIntegrity(final float float1) {
        this.integrity = float1;
    }
    
    public long getSeed() {
        return this.seed;
    }
    
    public void setSeed(final long long1) {
        this.seed = long1;
    }
    
    public boolean detectSize() {
        if (this.mode != StructureMode.SAVE) {
            return false;
        }
        final BlockPos fk2 = this.getBlockPos();
        final int integer3 = 80;
        final BlockPos fk3 = new BlockPos(fk2.getX() - 80, 0, fk2.getZ() - 80);
        final BlockPos fk4 = new BlockPos(fk2.getX() + 80, 255, fk2.getZ() + 80);
        final List<StructureBlockEntity> list6 = this.getNearbyCornerBlocks(fk3, fk4);
        final List<StructureBlockEntity> list7 = this.filterRelatedCornerBlocks(list6);
        if (list7.size() < 1) {
            return false;
        }
        final BoundingBox clb8 = this.calculateEnclosingBoundingBox(fk2, list7);
        if (clb8.x1 - clb8.x0 > 1 && clb8.y1 - clb8.y0 > 1 && clb8.z1 - clb8.z0 > 1) {
            this.structurePos = new BlockPos(clb8.x0 - fk2.getX() + 1, clb8.y0 - fk2.getY() + 1, clb8.z0 - fk2.getZ() + 1);
            this.structureSize = new BlockPos(clb8.x1 - clb8.x0 - 1, clb8.y1 - clb8.y0 - 1, clb8.z1 - clb8.z0 - 1);
            this.setChanged();
            final BlockState byj9 = this.level.getBlockState(fk2);
            this.level.sendBlockUpdated(fk2, byj9, byj9, 3);
            return true;
        }
        return false;
    }
    
    private List<StructureBlockEntity> filterRelatedCornerBlocks(final List<StructureBlockEntity> list) {
        final Predicate<StructureBlockEntity> predicate3 = bxl -> bxl.mode == StructureMode.CORNER && Objects.equals(this.structureName, bxl.structureName);
        return list.stream().filter(predicate3).collect(Collectors.toList());
    }
    
    private List<StructureBlockEntity> getNearbyCornerBlocks(final BlockPos fk1, final BlockPos fk2) {
        final List<StructureBlockEntity> list4 = Lists.newArrayList();
        for (final BlockPos fk3 : BlockPos.betweenClosed(fk1, fk2)) {
            final BlockState byj7 = this.level.getBlockState(fk3);
            if (byj7.getBlock() != Blocks.STRUCTURE_BLOCK) {
                continue;
            }
            final BlockEntity bwl8 = this.level.getBlockEntity(fk3);
            if (bwl8 == null || !(bwl8 instanceof StructureBlockEntity)) {
                continue;
            }
            list4.add((StructureBlockEntity)bwl8);
        }
        return list4;
    }
    
    private BoundingBox calculateEnclosingBoundingBox(final BlockPos fk, final List<StructureBlockEntity> list) {
        BoundingBox clb4;
        if (list.size() > 1) {
            final BlockPos fk2 = list.get(0).getBlockPos();
            clb4 = new BoundingBox(fk2, fk2);
        }
        else {
            clb4 = new BoundingBox(fk, fk);
        }
        for (final StructureBlockEntity bxl6 : list) {
            final BlockPos fk3 = bxl6.getBlockPos();
            if (fk3.getX() < clb4.x0) {
                clb4.x0 = fk3.getX();
            }
            else if (fk3.getX() > clb4.x1) {
                clb4.x1 = fk3.getX();
            }
            if (fk3.getY() < clb4.y0) {
                clb4.y0 = fk3.getY();
            }
            else if (fk3.getY() > clb4.y1) {
                clb4.y1 = fk3.getY();
            }
            if (fk3.getZ() < clb4.z0) {
                clb4.z0 = fk3.getZ();
            }
            else {
                if (fk3.getZ() <= clb4.z1) {
                    continue;
                }
                clb4.z1 = fk3.getZ();
            }
        }
        return clb4;
    }
    
    public boolean saveStructure() {
        return this.saveStructure(true);
    }
    
    public boolean saveStructure(final boolean boolean1) {
        if (this.mode != StructureMode.SAVE || this.level.isClientSide || this.structureName == null) {
            return false;
        }
        final BlockPos fk3 = this.getBlockPos().offset(this.structurePos);
        final ServerLevel xd4 = (ServerLevel)this.level;
        final StructureManager cmo5 = xd4.getStructureManager();
        StructureTemplate cms6;
        try {
            cms6 = cmo5.getOrCreate(this.structureName);
        }
        catch (ResourceLocationException r7) {
            return false;
        }
        cms6.fillFromWorld(this.level, fk3, this.structureSize, !this.ignoreEntities, Blocks.STRUCTURE_VOID);
        cms6.setAuthor(this.author);
        if (boolean1) {
            try {
                return cmo5.save(this.structureName);
            }
            catch (ResourceLocationException r7) {
                return false;
            }
        }
        return true;
    }
    
    public boolean loadStructure() {
        return this.loadStructure(true);
    }
    
    private static Random createRandom(final long long1) {
        if (long1 == 0L) {
            return new Random(Util.getMillis());
        }
        return new Random(long1);
    }
    
    public boolean loadStructure(final boolean boolean1) {
        if (this.mode != StructureMode.LOAD || this.level.isClientSide || this.structureName == null) {
            return false;
        }
        final ServerLevel xd3 = (ServerLevel)this.level;
        final StructureManager cmo4 = xd3.getStructureManager();
        StructureTemplate cms5;
        try {
            cms5 = cmo4.get(this.structureName);
        }
        catch (ResourceLocationException r6) {
            return false;
        }
        return cms5 != null && this.loadStructure(boolean1, cms5);
    }
    
    public boolean loadStructure(final boolean boolean1, final StructureTemplate cms) {
        final BlockPos fk4 = this.getBlockPos();
        if (!StringUtil.isNullOrEmpty(cms.getAuthor())) {
            this.author = cms.getAuthor();
        }
        final BlockPos fk5 = cms.getSize();
        final boolean boolean2 = this.structureSize.equals(fk5);
        if (!boolean2) {
            this.structureSize = fk5;
            this.setChanged();
            final BlockState byj7 = this.level.getBlockState(fk4);
            this.level.sendBlockUpdated(fk4, byj7, byj7, 3);
        }
        if (!boolean1 || boolean2) {
            final StructurePlaceSettings cmp7 = new StructurePlaceSettings().setMirror(this.mirror).setRotation(this.rotation).setIgnoreEntities(this.ignoreEntities).setChunkPos(null);
            if (this.integrity < 1.0f) {
                cmp7.clearProcessors().addProcessor(new BlockRotProcessor(Mth.clamp(this.integrity, 0.0f, 1.0f))).setRandom(createRandom(this.seed));
            }
            final BlockPos fk6 = fk4.offset(this.structurePos);
            cms.placeInWorldChunk(this.level, fk6, cmp7);
            return true;
        }
        return false;
    }
    
    public void unloadStructure() {
        if (this.structureName == null) {
            return;
        }
        final ServerLevel xd2 = (ServerLevel)this.level;
        final StructureManager cmo3 = xd2.getStructureManager();
        cmo3.remove(this.structureName);
    }
    
    public boolean isStructureLoadable() {
        if (this.mode != StructureMode.LOAD || this.level.isClientSide || this.structureName == null) {
            return false;
        }
        final ServerLevel xd2 = (ServerLevel)this.level;
        final StructureManager cmo3 = xd2.getStructureManager();
        try {
            return cmo3.get(this.structureName) != null;
        }
        catch (ResourceLocationException r4) {
            return false;
        }
    }
    
    public boolean isPowered() {
        return this.powered;
    }
    
    public void setPowered(final boolean boolean1) {
        this.powered = boolean1;
    }
    
    public boolean getShowAir() {
        return this.showAir;
    }
    
    public void setShowAir(final boolean boolean1) {
        this.showAir = boolean1;
    }
    
    public boolean getShowBoundingBox() {
        return this.showBoundingBox;
    }
    
    public void setShowBoundingBox(final boolean boolean1) {
        this.showBoundingBox = boolean1;
    }
    
    public enum UpdateType {
        UPDATE_DATA, 
        SAVE_AREA, 
        LOAD_AREA, 
        SCAN_AREA;
    }
}
