package net.minecraft.world.entity.ai.navigation;

import net.minecraft.core.Position;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.Util;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.world.level.PathNavigationRegion;
import net.minecraft.world.entity.Entity;
import com.google.common.collect.ImmutableSet;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.stream.Stream;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.monster.SharedMonsterAttributes;
import net.minecraft.world.level.pathfinder.PathFinder;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.pathfinder.NodeEvaluator;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import javax.annotation.Nullable;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.Mob;

public abstract class PathNavigation {
    protected final Mob mob;
    protected final Level level;
    @Nullable
    protected Path path;
    protected double speedModifier;
    private final AttributeInstance followRange;
    protected int tick;
    protected int lastStuckCheck;
    protected Vec3 lastStuckCheckPos;
    protected Vec3 timeoutCachedNode;
    protected long timeoutTimer;
    protected long lastTimeoutCheck;
    protected double timeoutLimit;
    protected float maxDistanceToWaypoint;
    protected boolean hasDelayedRecomputation;
    protected long timeLastRecompute;
    protected NodeEvaluator nodeEvaluator;
    private BlockPos targetPos;
    private int reachRange;
    private float maxVisitedNodesMultiplier;
    private final PathFinder pathFinder;
    
    public PathNavigation(final Mob ala, final Level bjw) {
        this.lastStuckCheckPos = Vec3.ZERO;
        this.timeoutCachedNode = Vec3.ZERO;
        this.maxDistanceToWaypoint = 0.5f;
        this.maxVisitedNodesMultiplier = 1.0f;
        this.mob = ala;
        this.level = bjw;
        this.followRange = ala.getAttribute(SharedMonsterAttributes.FOLLOW_RANGE);
        final int integer4 = Mth.floor(this.followRange.getValue() * 16.0);
        this.pathFinder = this.createPathFinder(integer4);
    }
    
    public void resetMaxVisitedNodesMultiplier() {
        this.maxVisitedNodesMultiplier = 1.0f;
    }
    
    public void setMaxVisitedNodesMultiplier(final float float1) {
        this.maxVisitedNodesMultiplier = float1;
    }
    
    public BlockPos getTargetPos() {
        return this.targetPos;
    }
    
    protected abstract PathFinder createPathFinder(final int integer);
    
    public void setSpeedModifier(final double double1) {
        this.speedModifier = double1;
    }
    
    public boolean hasDelayedRecomputation() {
        return this.hasDelayedRecomputation;
    }
    
    public void recomputePath() {
        if (this.level.getGameTime() - this.timeLastRecompute > 20L) {
            if (this.targetPos != null) {
                this.path = null;
                this.path = this.createPath(this.targetPos, this.reachRange);
                this.timeLastRecompute = this.level.getGameTime();
                this.hasDelayedRecomputation = false;
            }
        }
        else {
            this.hasDelayedRecomputation = true;
        }
    }
    
    @Nullable
    public final Path createPath(final double double1, final double double2, final double double3, final int integer) {
        return this.createPath(new BlockPos(double1, double2, double3), integer);
    }
    
    @Nullable
    public Path createPath(final Stream<BlockPos> stream, final int integer) {
        return this.createPath(stream.collect(Collectors.<BlockPos>toSet()), 8, false, integer);
    }
    
    @Nullable
    public Path createPath(final BlockPos fk, final int integer) {
        return this.createPath(ImmutableSet.<BlockPos>of(fk), 8, false, integer);
    }
    
    @Nullable
    public Path createPath(final Entity akq, final int integer) {
        return this.createPath(ImmutableSet.<BlockPos>of(new BlockPos(akq)), 16, true, integer);
    }
    
    @Nullable
    protected Path createPath(final Set<BlockPos> set, final int integer2, final boolean boolean3, final int integer4) {
        if (set.isEmpty()) {
            return null;
        }
        if (this.mob.getY() < 0.0) {
            return null;
        }
        if (!this.canUpdatePath()) {
            return null;
        }
        if (this.path != null && !this.path.isDone() && set.contains(this.targetPos)) {
            return this.path;
        }
        this.level.getProfiler().push("pathfind");
        final float float6 = (float)this.followRange.getValue();
        final BlockPos fk7 = boolean3 ? new BlockPos(this.mob).above() : new BlockPos(this.mob);
        final int integer5 = (int)(float6 + integer2);
        final PathNavigationRegion bkh9 = new PathNavigationRegion(this.level, fk7.offset(-integer5, -integer5, -integer5), fk7.offset(integer5, integer5, integer5));
        final Path cqq10 = this.pathFinder.findPath(bkh9, this.mob, set, float6, integer4, this.maxVisitedNodesMultiplier);
        this.level.getProfiler().pop();
        if (cqq10 != null && cqq10.getTarget() != null) {
            this.targetPos = cqq10.getTarget();
            this.reachRange = integer4;
        }
        return cqq10;
    }
    
    public boolean moveTo(final double double1, final double double2, final double double3, final double double4) {
        return this.moveTo(this.createPath(double1, double2, double3, 1), double4);
    }
    
    public boolean moveTo(final Entity akq, final double double2) {
        final Path cqq5 = this.createPath(akq, 1);
        return cqq5 != null && this.moveTo(cqq5, double2);
    }
    
    public boolean moveTo(@Nullable final Path cqq, final double double2) {
        if (cqq == null) {
            this.path = null;
            return false;
        }
        if (!cqq.sameAs(this.path)) {
            this.path = cqq;
        }
        if (this.isDone()) {
            return false;
        }
        this.trimPath();
        if (this.path.getSize() <= 0) {
            return false;
        }
        this.speedModifier = double2;
        final Vec3 cvl5 = this.getTempMobPos();
        this.lastStuckCheck = this.tick;
        this.lastStuckCheckPos = cvl5;
        return true;
    }
    
    @Nullable
    public Path getPath() {
        return this.path;
    }
    
    public void tick() {
        ++this.tick;
        if (this.hasDelayedRecomputation) {
            this.recomputePath();
        }
        if (this.isDone()) {
            return;
        }
        if (this.canUpdatePath()) {
            this.updatePath();
        }
        else if (this.path != null && this.path.getIndex() < this.path.getSize()) {
            final Vec3 cvl2 = this.getTempMobPos();
            final Vec3 cvl3 = this.path.getPos(this.mob, this.path.getIndex());
            if (cvl2.y > cvl3.y && !this.mob.onGround && Mth.floor(cvl2.x) == Mth.floor(cvl3.x) && Mth.floor(cvl2.z) == Mth.floor(cvl3.z)) {
                this.path.setIndex(this.path.getIndex() + 1);
            }
        }
        DebugPackets.sendPathFindingPacket(this.level, this.mob, this.path, this.maxDistanceToWaypoint);
        if (this.isDone()) {
            return;
        }
        final Vec3 cvl2 = this.path.currentPos(this.mob);
        final BlockPos fk3 = new BlockPos(cvl2);
        this.mob.getMoveControl().setWantedPosition(cvl2.x, this.level.getBlockState(fk3.below()).isAir() ? cvl2.y : WalkNodeEvaluator.getFloorLevel(this.level, fk3), cvl2.z, this.speedModifier);
    }
    
    protected void updatePath() {
        final Vec3 cvl2 = this.getTempMobPos();
        this.maxDistanceToWaypoint = ((this.mob.getBbWidth() > 0.75f) ? (this.mob.getBbWidth() / 2.0f) : (0.75f - this.mob.getBbWidth() / 2.0f));
        final Vec3 cvl3 = this.path.currentPos();
        if (Math.abs(this.mob.getX() - (cvl3.x + 0.5)) < this.maxDistanceToWaypoint && Math.abs(this.mob.getZ() - (cvl3.z + 0.5)) < this.maxDistanceToWaypoint && Math.abs(this.mob.getY() - cvl3.y) < 1.0) {
            this.path.setIndex(this.path.getIndex() + 1);
        }
        this.doStuckDetection(cvl2);
    }
    
    protected void doStuckDetection(final Vec3 cvl) {
        if (this.tick - this.lastStuckCheck > 100) {
            if (cvl.distanceToSqr(this.lastStuckCheckPos) < 2.25) {
                this.stop();
            }
            this.lastStuckCheck = this.tick;
            this.lastStuckCheckPos = cvl;
        }
        if (this.path != null && !this.path.isDone()) {
            final Vec3 cvl2 = this.path.currentPos();
            if (cvl2.equals(this.timeoutCachedNode)) {
                this.timeoutTimer += Util.getMillis() - this.lastTimeoutCheck;
            }
            else {
                this.timeoutCachedNode = cvl2;
                final double double4 = cvl.distanceTo(this.timeoutCachedNode);
                this.timeoutLimit = ((this.mob.getSpeed() > 0.0f) ? (double4 / this.mob.getSpeed() * 1000.0) : 0.0);
            }
            if (this.timeoutLimit > 0.0 && this.timeoutTimer > this.timeoutLimit * 3.0) {
                this.timeoutCachedNode = Vec3.ZERO;
                this.timeoutTimer = 0L;
                this.timeoutLimit = 0.0;
                this.stop();
            }
            this.lastTimeoutCheck = Util.getMillis();
        }
    }
    
    public boolean isDone() {
        return this.path == null || this.path.isDone();
    }
    
    public boolean isInProgress() {
        return !this.isDone();
    }
    
    public void stop() {
        this.path = null;
    }
    
    protected abstract Vec3 getTempMobPos();
    
    protected abstract boolean canUpdatePath();
    
    protected boolean isInLiquid() {
        return this.mob.isInWaterOrBubble() || this.mob.isInLava();
    }
    
    protected void trimPath() {
        if (this.path == null) {
            return;
        }
        for (int integer2 = 0; integer2 < this.path.getSize(); ++integer2) {
            final Node cqo3 = this.path.get(integer2);
            final Node cqo4 = (integer2 + 1 < this.path.getSize()) ? this.path.get(integer2 + 1) : null;
            final BlockState byj5 = this.level.getBlockState(new BlockPos(cqo3.x, cqo3.y, cqo3.z));
            final Block bph6 = byj5.getBlock();
            if (bph6 == Blocks.CAULDRON) {
                this.path.set(integer2, cqo3.cloneMove(cqo3.x, cqo3.y + 1, cqo3.z));
                if (cqo4 != null && cqo3.y >= cqo4.y) {
                    this.path.set(integer2 + 1, cqo4.cloneMove(cqo4.x, cqo3.y + 1, cqo4.z));
                }
            }
        }
    }
    
    protected abstract boolean canMoveDirectly(final Vec3 cvl1, final Vec3 cvl2, final int integer3, final int integer4, final int integer5);
    
    public boolean isStableDestination(final BlockPos fk) {
        final BlockPos fk2 = fk.below();
        return this.level.getBlockState(fk2).isSolidRender(this.level, fk2);
    }
    
    public NodeEvaluator getNodeEvaluator() {
        return this.nodeEvaluator;
    }
    
    public void setCanFloat(final boolean boolean1) {
        this.nodeEvaluator.setCanFloat(boolean1);
    }
    
    public boolean canFloat() {
        return this.nodeEvaluator.canFloat();
    }
    
    public void recomputePath(final BlockPos fk) {
        if (this.path == null || this.path.isDone() || this.path.getSize() == 0) {
            return;
        }
        final Node cqo3 = this.path.last();
        final Vec3 cvl4 = new Vec3((cqo3.x + this.mob.getX()) / 2.0, (cqo3.y + this.mob.getY()) / 2.0, (cqo3.z + this.mob.getZ()) / 2.0);
        if (fk.closerThan(cvl4, this.path.getSize() - this.path.getIndex())) {
            this.recomputePath();
        }
    }
}
