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.state.BlockState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.ChunkGeneratorSettings;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.LevelAccessor;
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.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.dimension.end.TheEndDimension;
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.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 jt) {
        super.save(jt);
        jt.putLong("Age", this.age);
        if (this.exitPortal != null) {
            jt.put("ExitPortal", NbtUtils.writeBlockPos(this.exitPortal));
        }
        if (this.exactTeleport) {
            jt.putBoolean("ExactTeleport", this.exactTeleport);
        }
        return jt;
    }
    
    @Override
    public void load(final CompoundTag jt) {
        super.load(jt);
        this.age = jt.getLong("Age");
        if (jt.contains("ExitPortal", 10)) {
            this.exitPortal = NbtUtils.readBlockPos(jt.getCompound("ExitPortal"));
        }
        this.exactTeleport = jt.getBoolean("ExactTeleport");
    }
    
    @Override
    public double getViewDistance() {
        return 65536.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(0).getRootVehicle());
            }
            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 akq) {
        if (!(this.level instanceof ServerLevel) || this.isCoolingDown()) {
            return;
        }
        this.teleportCooldown = 100;
        if (this.exitPortal == null && this.level.dimension instanceof TheEndDimension) {
            this.findExitPortal((ServerLevel)this.level);
        }
        if (this.exitPortal != null) {
            final BlockPos fk3 = this.exactTeleport ? this.exitPortal : this.findExitPosition();
            akq.teleportToWithTicket(fk3.getX() + 0.5, fk3.getY() + 0.5, fk3.getZ() + 0.5);
        }
        this.triggerCooldown();
    }
    
    private BlockPos findExitPosition() {
        final BlockPos fk2 = findTallestBlock(this.level, this.exitPortal, 5, false);
        TheEndGatewayBlockEntity.LOGGER.debug("Best exit position for portal at {} is {}", this.exitPortal, fk2);
        return fk2.above();
    }
    
    private void findExitPortal(final ServerLevel xd) {
        final Vec3 cvl3 = new Vec3(this.getBlockPos().getX(), 0.0, this.getBlockPos().getZ()).normalize();
        Vec3 cvl4 = cvl3.scale(1024.0);
        for (int integer5 = 16; getChunk(xd, cvl4).getHighestSectionPosition() > 0 && integer5-- > 0; cvl4 = cvl4.add(cvl3.scale(-16.0))) {
            TheEndGatewayBlockEntity.LOGGER.debug("Skipping backwards past nonempty chunk at {}", cvl4);
        }
        for (int integer5 = 16; getChunk(xd, cvl4).getHighestSectionPosition() == 0 && integer5-- > 0; cvl4 = cvl4.add(cvl3.scale(16.0))) {
            TheEndGatewayBlockEntity.LOGGER.debug("Skipping forward past empty chunk at {}", cvl4);
        }
        TheEndGatewayBlockEntity.LOGGER.debug("Found chunk at {}", cvl4);
        final LevelChunk cal6 = getChunk(xd, cvl4);
        this.exitPortal = findValidSpawnInChunk(cal6);
        if (this.exitPortal == null) {
            this.exitPortal = new BlockPos(cvl4.x + 0.5, 75.0, cvl4.z + 0.5);
            TheEndGatewayBlockEntity.LOGGER.debug("Failed to find suitable block, settling on {}", this.exitPortal);
            Feature.END_ISLAND.configured(FeatureConfiguration.NONE).place(xd, xd.getChunkSource().getGenerator(), new Random(this.exitPortal.asLong()), this.exitPortal);
        }
        else {
            TheEndGatewayBlockEntity.LOGGER.debug("Found block at {}", this.exitPortal);
        }
        this.exitPortal = findTallestBlock(xd, this.exitPortal, 16, true);
        TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", this.exitPortal);
        this.createExitPortal(xd, this.exitPortal = this.exitPortal.above(10));
        this.setChanged();
    }
    
    private static BlockPos findTallestBlock(final BlockGetter bjg, final BlockPos fk, final int integer, final boolean boolean4) {
        BlockPos fk2 = 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 > ((fk2 == null) ? 0 : fk2.getY()); --integer4) {
                        final BlockPos fk3 = new BlockPos(fk.getX() + integer2, integer4, fk.getZ() + integer3);
                        final BlockState byj10 = bjg.getBlockState(fk3);
                        if (byj10.isCollisionShapeFullBlock(bjg, fk3) && (boolean4 || byj10.getBlock() != Blocks.BEDROCK)) {
                            fk2 = fk3;
                            break;
                        }
                    }
                }
            }
        }
        return (fk2 == null) ? fk : fk2;
    }
    
    private static LevelChunk getChunk(final Level bjw, final Vec3 cvl) {
        return bjw.getChunk(Mth.floor(cvl.x / 16.0), Mth.floor(cvl.z / 16.0));
    }
    
    @Nullable
    private static BlockPos findValidSpawnInChunk(final LevelChunk cal) {
        final ChunkPos bjh2 = cal.getPos();
        final BlockPos fk3 = new BlockPos(bjh2.getMinBlockX(), 30, bjh2.getMinBlockZ());
        final int integer4 = cal.getHighestSectionPosition() + 16 - 1;
        final BlockPos fk4 = new BlockPos(bjh2.getMaxBlockX(), integer4, bjh2.getMaxBlockZ());
        BlockPos fk5 = null;
        double double7 = 0.0;
        for (final BlockPos fk6 : BlockPos.betweenClosed(fk3, fk4)) {
            final BlockState byj11 = cal.getBlockState(fk6);
            final BlockPos fk7 = fk6.above();
            final BlockPos fk8 = fk6.above(2);
            if (byj11.getBlock() == Blocks.END_STONE && !cal.getBlockState(fk7).isCollisionShapeFullBlock(cal, fk7) && !cal.getBlockState(fk8).isCollisionShapeFullBlock(cal, fk8)) {
                final double double8 = fk6.distSqr(0.0, 0.0, 0.0, true);
                if (fk5 != null && double8 >= double7) {
                    continue;
                }
                fk5 = fk6;
                double7 = double8;
            }
        }
        return fk5;
    }
    
    private void createExitPortal(final ServerLevel xd, final BlockPos fk) {
        Feature.END_GATEWAY.configured(EndGatewayConfiguration.knownExit(this.getBlockPos(), false)).place(xd, xd.getChunkSource().getGenerator(), new Random(), fk);
    }
    
    @Override
    public boolean shouldRenderFace(final Direction fp) {
        return Block.shouldRenderFace(this.getBlockState(), this.level, this.getBlockPos(), fp);
    }
    
    public int getParticleAmount() {
        int integer2 = 0;
        for (final Direction fp6 : Direction.values()) {
            integer2 += (this.shouldRenderFace(fp6) ? 1 : 0);
        }
        return integer2;
    }
    
    public void setExitPosition(final BlockPos fk, final boolean boolean2) {
        this.exactTeleport = boolean2;
        this.exitPortal = fk;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
