package net.minecraft.world.entity.projectile;

import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.item.ItemStack;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.core.Registry;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.phys.BlockHitResult;
import java.util.Arrays;
import java.util.Collection;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.damagesource.DamageSource;
import com.google.common.collect.Lists;
import net.minecraft.world.entity.MoverType;
import java.util.Iterator;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Entity;
import java.util.List;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.network.syncher.EntityDataAccessor;

public abstract class AbstractArrow extends Projectile {
    private static final EntityDataAccessor<Byte> ID_FLAGS;
    private static final EntityDataAccessor<Byte> PIERCE_LEVEL;
    @Nullable
    private BlockState lastState;
    protected boolean inGround;
    protected int inGroundTime;
    public Pickup pickup;
    public int shakeTime;
    private int life;
    private double baseDamage;
    private int knockback;
    private SoundEvent soundEvent;
    private IntOpenHashSet piercingIgnoreEntityIds;
    private List<Entity> piercedAndKilledEntities;
    
    protected AbstractArrow(final EntityType<? extends AbstractArrow> aoq, final Level bqb) {
        super(aoq, bqb);
        this.pickup = Pickup.DISALLOWED;
        this.baseDamage = 2.0;
        this.soundEvent = this.getDefaultHitGroundSoundEvent();
    }
    
    protected AbstractArrow(final EntityType<? extends AbstractArrow> aoq, final double double2, final double double3, final double double4, final Level bqb) {
        this(aoq, bqb);
        this.setPos(double2, double3, double4);
    }
    
    protected AbstractArrow(final EntityType<? extends AbstractArrow> aoq, final LivingEntity aoy, final Level bqb) {
        this(aoq, aoy.getX(), aoy.getEyeY() - 0.10000000149011612, aoy.getZ(), bqb);
        this.setOwner(aoy);
        if (aoy instanceof Player) {
            this.pickup = Pickup.ALLOWED;
        }
    }
    
    public void setSoundEvent(final SoundEvent ack) {
        this.soundEvent = ack;
    }
    
    @Override
    public boolean shouldRenderAtSqrDistance(final double double1) {
        double double2 = this.getBoundingBox().getSize() * 10.0;
        if (Double.isNaN(double2)) {
            double2 = 1.0;
        }
        double2 *= 64.0 * getViewScale();
        return double1 < double2 * double2;
    }
    
    @Override
    protected void defineSynchedData() {
        this.entityData.<Byte>define(AbstractArrow.ID_FLAGS, (Byte)0);
        this.entityData.<Byte>define(AbstractArrow.PIERCE_LEVEL, (Byte)0);
    }
    
    @Override
    public void shoot(final double double1, final double double2, final double double3, final float float4, final float float5) {
        super.shoot(double1, double2, double3, float4, float5);
        this.life = 0;
    }
    
    @Override
    public void lerpTo(final double double1, final double double2, final double double3, final float float4, final float float5, final int integer, final boolean boolean7) {
        this.setPos(double1, double2, double3);
        this.setRot(float4, float5);
    }
    
    @Override
    public void lerpMotion(final double double1, final double double2, final double double3) {
        super.lerpMotion(double1, double2, double3);
        this.life = 0;
    }
    
    @Override
    public void tick() {
        super.tick();
        final boolean boolean2 = this.isNoPhysics();
        Vec3 dem3 = this.getDeltaMovement();
        if (this.xRotO == 0.0f && this.yRotO == 0.0f) {
            final float float4 = Mth.sqrt(Entity.getHorizontalDistanceSqr(dem3));
            this.yRot = (float)(Mth.atan2(dem3.x, dem3.z) * 57.2957763671875);
            this.xRot = (float)(Mth.atan2(dem3.y, float4) * 57.2957763671875);
            this.yRotO = this.yRot;
            this.xRotO = this.xRot;
        }
        final BlockPos fu4 = this.blockPosition();
        final BlockState cfj5 = this.level.getBlockState(fu4);
        if (!cfj5.isAir() && !boolean2) {
            final VoxelShape dfg6 = cfj5.getCollisionShape(this.level, fu4);
            if (!dfg6.isEmpty()) {
                final Vec3 dem4 = this.position();
                for (final AABB deg9 : dfg6.toAabbs()) {
                    if (deg9.move(fu4).contains(dem4)) {
                        this.inGround = true;
                        break;
                    }
                }
            }
        }
        if (this.shakeTime > 0) {
            --this.shakeTime;
        }
        if (this.isInWaterOrRain()) {
            this.clearFire();
        }
        if (this.inGround && !boolean2) {
            if (this.lastState != cfj5 && this.shouldFall()) {
                this.startFalling();
            }
            else if (!this.level.isClientSide) {
                this.tickDespawn();
            }
            ++this.inGroundTime;
            return;
        }
        this.inGroundTime = 0;
        final Vec3 dem5 = this.position();
        Vec3 dem4 = dem5.add(dem3);
        HitResult dej8 = this.level.clip(new ClipContext(dem5, dem4, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
        if (dej8.getType() != HitResult.Type.MISS) {
            dem4 = dej8.getLocation();
        }
        while (!this.removed) {
            EntityHitResult dei9 = this.findHitEntity(dem5, dem4);
            if (dei9 != null) {
                dej8 = dei9;
            }
            if (dej8 != null && dej8.getType() == HitResult.Type.ENTITY) {
                final Entity aom10 = ((EntityHitResult)dej8).getEntity();
                final Entity aom11 = this.getOwner();
                if (aom10 instanceof Player && aom11 instanceof Player && !((Player)aom11).canHarmPlayer((Player)aom10)) {
                    dej8 = null;
                    dei9 = null;
                }
            }
            if (dej8 != null && !boolean2) {
                this.onHit(dej8);
                this.hasImpulse = true;
            }
            if (dei9 == null) {
                break;
            }
            if (this.getPierceLevel() <= 0) {
                break;
            }
            dej8 = null;
        }
        dem3 = this.getDeltaMovement();
        final double double9 = dem3.x;
        final double double10 = dem3.y;
        final double double11 = dem3.z;
        if (this.isCritArrow()) {
            for (int integer15 = 0; integer15 < 4; ++integer15) {
                this.level.addParticle(ParticleTypes.CRIT, this.getX() + double9 * integer15 / 4.0, this.getY() + double10 * integer15 / 4.0, this.getZ() + double11 * integer15 / 4.0, -double9, -double10 + 0.2, -double11);
            }
        }
        final double double12 = this.getX() + double9;
        final double double13 = this.getY() + double10;
        final double double14 = this.getZ() + double11;
        final float float5 = Mth.sqrt(Entity.getHorizontalDistanceSqr(dem3));
        if (boolean2) {
            this.yRot = (float)(Mth.atan2(-double9, -double11) * 57.2957763671875);
        }
        else {
            this.yRot = (float)(Mth.atan2(double9, double11) * 57.2957763671875);
        }
        this.xRot = (float)(Mth.atan2(double10, float5) * 57.2957763671875);
        this.xRot = Projectile.lerpRotation(this.xRotO, this.xRot);
        this.yRot = Projectile.lerpRotation(this.yRotO, this.yRot);
        float float6 = 0.99f;
        final float float7 = 0.05f;
        if (this.isInWater()) {
            for (int integer16 = 0; integer16 < 4; ++integer16) {
                final float float8 = 0.25f;
                this.level.addParticle(ParticleTypes.BUBBLE, double12 - double9 * 0.25, double13 - double10 * 0.25, double14 - double11 * 0.25, double9, double10, double11);
            }
            float6 = this.getWaterInertia();
        }
        this.setDeltaMovement(dem3.scale(float6));
        if (!this.isNoGravity() && !boolean2) {
            final Vec3 dem6 = this.getDeltaMovement();
            this.setDeltaMovement(dem6.x, dem6.y - 0.05000000074505806, dem6.z);
        }
        this.setPos(double12, double13, double14);
        this.checkInsideBlocks();
    }
    
    private boolean shouldFall() {
        return this.inGround && this.level.noCollision(new AABB(this.position(), this.position()).inflate(0.06));
    }
    
    private void startFalling() {
        this.inGround = false;
        final Vec3 dem2 = this.getDeltaMovement();
        this.setDeltaMovement(dem2.multiply(this.random.nextFloat() * 0.2f, this.random.nextFloat() * 0.2f, this.random.nextFloat() * 0.2f));
        this.life = 0;
    }
    
    @Override
    public void move(final MoverType apd, final Vec3 dem) {
        super.move(apd, dem);
        if (apd != MoverType.SELF && this.shouldFall()) {
            this.startFalling();
        }
    }
    
    protected void tickDespawn() {
        ++this.life;
        if (this.life >= 1200) {
            this.remove();
        }
    }
    
    private void resetPiercedEntities() {
        if (this.piercedAndKilledEntities != null) {
            this.piercedAndKilledEntities.clear();
        }
        if (this.piercingIgnoreEntityIds != null) {
            this.piercingIgnoreEntityIds.clear();
        }
    }
    
    @Override
    protected void onHitEntity(final EntityHitResult dei) {
        super.onHitEntity(dei);
        final Entity aom3 = dei.getEntity();
        final float float4 = (float)this.getDeltaMovement().length();
        int integer5 = Mth.ceil(Mth.clamp(float4 * this.baseDamage, 0.0, 2.147483647E9));
        if (this.getPierceLevel() > 0) {
            if (this.piercingIgnoreEntityIds == null) {
                this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
            }
            if (this.piercedAndKilledEntities == null) {
                this.piercedAndKilledEntities = Lists.newArrayListWithCapacity(5);
            }
            if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
                this.remove();
                return;
            }
            this.piercingIgnoreEntityIds.add(aom3.getId());
        }
        if (this.isCritArrow()) {
            final long long6 = this.random.nextInt(integer5 / 2 + 2);
            integer5 = (int)Math.min(long6 + integer5, 2147483647L);
        }
        final Entity aom4 = this.getOwner();
        DamageSource anw6;
        if (aom4 == null) {
            anw6 = DamageSource.arrow(this, this);
        }
        else {
            anw6 = DamageSource.arrow(this, aom4);
            if (aom4 instanceof LivingEntity) {
                ((LivingEntity)aom4).setLastHurtMob(aom3);
            }
        }
        final boolean boolean8 = aom3.getType() == EntityType.ENDERMAN;
        final int integer6 = aom3.getRemainingFireTicks();
        if (this.isOnFire() && !boolean8) {
            aom3.setSecondsOnFire(5);
        }
        if (aom3.hurt(anw6, (float)integer5)) {
            if (boolean8) {
                return;
            }
            if (aom3 instanceof LivingEntity) {
                final LivingEntity aoy10 = (LivingEntity)aom3;
                if (!this.level.isClientSide && this.getPierceLevel() <= 0) {
                    aoy10.setArrowCount(aoy10.getArrowCount() + 1);
                }
                if (this.knockback > 0) {
                    final Vec3 dem11 = this.getDeltaMovement().multiply(1.0, 0.0, 1.0).normalize().scale(this.knockback * 0.6);
                    if (dem11.lengthSqr() > 0.0) {
                        aoy10.push(dem11.x, 0.1, dem11.z);
                    }
                }
                if (!this.level.isClientSide && aom4 instanceof LivingEntity) {
                    EnchantmentHelper.doPostHurtEffects(aoy10, aom4);
                    EnchantmentHelper.doPostDamageEffects((LivingEntity)aom4, aoy10);
                }
                this.doPostHurtEffects(aoy10);
                if (aom4 != null && aoy10 != aom4 && aoy10 instanceof Player && aom4 instanceof ServerPlayer && !this.isSilent()) {
                    ((ServerPlayer)aom4).connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.ARROW_HIT_PLAYER, 0.0f));
                }
                if (!aom3.isAlive() && this.piercedAndKilledEntities != null) {
                    this.piercedAndKilledEntities.add(aoy10);
                }
                if (!this.level.isClientSide && aom4 instanceof ServerPlayer) {
                    final ServerPlayer ze11 = (ServerPlayer)aom4;
                    if (this.piercedAndKilledEntities != null && this.shotFromCrossbow()) {
                        CriteriaTriggers.KILLED_BY_CROSSBOW.trigger(ze11, this.piercedAndKilledEntities);
                    }
                    else if (!aom3.isAlive() && this.shotFromCrossbow()) {
                        CriteriaTriggers.KILLED_BY_CROSSBOW.trigger(ze11, Arrays.<Entity>asList(aom3));
                    }
                }
            }
            this.playSound(this.soundEvent, 1.0f, 1.2f / (this.random.nextFloat() * 0.2f + 0.9f));
            if (this.getPierceLevel() <= 0) {
                this.remove();
            }
        }
        else {
            aom3.setRemainingFireTicks(integer6);
            this.setDeltaMovement(this.getDeltaMovement().scale(-0.1));
            this.yRot += 180.0f;
            this.yRotO += 180.0f;
            if (!this.level.isClientSide && this.getDeltaMovement().lengthSqr() < 1.0E-7) {
                if (this.pickup == Pickup.ALLOWED) {
                    this.spawnAtLocation(this.getPickupItem(), 0.1f);
                }
                this.remove();
            }
        }
    }
    
    @Override
    protected void onHitBlock(final BlockHitResult deh) {
        this.lastState = this.level.getBlockState(deh.getBlockPos());
        super.onHitBlock(deh);
        final Vec3 dem3 = deh.getLocation().subtract(this.getX(), this.getY(), this.getZ());
        this.setDeltaMovement(dem3);
        final Vec3 dem4 = dem3.normalize().scale(0.05000000074505806);
        this.setPosRaw(this.getX() - dem4.x, this.getY() - dem4.y, this.getZ() - dem4.z);
        this.playSound(this.getHitGroundSoundEvent(), 1.0f, 1.2f / (this.random.nextFloat() * 0.2f + 0.9f));
        this.inGround = true;
        this.shakeTime = 7;
        this.setCritArrow(false);
        this.setPierceLevel((byte)0);
        this.setSoundEvent(SoundEvents.ARROW_HIT);
        this.setShotFromCrossbow(false);
        this.resetPiercedEntities();
    }
    
    protected SoundEvent getDefaultHitGroundSoundEvent() {
        return SoundEvents.ARROW_HIT;
    }
    
    protected final SoundEvent getHitGroundSoundEvent() {
        return this.soundEvent;
    }
    
    protected void doPostHurtEffects(final LivingEntity aoy) {
    }
    
    @Nullable
    protected EntityHitResult findHitEntity(final Vec3 dem1, final Vec3 dem2) {
        return ProjectileUtil.getEntityHitResult(this.level, this, dem1, dem2, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0), this::canHitEntity);
    }
    
    @Override
    protected boolean canHitEntity(final Entity aom) {
        return super.canHitEntity(aom) && (this.piercingIgnoreEntityIds == null || !this.piercingIgnoreEntityIds.contains(aom.getId()));
    }
    
    public void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        le.putShort("life", (short)this.life);
        if (this.lastState != null) {
            le.put("inBlockState", NbtUtils.writeBlockState(this.lastState));
        }
        le.putByte("shake", (byte)this.shakeTime);
        le.putBoolean("inGround", this.inGround);
        le.putByte("pickup", (byte)this.pickup.ordinal());
        le.putDouble("damage", this.baseDamage);
        le.putBoolean("crit", this.isCritArrow());
        le.putByte("PierceLevel", this.getPierceLevel());
        le.putString("SoundEvent", Registry.SOUND_EVENT.getKey(this.soundEvent).toString());
        le.putBoolean("ShotFromCrossbow", this.shotFromCrossbow());
    }
    
    public void readAdditionalSaveData(final CompoundTag le) {
        super.readAdditionalSaveData(le);
        this.life = le.getShort("life");
        if (le.contains("inBlockState", 10)) {
            this.lastState = NbtUtils.readBlockState(le.getCompound("inBlockState"));
        }
        this.shakeTime = (le.getByte("shake") & 0xFF);
        this.inGround = le.getBoolean("inGround");
        if (le.contains("damage", 99)) {
            this.baseDamage = le.getDouble("damage");
        }
        if (le.contains("pickup", 99)) {
            this.pickup = Pickup.byOrdinal(le.getByte("pickup"));
        }
        else if (le.contains("player", 99)) {
            this.pickup = (le.getBoolean("player") ? Pickup.ALLOWED : Pickup.DISALLOWED);
        }
        this.setCritArrow(le.getBoolean("crit"));
        this.setPierceLevel(le.getByte("PierceLevel"));
        if (le.contains("SoundEvent", 8)) {
            this.soundEvent = Registry.SOUND_EVENT.getOptional(new ResourceLocation(le.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent());
        }
        this.setShotFromCrossbow(le.getBoolean("ShotFromCrossbow"));
    }
    
    @Override
    public void setOwner(@Nullable final Entity aom) {
        super.setOwner(aom);
        if (aom instanceof Player) {
            this.pickup = (((Player)aom).abilities.instabuild ? Pickup.CREATIVE_ONLY : Pickup.ALLOWED);
        }
    }
    
    @Override
    public void playerTouch(final Player bec) {
        if (this.level.isClientSide || (!this.inGround && !this.isNoPhysics()) || this.shakeTime > 0) {
            return;
        }
        boolean boolean3 = this.pickup == Pickup.ALLOWED || (this.pickup == Pickup.CREATIVE_ONLY && bec.abilities.instabuild) || (this.isNoPhysics() && this.getOwner().getUUID() == bec.getUUID());
        if (this.pickup == Pickup.ALLOWED && !bec.inventory.add(this.getPickupItem())) {
            boolean3 = false;
        }
        if (boolean3) {
            bec.take(this, 1);
            this.remove();
        }
    }
    
    protected abstract ItemStack getPickupItem();
    
    @Override
    protected boolean isMovementNoisy() {
        return false;
    }
    
    public void setBaseDamage(final double double1) {
        this.baseDamage = double1;
    }
    
    public double getBaseDamage() {
        return this.baseDamage;
    }
    
    public void setKnockback(final int integer) {
        this.knockback = integer;
    }
    
    @Override
    public boolean isAttackable() {
        return false;
    }
    
    @Override
    protected float getEyeHeight(final Pose apj, final EntityDimensions aon) {
        return 0.13f;
    }
    
    public void setCritArrow(final boolean boolean1) {
        this.setFlag(1, boolean1);
    }
    
    public void setPierceLevel(final byte byte1) {
        this.entityData.<Byte>set(AbstractArrow.PIERCE_LEVEL, byte1);
    }
    
    private void setFlag(final int integer, final boolean boolean2) {
        final byte byte4 = this.entityData.<Byte>get(AbstractArrow.ID_FLAGS);
        if (boolean2) {
            this.entityData.<Byte>set(AbstractArrow.ID_FLAGS, (byte)(byte4 | integer));
        }
        else {
            this.entityData.<Byte>set(AbstractArrow.ID_FLAGS, (byte)(byte4 & ~integer));
        }
    }
    
    public boolean isCritArrow() {
        final byte byte2 = this.entityData.<Byte>get(AbstractArrow.ID_FLAGS);
        return (byte2 & 0x1) != 0x0;
    }
    
    public boolean shotFromCrossbow() {
        final byte byte2 = this.entityData.<Byte>get(AbstractArrow.ID_FLAGS);
        return (byte2 & 0x4) != 0x0;
    }
    
    public byte getPierceLevel() {
        return this.entityData.<Byte>get(AbstractArrow.PIERCE_LEVEL);
    }
    
    public void setEnchantmentEffectsFromEntity(final LivingEntity aoy, final float float2) {
        final int integer4 = EnchantmentHelper.getEnchantmentLevel(Enchantments.POWER_ARROWS, aoy);
        final int integer5 = EnchantmentHelper.getEnchantmentLevel(Enchantments.PUNCH_ARROWS, aoy);
        this.setBaseDamage(float2 * 2.0f + (this.random.nextGaussian() * 0.25 + this.level.getDifficulty().getId() * 0.11f));
        if (integer4 > 0) {
            this.setBaseDamage(this.getBaseDamage() + integer4 * 0.5 + 0.5);
        }
        if (integer5 > 0) {
            this.setKnockback(integer5);
        }
        if (EnchantmentHelper.getEnchantmentLevel(Enchantments.FLAMING_ARROWS, aoy) > 0) {
            this.setSecondsOnFire(100);
        }
    }
    
    protected float getWaterInertia() {
        return 0.6f;
    }
    
    public void setNoPhysics(final boolean boolean1) {
        this.setFlag(2, this.noPhysics = boolean1);
    }
    
    public boolean isNoPhysics() {
        if (!this.level.isClientSide) {
            return this.noPhysics;
        }
        return (this.entityData.<Byte>get(AbstractArrow.ID_FLAGS) & 0x2) != 0x0;
    }
    
    public void setShotFromCrossbow(final boolean boolean1) {
        this.setFlag(4, boolean1);
    }
    
    @Override
    public Packet<?> getAddEntityPacket() {
        final Entity aom2 = this.getOwner();
        return new ClientboundAddEntityPacket(this, (aom2 == null) ? 0 : aom2.getId());
    }
    
    static {
        ID_FLAGS = SynchedEntityData.<Byte>defineId(AbstractArrow.class, EntityDataSerializers.BYTE);
        PIERCE_LEVEL = SynchedEntityData.<Byte>defineId(AbstractArrow.class, EntityDataSerializers.BYTE);
    }
    
    public enum Pickup {
        DISALLOWED, 
        ALLOWED, 
        CREATIVE_ONLY;
        
        public static Pickup byOrdinal(int integer) {
            if (integer < 0 || integer > values().length) {
                integer = 0;
            }
            return values()[integer];
        }
    }
}
