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

import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.core.Direction;
import net.minecraft.world.level.levelgen.feature.configurations.EndGatewayConfiguration;
import java.util.Iterator;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.WorldGenLevel;
import java.util.Random;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.projectile.ThrownEnderpearl;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.util.Mth;
import java.util.List;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.CompoundTag;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import org.apache.logging.log4j.Logger;

public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity implements TickableBlockEntity {
    private static final Logger LOGGER;
    private long age;
    private int teleportCooldown;
    @Nullable
    private BlockPos exitPortal;
    private boolean exactTeleport;
    
    public TheEndGatewayBlockEntity() {
        super(BlockEntityType.END_GATEWAY);
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        super.save(le);
        le.putLong("Age", this.age);
        if (this.exitPortal != null) {
            le.put("ExitPortal", NbtUtils.writeBlockPos(this.exitPortal));
        }
        if (this.exactTeleport) {
            le.putBoolean("ExactTeleport", this.exactTeleport);
        }
        return le;
    }
    
    @Override
    public void load(final BlockState cfj, final CompoundTag le) {
        super.load(cfj, le);
        this.age = le.getLong("Age");
        if (le.contains("ExitPortal", 10)) {
            this.exitPortal = NbtUtils.readBlockPos(le.getCompound("ExitPortal"));
        }
        this.exactTeleport = le.getBoolean("ExactTeleport");
    }
    
    @Override
    public double getViewDistance() {
        return 256.0;
    }
    
    @Override
    public void tick() {
        final boolean boolean2 = this.isSpawning();
        final boolean boolean3 = this.isCoolingDown();
        ++this.age;
        if (boolean3) {
            --this.teleportCooldown;
        }
        else if (!this.level.isClientSide) {
            final List<Entity> list4 = this.level.<Entity>getEntitiesOfClass(Entity.class, new AABB(this.getBlockPos()));
            if (!list4.isEmpty()) {
                this.teleportEntity(list4.get(this.level.random.nextInt(list4.size())));
            }
            if (this.age % 2400L == 0L) {
                this.triggerCooldown();
            }
        }
        if (boolean2 != this.isSpawning() || boolean3 != this.isCoolingDown()) {
            this.setChanged();
        }
    }
    
    public boolean isSpawning() {
        return this.age < 200L;
    }
    
    public boolean isCoolingDown() {
        return this.teleportCooldown > 0;
    }
    
    public float getSpawnPercent(final float float1) {
        return Mth.clamp((this.age + float1) / 200.0f, 0.0f, 1.0f);
    }
    
    public float getCooldownPercent(final float float1) {
        return 1.0f - Mth.clamp((this.teleportCooldown - float1) / 40.0f, 0.0f, 1.0f);
    }
    
    @Nullable
    @Override
    public ClientboundBlockEntityDataPacket getUpdatePacket() {
        return new ClientboundBlockEntityDataPacket(this.worldPosition, 8, this.getUpdateTag());
    }
    
    @Override
    public CompoundTag getUpdateTag() {
        return this.save(new CompoundTag());
    }
    
    public void triggerCooldown() {
        if (!this.level.isClientSide) {
            this.teleportCooldown = 40;
            this.level.blockEvent(this.getBlockPos(), this.getBlockState().getBlock(), 1, 0);
            this.setChanged();
        }
    }
    
    @Override
    public boolean triggerEvent(final int integer1, final int integer2) {
        if (integer1 == 1) {
            this.teleportCooldown = 40;
            return true;
        }
        return super.triggerEvent(integer1, integer2);
    }
    
    public void teleportEntity(final Entity aom) {
        if (!(this.level instanceof ServerLevel) || this.isCoolingDown()) {
            return;
        }
        this.teleportCooldown = 100;
        if (this.exitPortal == null && this.level.dimension() == Level.END) {
            this.findExitPortal((ServerLevel)this.level);
        }
        if (this.exitPortal != null) {
            final BlockPos fu3 = this.exactTeleport ? this.exitPortal : this.findExitPosition();
            Entity aom3;
            if (aom instanceof ThrownEnderpearl) {
                final Entity aom2 = ((ThrownEnderpearl)aom).getOwner();
                if (aom2 instanceof ServerPlayer) {
                    CriteriaTriggers.ENTER_BLOCK.trigger((ServerPlayer)aom2, this.level.getBlockState(this.getBlockPos()));
                }
                if (aom2 != null) {
                    aom3 = aom2;
                    aom.remove();
                }
                else {
                    aom3 = aom;
                }
            }
            else {
                aom3 = aom.getRootVehicle();
            }
            aom3.teleportToWithTicket(fu3.getX() + 0.5, fu3.getY(), fu3.getZ() + 0.5);
        }
        this.triggerCooldown();
    }
    
    private BlockPos findExitPosition() {
        final BlockPos fu2 = findTallestBlock(this.level, this.exitPortal, 5, false);
        TheEndGatewayBlockEntity.LOGGER.debug("Best exit position for portal at {} is {}", this.exitPortal, fu2);
        return fu2.above();
    }
    
    private void findExitPortal(final ServerLevel zd) {
        final Vec3 dem3 = new Vec3(this.getBlockPos().getX(), 0.0, this.getBlockPos().getZ()).normalize();
        Vec3 dem4 = dem3.scale(1024.0);
        for (int integer5 = 16; getChunk(zd, dem4).getHighestSectionPosition() > 0 && integer5-- > 0; dem4 = dem4.add(dem3.scale(-16.0))) {
            TheEndGatewayBlockEntity.LOGGER.debug("Skipping backwards past nonempty chunk at {}", dem4);
        }
        for (int integer5 = 16; getChunk(zd, dem4).getHighestSectionPosition() == 0 && integer5-- > 0; dem4 = dem4.add(dem3.scale(16.0))) {
            TheEndGatewayBlockEntity.LOGGER.debug("Skipping forward past empty chunk at {}", dem4);
        }
        TheEndGatewayBlockEntity.LOGGER.debug("Found chunk at {}", dem4);
        final LevelChunk chj6 = getChunk(zd, dem4);
        this.exitPortal = findValidSpawnInChunk(chj6);
        if (this.exitPortal == null) {
            this.exitPortal = new BlockPos(dem4.x + 0.5, 75.0, dem4.z + 0.5);
            TheEndGatewayBlockEntity.LOGGER.debug("Failed to find suitable block, settling on {}", this.exitPortal);
            Feature.END_ISLAND.configured(FeatureConfiguration.NONE).place(zd, zd.structureFeatureManager(), zd.getChunkSource().getGenerator(), new Random(this.exitPortal.asLong()), this.exitPortal);
        }
        else {
            TheEndGatewayBlockEntity.LOGGER.debug("Found block at {}", this.exitPortal);
        }
        this.exitPortal = findTallestBlock(zd, this.exitPortal, 16, true);
        TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", this.exitPortal);
        this.createExitPortal(zd, this.exitPortal = this.exitPortal.above(10));
        this.setChanged();
    }
    
    private static BlockPos findTallestBlock(final BlockGetter bpg, final BlockPos fu, final int integer, final boolean boolean4) {
        BlockPos fu2 = null;
        for (int integer2 = -integer; integer2 <= integer; ++integer2) {
            for (int integer3 = -integer; integer3 <= integer; ++integer3) {
                if (integer2 != 0 || integer3 != 0 || boolean4) {
                    for (int integer4 = 255; integer4 > ((fu2 == null) ? 0 : fu2.getY()); --integer4) {
                        final BlockPos fu3 = new BlockPos(fu.getX() + integer2, integer4, fu.getZ() + integer3);
                        final BlockState cfj10 = bpg.getBlockState(fu3);
                        if (cfj10.isCollisionShapeFullBlock(bpg, fu3) && (boolean4 || !cfj10.is(Blocks.BEDROCK))) {
                            fu2 = fu3;
                            break;
                        }
                    }
                }
            }
        }
        return (fu2 == null) ? fu : fu2;
    }
    
    private static LevelChunk getChunk(final Level bqb, final Vec3 dem) {
        return bqb.getChunk(Mth.floor(dem.x / 16.0), Mth.floor(dem.z / 16.0));
    }
    
    @Nullable
    private static BlockPos findValidSpawnInChunk(final LevelChunk chj) {
        final ChunkPos bph2 = chj.getPos();
        final BlockPos fu3 = new BlockPos(bph2.getMinBlockX(), 30, bph2.getMinBlockZ());
        final int integer4 = chj.getHighestSectionPosition() + 16 - 1;
        final BlockPos fu4 = new BlockPos(bph2.getMaxBlockX(), integer4, bph2.getMaxBlockZ());
        BlockPos fu5 = null;
        double double7 = 0.0;
        for (final BlockPos fu6 : BlockPos.betweenClosed(fu3, fu4)) {
            final BlockState cfj11 = chj.getBlockState(fu6);
            final BlockPos fu7 = fu6.above();
            final BlockPos fu8 = fu6.above(2);
            if (cfj11.is(Blocks.END_STONE) && !chj.getBlockState(fu7).isCollisionShapeFullBlock(chj, fu7) && !chj.getBlockState(fu8).isCollisionShapeFullBlock(chj, fu8)) {
                final double double8 = fu6.distSqr(0.0, 0.0, 0.0, true);
                if (fu5 != null && double8 >= double7) {
                    continue;
                }
                fu5 = fu6;
                double7 = double8;
            }
        }
        return fu5;
    }
    
    private void createExitPortal(final ServerLevel zd, final BlockPos fu) {
        Feature.END_GATEWAY.configured(EndGatewayConfiguration.knownExit(this.getBlockPos(), false)).place(zd, zd.structureFeatureManager(), zd.getChunkSource().getGenerator(), new Random(), fu);
    }
    
    @Override
    public boolean shouldRenderFace(final Direction fz) {
        return Block.shouldRenderFace(this.getBlockState(), this.level, this.getBlockPos(), fz);
    }
    
    public int getParticleAmount() {
        int integer2 = 0;
        for (final Direction fz6 : Direction.values()) {
            integer2 += (this.shouldRenderFace(fz6) ? 1 : 0);
        }
        return integer2;
    }
    
    public void setExitPosition(final BlockPos fu, final boolean boolean2) {
        this.exactTeleport = boolean2;
        this.exitPortal = fu;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
