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

import net.minecraft.world.level.block.state.StateHolder;
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.Blocks;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.state.BlockState;
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 double getViewDistance() {
        return 96.0;
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        super.save(le);
        le.putString("name", this.getStructureName());
        le.putString("author", this.author);
        le.putString("metadata", this.metaData);
        le.putInt("posX", this.structurePos.getX());
        le.putInt("posY", this.structurePos.getY());
        le.putInt("posZ", this.structurePos.getZ());
        le.putInt("sizeX", this.structureSize.getX());
        le.putInt("sizeY", this.structureSize.getY());
        le.putInt("sizeZ", this.structureSize.getZ());
        le.putString("rotation", this.rotation.toString());
        le.putString("mirror", this.mirror.toString());
        le.putString("mode", this.mode.toString());
        le.putBoolean("ignoreEntities", this.ignoreEntities);
        le.putBoolean("powered", this.powered);
        le.putBoolean("showair", this.showAir);
        le.putBoolean("showboundingbox", this.showBoundingBox);
        le.putFloat("integrity", this.integrity);
        le.putLong("seed", this.seed);
        return le;
    }
    
    @Override
    public void load(final BlockState cfj, final CompoundTag le) {
        super.load(cfj, le);
        this.setStructureName(le.getString("name"));
        this.author = le.getString("author");
        this.metaData = le.getString("metadata");
        final int integer4 = Mth.clamp(le.getInt("posX"), -48, 48);
        final int integer5 = Mth.clamp(le.getInt("posY"), -48, 48);
        final int integer6 = Mth.clamp(le.getInt("posZ"), -48, 48);
        this.structurePos = new BlockPos(integer4, integer5, integer6);
        final int integer7 = Mth.clamp(le.getInt("sizeX"), 0, 48);
        final int integer8 = Mth.clamp(le.getInt("sizeY"), 0, 48);
        final int integer9 = Mth.clamp(le.getInt("sizeZ"), 0, 48);
        this.structureSize = new BlockPos(integer7, integer8, integer9);
        try {
            this.rotation = Rotation.valueOf(le.getString("rotation"));
        }
        catch (IllegalArgumentException illegalArgumentException10) {
            this.rotation = Rotation.NONE;
        }
        try {
            this.mirror = Mirror.valueOf(le.getString("mirror"));
        }
        catch (IllegalArgumentException illegalArgumentException10) {
            this.mirror = Mirror.NONE;
        }
        try {
            this.mode = StructureMode.valueOf(le.getString("mode"));
        }
        catch (IllegalArgumentException illegalArgumentException10) {
            this.mode = StructureMode.DATA;
        }
        this.ignoreEntities = le.getBoolean("ignoreEntities");
        this.powered = le.getBoolean("powered");
        this.showAir = le.getBoolean("showair");
        this.showBoundingBox = le.getBoolean("showboundingbox");
        if (le.contains("integrity")) {
            this.integrity = le.getFloat("integrity");
        }
        else {
            this.integrity = 1.0f;
        }
        this.seed = le.getLong("seed");
        this.updateBlockState();
    }
    
    private void updateBlockState() {
        if (this.level == null) {
            return;
        }
        final BlockPos fu2 = this.getBlockPos();
        final BlockState cfj3 = this.level.getBlockState(fu2);
        if (cfj3.is(Blocks.STRUCTURE_BLOCK)) {
            this.level.setBlock(fu2, ((StateHolder<O, BlockState>)cfj3).<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 bec) {
        if (!bec.canUseGameMasterBlocks()) {
            return false;
        }
        if (bec.getCommandSenderWorld().isClientSide) {
            bec.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 uh) {
        this.structureName = uh;
    }
    
    public void createdBy(final LivingEntity aoy) {
        this.author = aoy.getName().getString();
    }
    
    public BlockPos getStructurePos() {
        return this.structurePos;
    }
    
    public void setStructurePos(final BlockPos fu) {
        this.structurePos = fu;
    }
    
    public BlockPos getStructureSize() {
        return this.structureSize;
    }
    
    public void setStructureSize(final BlockPos fu) {
        this.structureSize = fu;
    }
    
    public Mirror getMirror() {
        return this.mirror;
    }
    
    public void setMirror(final Mirror bzj) {
        this.mirror = bzj;
    }
    
    public Rotation getRotation() {
        return this.rotation;
    }
    
    public void setRotation(final Rotation cap) {
        this.rotation = cap;
    }
    
    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 cgq) {
        this.mode = cgq;
        final BlockState cfj3 = this.level.getBlockState(this.getBlockPos());
        if (cfj3.is(Blocks.STRUCTURE_BLOCK)) {
            this.level.setBlock(this.getBlockPos(), ((StateHolder<O, BlockState>)cfj3).<StructureMode, StructureMode>setValue(StructureBlock.MODE, cgq), 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 fu2 = this.getBlockPos();
        final int integer3 = 80;
        final BlockPos fu3 = new BlockPos(fu2.getX() - 80, 0, fu2.getZ() - 80);
        final BlockPos fu4 = new BlockPos(fu2.getX() + 80, 255, fu2.getZ() + 80);
        final List<StructureBlockEntity> list6 = this.getNearbyCornerBlocks(fu3, fu4);
        final List<StructureBlockEntity> list7 = this.filterRelatedCornerBlocks(list6);
        if (list7.size() < 1) {
            return false;
        }
        final BoundingBox ctd8 = this.calculateEnclosingBoundingBox(fu2, list7);
        if (ctd8.x1 - ctd8.x0 > 1 && ctd8.y1 - ctd8.y0 > 1 && ctd8.z1 - ctd8.z0 > 1) {
            this.structurePos = new BlockPos(ctd8.x0 - fu2.getX() + 1, ctd8.y0 - fu2.getY() + 1, ctd8.z0 - fu2.getZ() + 1);
            this.structureSize = new BlockPos(ctd8.x1 - ctd8.x0 - 1, ctd8.y1 - ctd8.y0 - 1, ctd8.z1 - ctd8.z0 - 1);
            this.setChanged();
            final BlockState cfj9 = this.level.getBlockState(fu2);
            this.level.sendBlockUpdated(fu2, cfj9, cfj9, 3);
            return true;
        }
        return false;
    }
    
    private List<StructureBlockEntity> filterRelatedCornerBlocks(final List<StructureBlockEntity> list) {
        final Predicate<StructureBlockEntity> predicate3 = cel -> cel.mode == StructureMode.CORNER && Objects.equals(this.structureName, cel.structureName);
        return list.stream().filter(predicate3).collect(Collectors.toList());
    }
    
    private List<StructureBlockEntity> getNearbyCornerBlocks(final BlockPos fu1, final BlockPos fu2) {
        final List<StructureBlockEntity> list4 = Lists.newArrayList();
        for (final BlockPos fu3 : BlockPos.betweenClosed(fu1, fu2)) {
            final BlockState cfj7 = this.level.getBlockState(fu3);
            if (!cfj7.is(Blocks.STRUCTURE_BLOCK)) {
                continue;
            }
            final BlockEntity cdl8 = this.level.getBlockEntity(fu3);
            if (cdl8 == null || !(cdl8 instanceof StructureBlockEntity)) {
                continue;
            }
            list4.add((StructureBlockEntity)cdl8);
        }
        return list4;
    }
    
    private BoundingBox calculateEnclosingBoundingBox(final BlockPos fu, final List<StructureBlockEntity> list) {
        BoundingBox ctd4;
        if (list.size() > 1) {
            final BlockPos fu2 = list.get(0).getBlockPos();
            ctd4 = new BoundingBox(fu2, fu2);
        }
        else {
            ctd4 = new BoundingBox(fu, fu);
        }
        for (final StructureBlockEntity cel6 : list) {
            final BlockPos fu3 = cel6.getBlockPos();
            if (fu3.getX() < ctd4.x0) {
                ctd4.x0 = fu3.getX();
            }
            else if (fu3.getX() > ctd4.x1) {
                ctd4.x1 = fu3.getX();
            }
            if (fu3.getY() < ctd4.y0) {
                ctd4.y0 = fu3.getY();
            }
            else if (fu3.getY() > ctd4.y1) {
                ctd4.y1 = fu3.getY();
            }
            if (fu3.getZ() < ctd4.z0) {
                ctd4.z0 = fu3.getZ();
            }
            else {
                if (fu3.getZ() <= ctd4.z1) {
                    continue;
                }
                ctd4.z1 = fu3.getZ();
            }
        }
        return ctd4;
    }
    
    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 fu3 = this.getBlockPos().offset(this.structurePos);
        final ServerLevel zd4 = (ServerLevel)this.level;
        final StructureManager cva5 = zd4.getStructureManager();
        StructureTemplate cve6;
        try {
            cve6 = cva5.getOrCreate(this.structureName);
        }
        catch (ResourceLocationException t7) {
            return false;
        }
        cve6.fillFromWorld(this.level, fu3, this.structureSize, !this.ignoreEntities, Blocks.STRUCTURE_VOID);
        cve6.setAuthor(this.author);
        if (boolean1) {
            try {
                return cva5.save(this.structureName);
            }
            catch (ResourceLocationException t7) {
                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 zd3 = (ServerLevel)this.level;
        final StructureManager cva4 = zd3.getStructureManager();
        StructureTemplate cve5;
        try {
            cve5 = cva4.get(this.structureName);
        }
        catch (ResourceLocationException t6) {
            return false;
        }
        return cve5 != null && this.loadStructure(boolean1, cve5);
    }
    
    public boolean loadStructure(final boolean boolean1, final StructureTemplate cve) {
        final BlockPos fu4 = this.getBlockPos();
        if (!StringUtil.isNullOrEmpty(cve.getAuthor())) {
            this.author = cve.getAuthor();
        }
        final BlockPos fu5 = cve.getSize();
        final boolean boolean2 = this.structureSize.equals(fu5);
        if (!boolean2) {
            this.structureSize = fu5;
            this.setChanged();
            final BlockState cfj7 = this.level.getBlockState(fu4);
            this.level.sendBlockUpdated(fu4, cfj7, cfj7, 3);
        }
        if (!boolean1 || boolean2) {
            final StructurePlaceSettings cvb7 = new StructurePlaceSettings().setMirror(this.mirror).setRotation(this.rotation).setIgnoreEntities(this.ignoreEntities).setChunkPos(null);
            if (this.integrity < 1.0f) {
                cvb7.clearProcessors().addProcessor(new BlockRotProcessor(Mth.clamp(this.integrity, 0.0f, 1.0f))).setRandom(createRandom(this.seed));
            }
            final BlockPos fu6 = fu4.offset(this.structurePos);
            cve.placeInWorldChunk(this.level, fu6, cvb7, createRandom(this.seed));
            return true;
        }
        return false;
    }
    
    public void unloadStructure() {
        if (this.structureName == null) {
            return;
        }
        final ServerLevel zd2 = (ServerLevel)this.level;
        final StructureManager cva3 = zd2.getStructureManager();
        cva3.remove(this.structureName);
    }
    
    public boolean isStructureLoadable() {
        if (this.mode != StructureMode.LOAD || this.level.isClientSide || this.structureName == null) {
            return false;
        }
        final ServerLevel zd2 = (ServerLevel)this.level;
        final StructureManager cva3 = zd2.getStructureManager();
        try {
            return cva3.get(this.structureName) != null;
        }
        catch (ResourceLocationException t4) {
            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;
    }
}
