package net.minecraft.world.entity.projectile;

import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.Packet;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.entity.player.Player;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Difficulty;
import java.util.List;
import net.minecraft.util.Mth;
import com.google.common.collect.Lists;
import net.minecraft.core.Position;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundSource;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.EntityType;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;

public class ShulkerBullet extends Projectile {
    private Entity finalTarget;
    @Nullable
    private Direction currentMoveDirection;
    private int flightSteps;
    private double targetDeltaX;
    private double targetDeltaY;
    private double targetDeltaZ;
    @Nullable
    private UUID targetId;
    
    public ShulkerBullet(final EntityType<? extends ShulkerBullet> aoq, final Level bqb) {
        super(aoq, bqb);
        this.noPhysics = true;
    }
    
    public ShulkerBullet(final Level bqb, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
        this(EntityType.SHULKER_BULLET, bqb);
        this.moveTo(double2, double3, double4, this.yRot, this.xRot);
        this.setDeltaMovement(double5, double6, double7);
    }
    
    public ShulkerBullet(final Level bqb, final LivingEntity aoy, final Entity aom, final Direction.Axis a) {
        this(EntityType.SHULKER_BULLET, bqb);
        this.setOwner(aoy);
        final BlockPos fu6 = aoy.blockPosition();
        final double double7 = fu6.getX() + 0.5;
        final double double8 = fu6.getY() + 0.5;
        final double double9 = fu6.getZ() + 0.5;
        this.moveTo(double7, double8, double9, this.yRot, this.xRot);
        this.finalTarget = aom;
        this.currentMoveDirection = Direction.UP;
        this.selectNextMoveDirection(a);
    }
    
    @Override
    public SoundSource getSoundSource() {
        return SoundSource.HOSTILE;
    }
    
    @Override
    protected void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        if (this.finalTarget != null) {
            le.putUUID("Target", this.finalTarget.getUUID());
        }
        if (this.currentMoveDirection != null) {
            le.putInt("Dir", this.currentMoveDirection.get3DDataValue());
        }
        le.putInt("Steps", this.flightSteps);
        le.putDouble("TXD", this.targetDeltaX);
        le.putDouble("TYD", this.targetDeltaY);
        le.putDouble("TZD", this.targetDeltaZ);
    }
    
    @Override
    protected void readAdditionalSaveData(final CompoundTag le) {
        super.readAdditionalSaveData(le);
        this.flightSteps = le.getInt("Steps");
        this.targetDeltaX = le.getDouble("TXD");
        this.targetDeltaY = le.getDouble("TYD");
        this.targetDeltaZ = le.getDouble("TZD");
        if (le.contains("Dir", 99)) {
            this.currentMoveDirection = Direction.from3DDataValue(le.getInt("Dir"));
        }
        if (le.hasUUID("Target")) {
            this.targetId = le.getUUID("Target");
        }
    }
    
    @Override
    protected void defineSynchedData() {
    }
    
    private void setMoveDirection(@Nullable final Direction fz) {
        this.currentMoveDirection = fz;
    }
    
    private void selectNextMoveDirection(@Nullable final Direction.Axis a) {
        double double4 = 0.5;
        BlockPos fu3;
        if (this.finalTarget == null) {
            fu3 = this.blockPosition().below();
        }
        else {
            double4 = this.finalTarget.getBbHeight() * 0.5;
            fu3 = new BlockPos(this.finalTarget.getX(), this.finalTarget.getY() + double4, this.finalTarget.getZ());
        }
        double double5 = fu3.getX() + 0.5;
        double double6 = fu3.getY() + double4;
        double double7 = fu3.getZ() + 0.5;
        Direction fz12 = null;
        if (!fu3.closerThan(this.position(), 2.0)) {
            final BlockPos fu4 = this.blockPosition();
            final List<Direction> list14 = Lists.newArrayList();
            if (a != Direction.Axis.X) {
                if (fu4.getX() < fu3.getX() && this.level.isEmptyBlock(fu4.east())) {
                    list14.add(Direction.EAST);
                }
                else if (fu4.getX() > fu3.getX() && this.level.isEmptyBlock(fu4.west())) {
                    list14.add(Direction.WEST);
                }
            }
            if (a != Direction.Axis.Y) {
                if (fu4.getY() < fu3.getY() && this.level.isEmptyBlock(fu4.above())) {
                    list14.add(Direction.UP);
                }
                else if (fu4.getY() > fu3.getY() && this.level.isEmptyBlock(fu4.below())) {
                    list14.add(Direction.DOWN);
                }
            }
            if (a != Direction.Axis.Z) {
                if (fu4.getZ() < fu3.getZ() && this.level.isEmptyBlock(fu4.south())) {
                    list14.add(Direction.SOUTH);
                }
                else if (fu4.getZ() > fu3.getZ() && this.level.isEmptyBlock(fu4.north())) {
                    list14.add(Direction.NORTH);
                }
            }
            fz12 = Direction.getRandom(this.random);
            if (list14.isEmpty()) {
                for (int integer15 = 5; !this.level.isEmptyBlock(fu4.relative(fz12)) && integer15 > 0; fz12 = Direction.getRandom(this.random), --integer15) {}
            }
            else {
                fz12 = list14.get(this.random.nextInt(list14.size()));
            }
            double5 = this.getX() + fz12.getStepX();
            double6 = this.getY() + fz12.getStepY();
            double7 = this.getZ() + fz12.getStepZ();
        }
        this.setMoveDirection(fz12);
        final double double8 = double5 - this.getX();
        final double double9 = double6 - this.getY();
        final double double10 = double7 - this.getZ();
        final double double11 = Mth.sqrt(double8 * double8 + double9 * double9 + double10 * double10);
        if (double11 == 0.0) {
            this.targetDeltaX = 0.0;
            this.targetDeltaY = 0.0;
            this.targetDeltaZ = 0.0;
        }
        else {
            this.targetDeltaX = double8 / double11 * 0.15;
            this.targetDeltaY = double9 / double11 * 0.15;
            this.targetDeltaZ = double10 / double11 * 0.15;
        }
        this.hasImpulse = true;
        this.flightSteps = 10 + this.random.nextInt(5) * 10;
    }
    
    @Override
    public void checkDespawn() {
        if (this.level.getDifficulty() == Difficulty.PEACEFUL) {
            this.remove();
        }
    }
    
    @Override
    public void tick() {
        super.tick();
        if (!this.level.isClientSide) {
            if (this.finalTarget == null && this.targetId != null) {
                this.finalTarget = ((ServerLevel)this.level).getEntity(this.targetId);
                if (this.finalTarget == null) {
                    this.targetId = null;
                }
            }
            if (this.finalTarget != null && this.finalTarget.isAlive() && (!(this.finalTarget instanceof Player) || !((Player)this.finalTarget).isSpectator())) {
                this.targetDeltaX = Mth.clamp(this.targetDeltaX * 1.025, -1.0, 1.0);
                this.targetDeltaY = Mth.clamp(this.targetDeltaY * 1.025, -1.0, 1.0);
                this.targetDeltaZ = Mth.clamp(this.targetDeltaZ * 1.025, -1.0, 1.0);
                final Vec3 dem2 = this.getDeltaMovement();
                this.setDeltaMovement(dem2.add((this.targetDeltaX - dem2.x) * 0.2, (this.targetDeltaY - dem2.y) * 0.2, (this.targetDeltaZ - dem2.z) * 0.2));
            }
            else if (!this.isNoGravity()) {
                this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.04, 0.0));
            }
            final HitResult dej2 = ProjectileUtil.getHitResult(this, this::canHitEntity, ClipContext.Block.COLLIDER);
            if (dej2.getType() != HitResult.Type.MISS) {
                this.onHit(dej2);
            }
        }
        final Vec3 dem2 = this.getDeltaMovement();
        this.setPos(this.getX() + dem2.x, this.getY() + dem2.y, this.getZ() + dem2.z);
        ProjectileUtil.rotateTowardsMovement(this, 0.5f);
        if (this.level.isClientSide) {
            this.level.addParticle(ParticleTypes.END_ROD, this.getX() - dem2.x, this.getY() - dem2.y + 0.15, this.getZ() - dem2.z, 0.0, 0.0, 0.0);
        }
        else if (this.finalTarget != null && !this.finalTarget.removed) {
            if (this.flightSteps > 0) {
                --this.flightSteps;
                if (this.flightSteps == 0) {
                    this.selectNextMoveDirection((this.currentMoveDirection == null) ? null : this.currentMoveDirection.getAxis());
                }
            }
            if (this.currentMoveDirection != null) {
                final BlockPos fu3 = this.blockPosition();
                final Direction.Axis a4 = this.currentMoveDirection.getAxis();
                if (this.level.loadedAndEntityCanStandOn(fu3.relative(this.currentMoveDirection), this)) {
                    this.selectNextMoveDirection(a4);
                }
                else {
                    final BlockPos fu4 = this.finalTarget.blockPosition();
                    if ((a4 == Direction.Axis.X && fu3.getX() == fu4.getX()) || (a4 == Direction.Axis.Z && fu3.getZ() == fu4.getZ()) || (a4 == Direction.Axis.Y && fu3.getY() == fu4.getY())) {
                        this.selectNextMoveDirection(a4);
                    }
                }
            }
        }
    }
    
    @Override
    protected boolean canHitEntity(final Entity aom) {
        return super.canHitEntity(aom) && !aom.noPhysics;
    }
    
    @Override
    public boolean isOnFire() {
        return false;
    }
    
    @Override
    public boolean shouldRenderAtSqrDistance(final double double1) {
        return double1 < 16384.0;
    }
    
    @Override
    public float getBrightness() {
        return 1.0f;
    }
    
    @Override
    protected void onHitEntity(final EntityHitResult dei) {
        super.onHitEntity(dei);
        final Entity aom3 = dei.getEntity();
        final Entity aom4 = this.getOwner();
        final LivingEntity aoy5 = (aom4 instanceof LivingEntity) ? ((LivingEntity)aom4) : null;
        final boolean boolean6 = aom3.hurt(DamageSource.indirectMobAttack(this, aoy5).setProjectile(), 4.0f);
        if (boolean6) {
            this.doEnchantDamageEffects(aoy5, aom3);
            if (aom3 instanceof LivingEntity) {
                ((LivingEntity)aom3).addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200));
            }
        }
    }
    
    @Override
    protected void onHitBlock(final BlockHitResult deh) {
        super.onHitBlock(deh);
        ((ServerLevel)this.level).<SimpleParticleType>sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(), this.getZ(), 2, 0.2, 0.2, 0.2, 0.0);
        this.playSound(SoundEvents.SHULKER_BULLET_HIT, 1.0f, 1.0f);
    }
    
    @Override
    protected void onHit(final HitResult dej) {
        super.onHit(dej);
        this.remove();
    }
    
    @Override
    public boolean isPickable() {
        return true;
    }
    
    @Override
    public boolean hurt(final DamageSource anw, final float float2) {
        if (!this.level.isClientSide) {
            this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0f, 1.0f);
            ((ServerLevel)this.level).<SimpleParticleType>sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2, 0.2, 0.2, 0.0);
            this.remove();
        }
        return true;
    }
    
    @Override
    public Packet<?> getAddEntityPacket() {
        return new ClientboundAddEntityPacket(this);
    }
}
