package net.citizensnpcs.nms.v1_13_R2.util;

import java.lang.reflect.Method;

import net.citizensnpcs.api.util.BoundingBox;
import net.citizensnpcs.nms.v1_13_R2.entity.EntityHumanNPC;
import net.citizensnpcs.util.NMS;
import net.minecraft.server.v1_13_R2.AttributeInstance;
import net.minecraft.server.v1_13_R2.Block;
import net.minecraft.server.v1_13_R2.BlockPosition;
import net.minecraft.server.v1_13_R2.Blocks;
import net.minecraft.server.v1_13_R2.ChunkCache;
import net.minecraft.server.v1_13_R2.Entity;
import net.minecraft.server.v1_13_R2.EntityInsentient;
import net.minecraft.server.v1_13_R2.EntityTypes;
import net.minecraft.server.v1_13_R2.GenericAttributes;
import net.minecraft.server.v1_13_R2.IBlockData;
import net.minecraft.server.v1_13_R2.MathHelper;
import net.minecraft.server.v1_13_R2.MethodProfiler;
import net.minecraft.server.v1_13_R2.NavigationAbstract;
import net.minecraft.server.v1_13_R2.PathEntity;
import net.minecraft.server.v1_13_R2.PathMode;
import net.minecraft.server.v1_13_R2.PathPoint;
import net.minecraft.server.v1_13_R2.PathType;
import net.minecraft.server.v1_13_R2.Pathfinder;
import net.minecraft.server.v1_13_R2.PathfinderAbstract;
import net.minecraft.server.v1_13_R2.PathfinderNormal;
import net.minecraft.server.v1_13_R2.SystemUtils;
import net.minecraft.server.v1_13_R2.Vec3D;
import net.minecraft.server.v1_13_R2.World;

public class PlayerNavigation extends NavigationAbstract {
    protected EntityHumanNPC a;
    protected World b;
    protected PathEntity c;
    protected double d;
    protected int e;
    protected int f;
    protected Vec3D g = Vec3D.a;
    protected Vec3D h = Vec3D.a;
    protected long i;
    protected long j;
    protected double k;
    protected float l = 0.5F;
    protected boolean m;
    protected long n;
    protected PlayerPathfinderNormal o;
    private final AttributeInstance p;
    private boolean pp;
    private BlockPosition q;
    private final PlayerPathfinder r;

    public PlayerNavigation(EntityHumanNPC entityinsentient, World world) {
        super(getDummyInsentient(entityinsentient, world), world);
        this.a = entityinsentient;
        this.b = world;
        this.p = entityinsentient.getAttributeInstance(GenericAttributes.FOLLOW_RANGE);
        this.setRange(24);
        this.o = new PlayerPathfinderNormal();
        this.o.a(true);
        this.r = new PlayerPathfinder(this.o);
        // this.b.C().a(this);
    }

    @Override
    protected Pathfinder a() {
        return null;
    }

    @Override
    public boolean a(BlockPosition paramBlockPosition) {
        BlockPosition localBlockPosition = paramBlockPosition.down();
        return this.b.getType(localBlockPosition).f(this.b, localBlockPosition);
    }

    public void a(boolean paramBoolean) {
        this.o.b(paramBoolean);
    }

    @Override
    public void a(double paramDouble) {
        this.d = paramDouble;
    }

    @Override
    public boolean a(double paramDouble1, double paramDouble2, double paramDouble3, double paramDouble4) {
        return a(a(paramDouble1, paramDouble2, paramDouble3), paramDouble4);
    }

    @Override
    public PathEntity a(Entity paramEntity) {
        return b(new BlockPosition(paramEntity));
    }

    @Override
    public boolean a(Entity paramEntity, double paramDouble) {
        PathEntity localPathEntity = a(paramEntity);
        return localPathEntity != null && a(localPathEntity, paramDouble);
    }

    private boolean a(int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5, int paramInt6,
            Vec3D paramVec3D, double paramDouble1, double paramDouble2) {
        int i = paramInt1 - paramInt4 / 2;
        int j = paramInt3 - paramInt6 / 2;
        if (!b(i, paramInt2, j, paramInt4, paramInt5, paramInt6, paramVec3D, paramDouble1, paramDouble2))
            return false;
        for (int k = i; k < i + paramInt4; k++) {
            for (int m = j; m < j + paramInt6; m++) {
                double d1 = k + 0.5D - paramVec3D.x;
                double d2 = m + 0.5D - paramVec3D.z;
                if (d1 * paramDouble1 + d2 * paramDouble2 >= 0.0D) {
                    PathType localPathType = this.o.a(this.b, k, paramInt2 - 1, m, this.a, paramInt4, paramInt5,
                            paramInt6, true, true);
                    if (localPathType == PathType.WATER || localPathType == PathType.LAVA
                            || localPathType == PathType.OPEN)
                        return false;
                    localPathType = this.o.a(this.b, k, paramInt2, m, this.a, paramInt4, paramInt5, paramInt6, true,
                            true);
                    float f = this.a.a(localPathType);
                    if (f < 0.0F || f >= 8.0F)
                        return false;
                    if (localPathType == PathType.DAMAGE_FIRE || localPathType == PathType.DANGER_FIRE
                            || localPathType == PathType.DAMAGE_OTHER)
                        return false;
                }
            }
        }
        return true;
    }

    @Override
    public boolean a(PathEntity paramPathEntity, double paramDouble) {
        if (paramPathEntity == null) {
            this.c = null;
            return false;
        }
        if (!paramPathEntity.a(this.c)) {
            this.c = paramPathEntity;
        }
        E_();
        if (this.c.d() <= 0)
            return false;
        this.d = paramDouble;
        Vec3D localVec3D = c();
        this.f = this.e;
        this.g = localVec3D;
        return true;
    }

    @Override
    protected void a(Vec3D paramVec3D) {
        if (this.e - this.f > 100) {
            if (paramVec3D.distanceSquared(this.g) < 2.25D) {
                q();
            }
            this.f = this.e;
            this.g = paramVec3D;
        }
        if (this.c != null && !this.c.b()) {
            Vec3D localVec3D = this.c.f();
            if (localVec3D.equals(this.h)) {
                this.i += getMonotonicMillis() - this.j;
            } else {
                this.h = localVec3D;
                double d1 = paramVec3D.f(this.h);
                this.k = this.a.cK() > 0.0F ? d1 / this.a.cK() * 1000.0D : 0.0D;
            }
            if (this.k > 0.0D && this.i > this.k * 3.0D) {
                this.h = Vec3D.a;
                this.i = 0L;
                this.k = 0.0D;
                q();
            }
            this.j = getMonotonicMillis();
        }
    }

    @Override
    protected boolean a(Vec3D paramVec3D1, Vec3D paramVec3D2, int paramInt1, int paramInt2, int paramInt3) {
        int i = MathHelper.floor(paramVec3D1.x);
        int j = MathHelper.floor(paramVec3D1.z);
        double d1 = paramVec3D2.x - paramVec3D1.x;
        double d2 = paramVec3D2.z - paramVec3D1.z;
        double d3 = d1 * d1 + d2 * d2;
        if (d3 < 1.0E-8D)
            return false;
        double d4 = 1.0D / Math.sqrt(d3);
        d1 *= d4;
        d2 *= d4;
        paramInt1 += 2;
        paramInt3 += 2;
        if (!a(i, (int) paramVec3D1.y, j, paramInt1, paramInt2, paramInt3, paramVec3D1, d1, d2))
            return false;
        paramInt1 -= 2;
        paramInt3 -= 2;
        double d5 = 1.0D / Math.abs(d1);
        double d6 = 1.0D / Math.abs(d2);
        double d7 = i - paramVec3D1.x;
        double d8 = j - paramVec3D1.z;
        if (d1 >= 0.0D) {
            d7 += 1.0D;
        }
        if (d2 >= 0.0D) {
            d8 += 1.0D;
        }
        d7 /= d1;
        d8 /= d2;
        int k = d1 < 0.0D ? -1 : 1;
        int m = d2 < 0.0D ? -1 : 1;
        int n = MathHelper.floor(paramVec3D2.x);
        int i1 = MathHelper.floor(paramVec3D2.z);
        int i2 = n - i;
        int i3 = i1 - j;
        while (i2 * k > 0 || i3 * m > 0) {
            if (d7 < d8) {
                d7 += d5;
                i += k;
                i2 = n - i;
            } else {
                d8 += d6;
                j += m;
                i3 = i1 - j;
            }
            if (!a(i, (int) paramVec3D1.y, j, paramInt1, paramInt2, paramInt3, paramVec3D1, d1, d2))
                return false;
        }
        return true;
    }

    @Override
    protected boolean b() {
        return this.a.onGround || r() || this.a.isPassenger();
    }

    @Override
    public PathEntity b(BlockPosition paramBlockPosition) {
        BlockPosition localBlockPosition;
        if (this.b.getType(paramBlockPosition).isAir()) {
            localBlockPosition = paramBlockPosition.down();
            while (localBlockPosition.getY() > 0 && this.b.getType(localBlockPosition).isAir()) {
                localBlockPosition = localBlockPosition.down();
            }
            if (localBlockPosition.getY() > 0)
                return superb(localBlockPosition.up());
            while (localBlockPosition.getY() < this.b.getHeight() && this.b.getType(localBlockPosition).isAir()) {
                localBlockPosition = localBlockPosition.up();
            }
            paramBlockPosition = localBlockPosition;
        }
        if (this.b.getType(paramBlockPosition).getMaterial().isBuildable()) {
            localBlockPosition = paramBlockPosition.up();
            while (localBlockPosition.getY() < this.b.getHeight()
                    && this.b.getType(localBlockPosition).getMaterial().isBuildable()) {
                localBlockPosition = localBlockPosition.up();
            }
            return superb(localBlockPosition);
        }
        return superb(paramBlockPosition);
    }

    public void b(boolean paramBoolean) {
        this.o.a(paramBoolean);
    }

    private boolean b(int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5, int paramInt6,
            Vec3D paramVec3D, double paramDouble1, double paramDouble2) {
        for (BlockPosition localBlockPosition : BlockPosition.a(new BlockPosition(paramInt1, paramInt2, paramInt3),
                new BlockPosition(paramInt1 + paramInt4 - 1, paramInt2 + paramInt5 - 1, paramInt3 + paramInt6 - 1))) {
            double d1 = localBlockPosition.getX() + 0.5D - paramVec3D.x;
            double d2 = localBlockPosition.getZ() + 0.5D - paramVec3D.z;
            if (d1 * paramDouble1 + d2 * paramDouble2 >= 0.0D) {
                if (!this.b.getType(localBlockPosition).a(this.b, localBlockPosition, PathMode.LAND))
                    return false;
            }
        }
        return true;
    }

    @Override
    protected Vec3D c() {
        return new Vec3D(this.a.locX, u(), this.a.locZ);
    }

    public void c(boolean paramBoolean) {
        this.pp = paramBoolean;
    }

    @Override
    public void d() {
        this.e += 1;
        if (this.m) {
            l();
        }
        if (p())
            return;
        if (b()) {
            o();
        } else if (this.c != null && this.c.e() < this.c.d()) {
            Vec3D localVec3D = c();
            Vec3D localObject = this.c.a(this.a, this.c.e());
            if (localVec3D.y > localObject.y && !this.a.onGround
                    && MathHelper.floor(localVec3D.x) == MathHelper.floor(localObject.x)
                    && MathHelper.floor(localVec3D.z) == MathHelper.floor(localObject.z)) {
                this.c.c(this.c.e() + 1);
            }
        }
        n();
        if (p())
            return;
        Vec3D localVec3D = this.c.a(this.a);
        Object localObject = new BlockPosition(localVec3D);
        this.a.getControllerMove().a(localVec3D.x,
                this.b.getType(((BlockPosition) localObject).down()).isAir() ? localVec3D.y
                        : PathfinderNormal.a(this.b, (BlockPosition) localObject),
                localVec3D.z, this.d);
    }

    @Override
    public void d(boolean paramBoolean) {
        this.o.c(paramBoolean);
    }

    @Override
    protected void E_() {
        superE_();
        if (this.pp) {
            BoundingBox bb = NMSBoundingBox.wrap(this.a.getBoundingBox());
            if (this.b.e(new BlockPosition(MathHelper.floor(this.a.locX), (int) (bb.minY + 0.5D),
                    MathHelper.floor(this.a.locZ))))
                return;
            for (int i = 0; i < this.c.d(); i++) {
                PathPoint localPathPoint = this.c.a(i);
                if (this.b.e(new BlockPosition(localPathPoint.a, localPathPoint.b, localPathPoint.c))) {
                    this.c.b(i - 1);
                    return;
                }
            }
        }
    }

    public boolean g() {
        return this.o.c();
    }

    @Override
    public BlockPosition i() {
        return this.q;
    }

    @Override
    public float j() {
        return (float) this.p.getValue();
    }

    @Override
    public boolean k() {
        return this.m;
    }

    @Override
    public void l() {
        if (this.b.getTime() - this.n > 20L) {
            if (this.q != null) {
                this.c = null;
                this.c = b(this.q);
                this.n = this.b.getTime();
                this.m = false;
            }
        } else {
            this.m = true;
        }
    }

    @Override
    public PathEntity m() {
        return this.c;
    }

    @Override
    protected void n() {
    }

    @Override
    protected void o() {
        Vec3D localVec3D1 = c();
        int i1 = this.c.d();
        for (int i2 = this.c.e(); i2 < this.c.d(); i2++) {
            if (this.c.a(i2).b != Math.floor(localVec3D1.y)) {
                i1 = i2;
                break;
            }
        }
        this.l = this.a.width > 0.75F ? this.a.width / 2.0F : 0.75F - this.a.width / 2.0F;
        Vec3D localVec3D2 = this.c.f();
        if (MathHelper.e((float) (this.a.locX - (localVec3D2.x + 0.5D))) < this.l
                && MathHelper.e((float) (this.a.locZ - (localVec3D2.z + 0.5D))) < this.l
                && Math.abs(this.a.locY - localVec3D2.y) < 1.0D) {
            this.c.c(this.c.e() + 1);
        }
        int i3 = MathHelper.f(this.a.width);
        int i4 = MathHelper.f(this.a.length);
        int i5 = i3;
        for (int i6 = i1 - 1; i6 >= this.c.e(); i6--) {
            if (a(localVec3D1, this.c.a(this.a, i6), i3, i4, i5)) {
                this.c.c(i6);
                break;
            }
        }
        a(localVec3D1);
    }

    @Override
    public boolean p() {
        return this.c == null || this.c.b();
    }

    @Override
    public void q() {
        this.c = null;
    }

    @Override
    protected boolean r() {
        return this.a.aq() || this.a.ax();
    }

    @Override
    public PathfinderAbstract s() {
        return this.o;
    }

    public void setRange(float pathfindingRange) {
        this.p.setValue(pathfindingRange);
    }

    public PathEntity superb(BlockPosition paramBlockPosition) {
        if (!b())
            return null;
        if (this.c != null && !this.c.b() && paramBlockPosition.equals(this.q))
            return this.c;
        this.q = paramBlockPosition;
        float f1 = j();
        try {
            this.b.methodProfiler.enter("pathfind");
        } catch (NoSuchMethodError ex) {
            try {
                PROFILER_ENTER.invoke(this.b.methodProfiler, "pathfind");
            } catch (Throwable ex2) {
                ex2.printStackTrace();
            }
        }
        BlockPosition localBlockPosition = new BlockPosition(this.a);
        int i1 = (int) (f1 + 8.0F);
        ChunkCache localChunkCache = new ChunkCache(this.b, localBlockPosition.a(-i1, -i1, -i1),
                localBlockPosition.a(i1, i1, i1), 0);
        PathEntity localPathEntity = this.r.a(localChunkCache, this.a, this.q, f1);
        try {
            this.b.methodProfiler.exit();
        } catch (NoSuchMethodError ex) {
            try {
                PROFILER_EXIT.invoke(this.b.methodProfiler);
            } catch (Throwable ex2) {
                ex2.printStackTrace();
            }
        }
        return localPathEntity;
    }

    protected void superE_() {
        if (this.c == null)
            return;
        for (int i1 = 0; i1 < this.c.d(); i1++) {
            PathPoint localPathPoint = this.c.a(i1);
            Object localObject = i1 + 1 < this.c.d() ? this.c.a(i1 + 1) : null;
            IBlockData localIBlockData = this.b
                    .getType(new BlockPosition(localPathPoint.a, localPathPoint.b, localPathPoint.c));
            Block localBlock = localIBlockData.getBlock();
            if (localBlock == Blocks.CAULDRON) {
                this.c.a(i1, localPathPoint.a(localPathPoint.a, localPathPoint.b + 1, localPathPoint.c));
                if (localObject != null && localPathPoint.b >= ((PathPoint) localObject).b) {
                    this.c.a(i1 + 1, ((PathPoint) localObject).a(((PathPoint) localObject).a, localPathPoint.b + 1,
                            ((PathPoint) localObject).c));
                }
            }
        }
    }

    @Override
    public boolean t() {
        return this.o.e();
    }

    private int u() {
        BoundingBox bb = NMSBoundingBox.wrap(this.a.getBoundingBox());
        if (!this.a.isInWater() || !t())
            return (int) (bb.minY + 0.5D);
        int i = (int) bb.minY;
        Block localBlock = this.b
                .getType(new BlockPosition(MathHelper.floor(this.a.locX), i, MathHelper.floor(this.a.locZ))).getBlock();
        int j = 0;
        while (localBlock == Blocks.WATER) {
            i++;
            localBlock = this.b
                    .getType(new BlockPosition(MathHelper.floor(this.a.locX), i, MathHelper.floor(this.a.locZ)))
                    .getBlock();
            j++;
            if (j > 16)
                return (int) bb.minY;
        }
        return i;
    }

    private static EntityInsentient getDummyInsentient(EntityHumanNPC from, World world) {
        return new EntityInsentient(EntityTypes.PLAYER, world) {
        };
    }

    private static long getMonotonicMillis() {
        try {
            return SystemUtils.getMonotonicMillis();
        } catch (NoSuchMethodError ex) {
            try {
                return (long) GET_MONOTONIC_MILLIS.invoke(null);
            } catch (Throwable ex2) {
                ex2.printStackTrace();
                return 0;
            }
        }
    }

    private static final Method GET_MONOTONIC_MILLIS = NMS.getMethod(SystemUtils.class, "b", false);
    private static final Method PROFILER_ENTER = NMS.getMethod(MethodProfiler.class, "a", false, String.class);
    private static final Method PROFILER_EXIT = NMS.getMethod(MethodProfiler.class, "e", false);
}
