package net.minecraft.world.level.border;

import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.util.Mth;
import net.minecraft.Util;
import net.minecraft.world.level.storage.LevelData;
import java.util.Iterator;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.BlockPos;
import com.google.common.collect.Lists;
import java.util.List;

public class WorldBorder {
    private final List<BorderChangeListener> listeners;
    private double damagePerBlock;
    private double damageSafeZone;
    private int warningTime;
    private int warningBlocks;
    private double centerX;
    private double centerZ;
    private int absoluteMaxSize;
    private BorderExtent extent;
    
    public WorldBorder() {
        this.listeners = Lists.newArrayList();
        this.damagePerBlock = 0.2;
        this.damageSafeZone = 5.0;
        this.warningTime = 15;
        this.warningBlocks = 5;
        this.absoluteMaxSize = 29999984;
        this.extent = new StaticBorderExtent(6.0E7);
    }
    
    public boolean isWithinBounds(final BlockPos fk) {
        return fk.getX() + 1 > this.getMinX() && fk.getX() < this.getMaxX() && fk.getZ() + 1 > this.getMinZ() && fk.getZ() < this.getMaxZ();
    }
    
    public boolean isWithinBounds(final ChunkPos bjh) {
        return bjh.getMaxBlockX() > this.getMinX() && bjh.getMinBlockX() < this.getMaxX() && bjh.getMaxBlockZ() > this.getMinZ() && bjh.getMinBlockZ() < this.getMaxZ();
    }
    
    public boolean isWithinBounds(final AABB cvf) {
        return cvf.maxX > this.getMinX() && cvf.minX < this.getMaxX() && cvf.maxZ > this.getMinZ() && cvf.minZ < this.getMaxZ();
    }
    
    public double getDistanceToBorder(final Entity akq) {
        return this.getDistanceToBorder(akq.getX(), akq.getZ());
    }
    
    public VoxelShape getCollisionShape() {
        return this.extent.getCollisionShape();
    }
    
    public double getDistanceToBorder(final double double1, final double double2) {
        final double double3 = double2 - this.getMinZ();
        final double double4 = this.getMaxZ() - double2;
        final double double5 = double1 - this.getMinX();
        final double double6 = this.getMaxX() - double1;
        double double7 = Math.min(double5, double6);
        double7 = Math.min(double7, double3);
        return Math.min(double7, double4);
    }
    
    public BorderStatus getStatus() {
        return this.extent.getStatus();
    }
    
    public double getMinX() {
        return this.extent.getMinX();
    }
    
    public double getMinZ() {
        return this.extent.getMinZ();
    }
    
    public double getMaxX() {
        return this.extent.getMaxX();
    }
    
    public double getMaxZ() {
        return this.extent.getMaxZ();
    }
    
    public double getCenterX() {
        return this.centerX;
    }
    
    public double getCenterZ() {
        return this.centerZ;
    }
    
    public void setCenter(final double double1, final double double2) {
        this.centerX = double1;
        this.centerZ = double2;
        this.extent.onCenterChange();
        for (final BorderChangeListener bzu7 : this.getListeners()) {
            bzu7.onBorderCenterSet(this, double1, double2);
        }
    }
    
    public double getSize() {
        return this.extent.getSize();
    }
    
    public long getLerpRemainingTime() {
        return this.extent.getLerpRemainingTime();
    }
    
    public double getLerpTarget() {
        return this.extent.getLerpTarget();
    }
    
    public void setSize(final double double1) {
        this.extent = new StaticBorderExtent(double1);
        for (final BorderChangeListener bzu5 : this.getListeners()) {
            bzu5.onBorderSizeSet(this, double1);
        }
    }
    
    public void lerpSizeBetween(final double double1, final double double2, final long long3) {
        this.extent = ((double1 == double2) ? new StaticBorderExtent(double2) : new MovingBorderExtent(double1, double2, long3));
        for (final BorderChangeListener bzu9 : this.getListeners()) {
            bzu9.onBorderSizeLerping(this, double1, double2, long3);
        }
    }
    
    protected List<BorderChangeListener> getListeners() {
        return Lists.newArrayList(this.listeners);
    }
    
    public void addListener(final BorderChangeListener bzu) {
        this.listeners.add(bzu);
    }
    
    public void setAbsoluteMaxSize(final int integer) {
        this.absoluteMaxSize = integer;
        this.extent.onAbsoluteMaxSizeChange();
    }
    
    public int getAbsoluteMaxSize() {
        return this.absoluteMaxSize;
    }
    
    public double getDamageSafeZone() {
        return this.damageSafeZone;
    }
    
    public void setDamageSafeZone(final double double1) {
        this.damageSafeZone = double1;
        for (final BorderChangeListener bzu5 : this.getListeners()) {
            bzu5.onBorderSetDamageSafeZOne(this, double1);
        }
    }
    
    public double getDamagePerBlock() {
        return this.damagePerBlock;
    }
    
    public void setDamagePerBlock(final double double1) {
        this.damagePerBlock = double1;
        for (final BorderChangeListener bzu5 : this.getListeners()) {
            bzu5.onBorderSetDamagePerBlock(this, double1);
        }
    }
    
    public double getLerpSpeed() {
        return this.extent.getLerpSpeed();
    }
    
    public int getWarningTime() {
        return this.warningTime;
    }
    
    public void setWarningTime(final int integer) {
        this.warningTime = integer;
        for (final BorderChangeListener bzu4 : this.getListeners()) {
            bzu4.onBorderSetWarningTime(this, integer);
        }
    }
    
    public int getWarningBlocks() {
        return this.warningBlocks;
    }
    
    public void setWarningBlocks(final int integer) {
        this.warningBlocks = integer;
        for (final BorderChangeListener bzu4 : this.getListeners()) {
            bzu4.onBorderSetWarningBlocks(this, integer);
        }
    }
    
    public void tick() {
        this.extent = this.extent.update();
    }
    
    public void saveWorldBorderData(final LevelData crm) {
        crm.setBorderSize(this.getSize());
        crm.setBorderX(this.getCenterX());
        crm.setBorderZ(this.getCenterZ());
        crm.setBorderSafeZone(this.getDamageSafeZone());
        crm.setBorderDamagePerBlock(this.getDamagePerBlock());
        crm.setBorderWarningBlocks(this.getWarningBlocks());
        crm.setBorderWarningTime(this.getWarningTime());
        crm.setBorderSizeLerpTarget(this.getLerpTarget());
        crm.setBorderSizeLerpTime(this.getLerpRemainingTime());
    }
    
    public void readBorderData(final LevelData crm) {
        this.setCenter(crm.getBorderX(), crm.getBorderZ());
        this.setDamagePerBlock(crm.getBorderDamagePerBlock());
        this.setDamageSafeZone(crm.getBorderSafeZone());
        this.setWarningBlocks(crm.getBorderWarningBlocks());
        this.setWarningTime(crm.getBorderWarningTime());
        if (crm.getBorderSizeLerpTime() > 0L) {
            this.lerpSizeBetween(crm.getBorderSize(), crm.getBorderSizeLerpTarget(), crm.getBorderSizeLerpTime());
        }
        else {
            this.setSize(crm.getBorderSize());
        }
    }
    
    class MovingBorderExtent implements BorderExtent {
        private final double from;
        private final double to;
        private final long lerpEnd;
        private final long lerpBegin;
        private final double lerpDuration;
        
        private MovingBorderExtent(final double double2, final double double3, final long long4) {
            this.from = double2;
            this.to = double3;
            this.lerpDuration = (double)long4;
            this.lerpBegin = Util.getMillis();
            this.lerpEnd = this.lerpBegin + long4;
        }
        
        @Override
        public double getMinX() {
            return Math.max(WorldBorder.this.getCenterX() - this.getSize() / 2.0, -WorldBorder.this.absoluteMaxSize);
        }
        
        @Override
        public double getMinZ() {
            return Math.max(WorldBorder.this.getCenterZ() - this.getSize() / 2.0, -WorldBorder.this.absoluteMaxSize);
        }
        
        @Override
        public double getMaxX() {
            return Math.min(WorldBorder.this.getCenterX() + this.getSize() / 2.0, WorldBorder.this.absoluteMaxSize);
        }
        
        @Override
        public double getMaxZ() {
            return Math.min(WorldBorder.this.getCenterZ() + this.getSize() / 2.0, WorldBorder.this.absoluteMaxSize);
        }
        
        @Override
        public double getSize() {
            final double double2 = (Util.getMillis() - this.lerpBegin) / this.lerpDuration;
            return (double2 < 1.0) ? Mth.lerp(double2, this.from, this.to) : this.to;
        }
        
        @Override
        public double getLerpSpeed() {
            return Math.abs(this.from - this.to) / (this.lerpEnd - this.lerpBegin);
        }
        
        @Override
        public long getLerpRemainingTime() {
            return this.lerpEnd - Util.getMillis();
        }
        
        @Override
        public double getLerpTarget() {
            return this.to;
        }
        
        @Override
        public BorderStatus getStatus() {
            return (this.to < this.from) ? BorderStatus.SHRINKING : BorderStatus.GROWING;
        }
        
        @Override
        public void onCenterChange() {
        }
        
        @Override
        public void onAbsoluteMaxSizeChange() {
        }
        
        @Override
        public BorderExtent update() {
            if (this.getLerpRemainingTime() <= 0L) {
                return new StaticBorderExtent(this.to);
            }
            return this;
        }
        
        @Override
        public VoxelShape getCollisionShape() {
            return Shapes.join(Shapes.INFINITY, Shapes.box(Math.floor(this.getMinX()), Double.NEGATIVE_INFINITY, Math.floor(this.getMinZ()), Math.ceil(this.getMaxX()), Double.POSITIVE_INFINITY, Math.ceil(this.getMaxZ())), BooleanOp.ONLY_FIRST);
        }
    }
    
    class StaticBorderExtent implements BorderExtent {
        private final double size;
        private double minX;
        private double minZ;
        private double maxX;
        private double maxZ;
        private VoxelShape shape;
        
        public StaticBorderExtent(final double double2) {
            this.size = double2;
            this.updateBox();
        }
        
        @Override
        public double getMinX() {
            return this.minX;
        }
        
        @Override
        public double getMaxX() {
            return this.maxX;
        }
        
        @Override
        public double getMinZ() {
            return this.minZ;
        }
        
        @Override
        public double getMaxZ() {
            return this.maxZ;
        }
        
        @Override
        public double getSize() {
            return this.size;
        }
        
        @Override
        public BorderStatus getStatus() {
            return BorderStatus.STATIONARY;
        }
        
        @Override
        public double getLerpSpeed() {
            return 0.0;
        }
        
        @Override
        public long getLerpRemainingTime() {
            return 0L;
        }
        
        @Override
        public double getLerpTarget() {
            return this.size;
        }
        
        private void updateBox() {
            this.minX = Math.max(WorldBorder.this.getCenterX() - this.size / 2.0, -WorldBorder.this.absoluteMaxSize);
            this.minZ = Math.max(WorldBorder.this.getCenterZ() - this.size / 2.0, -WorldBorder.this.absoluteMaxSize);
            this.maxX = Math.min(WorldBorder.this.getCenterX() + this.size / 2.0, WorldBorder.this.absoluteMaxSize);
            this.maxZ = Math.min(WorldBorder.this.getCenterZ() + this.size / 2.0, WorldBorder.this.absoluteMaxSize);
            this.shape = Shapes.join(Shapes.INFINITY, Shapes.box(Math.floor(this.getMinX()), Double.NEGATIVE_INFINITY, Math.floor(this.getMinZ()), Math.ceil(this.getMaxX()), Double.POSITIVE_INFINITY, Math.ceil(this.getMaxZ())), BooleanOp.ONLY_FIRST);
        }
        
        @Override
        public void onAbsoluteMaxSizeChange() {
            this.updateBox();
        }
        
        @Override
        public void onCenterChange() {
            this.updateBox();
        }
        
        @Override
        public BorderExtent update() {
            return this;
        }
        
        @Override
        public VoxelShape getCollisionShape() {
            return this.shape;
        }
    }
    
    interface BorderExtent {
        double getMinX();
        
        double getMaxX();
        
        double getMinZ();
        
        double getMaxZ();
        
        double getSize();
        
        double getLerpSpeed();
        
        long getLerpRemainingTime();
        
        double getLerpTarget();
        
        BorderStatus getStatus();
        
        void onAbsoluteMaxSizeChange();
        
        void onCenterChange();
        
        BorderExtent update();
        
        VoxelShape getCollisionShape();
    }
}
