package net.minecraft.world.entity.fishing;

import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.Packet;
import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.List;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.stats.Stats;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.LootContext;
import java.util.Collection;
import java.util.Collections;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.Block;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.tags.FluidTags;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.player.Player;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.world.entity.Entity;

public class FishingHook extends Entity {
    private static final EntityDataAccessor<Integer> DATA_HOOKED_ENTITY;
    private boolean inGround;
    private int life;
    private final Player owner;
    private int flightTime;
    private int nibble;
    private int timeUntilLured;
    private int timeUntilHooked;
    private float fishAngle;
    public Entity hookedIn;
    private FishHookState currentState;
    private final int luck;
    private final int lureSpeed;
    
    private FishingHook(final Level bjw, final Player ayj, final int integer3, final int integer4) {
        super(EntityType.FISHING_BOBBER, bjw);
        this.currentState = FishHookState.FLYING;
        this.noCulling = true;
        this.owner = ayj;
        this.owner.fishing = this;
        this.luck = Math.max(0, integer3);
        this.lureSpeed = Math.max(0, integer4);
    }
    
    public FishingHook(final Level bjw, final Player ayj, final double double3, final double double4, final double double5) {
        this(bjw, ayj, 0, 0);
        this.setPos(double3, double4, double5);
        this.xo = this.getX();
        this.yo = this.getY();
        this.zo = this.getZ();
    }
    
    public FishingHook(final Player ayj, final Level bjw, final int integer3, final int integer4) {
        this(bjw, ayj, integer3, integer4);
        final float float6 = this.owner.xRot;
        final float float7 = this.owner.yRot;
        final float float8 = Mth.cos(-float7 * 0.017453292f - 3.1415927f);
        final float float9 = Mth.sin(-float7 * 0.017453292f - 3.1415927f);
        final float float10 = -Mth.cos(-float6 * 0.017453292f);
        final float float11 = Mth.sin(-float6 * 0.017453292f);
        final double double12 = this.owner.getX() - float9 * 0.3;
        final double double13 = this.owner.getEyeY();
        final double double14 = this.owner.getZ() - float8 * 0.3;
        this.moveTo(double12, double13, double14, float7, float6);
        Vec3 cvl18 = new Vec3(-float9, Mth.clamp(-(float11 / float10), -5.0f, 5.0f), -float8);
        final double double15 = cvl18.length();
        cvl18 = cvl18.multiply(0.6 / double15 + 0.5 + this.random.nextGaussian() * 0.0045, 0.6 / double15 + 0.5 + this.random.nextGaussian() * 0.0045, 0.6 / double15 + 0.5 + this.random.nextGaussian() * 0.0045);
        this.setDeltaMovement(cvl18);
        this.yRot = (float)(Mth.atan2(cvl18.x, cvl18.z) * 57.2957763671875);
        this.xRot = (float)(Mth.atan2(cvl18.y, Mth.sqrt(Entity.getHorizontalDistanceSqr(cvl18))) * 57.2957763671875);
        this.yRotO = this.yRot;
        this.xRotO = this.xRot;
    }
    
    @Override
    protected void defineSynchedData() {
        this.getEntityData().<Integer>define(FishingHook.DATA_HOOKED_ENTITY, 0);
    }
    
    @Override
    public void onSyncedDataUpdated(final EntityDataAccessor<?> sb) {
        if (FishingHook.DATA_HOOKED_ENTITY.equals(sb)) {
            final int integer3 = this.getEntityData().<Integer>get(FishingHook.DATA_HOOKED_ENTITY);
            this.hookedIn = ((integer3 > 0) ? this.level.getEntity(integer3 - 1) : null);
        }
        super.onSyncedDataUpdated(sb);
    }
    
    @Override
    public boolean shouldRenderAtSqrDistance(final double double1) {
        final double double2 = 64.0;
        return double1 < 4096.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) {
    }
    
    @Override
    public void tick() {
        super.tick();
        if (this.owner == null) {
            this.remove();
            return;
        }
        if (!this.level.isClientSide && this.shouldStopFishing()) {
            return;
        }
        if (this.inGround) {
            ++this.life;
            if (this.life >= 1200) {
                this.remove();
                return;
            }
        }
        float float2 = 0.0f;
        final BlockPos fk3 = new BlockPos(this);
        final FluidState coj4 = this.level.getFluidState(fk3);
        if (coj4.is(FluidTags.WATER)) {
            float2 = coj4.getHeight(this.level, fk3);
        }
        if (this.currentState == FishHookState.FLYING) {
            if (this.hookedIn != null) {
                this.setDeltaMovement(Vec3.ZERO);
                this.currentState = FishHookState.HOOKED_IN_ENTITY;
                return;
            }
            if (float2 > 0.0f) {
                this.setDeltaMovement(this.getDeltaMovement().multiply(0.3, 0.2, 0.3));
                this.currentState = FishHookState.BOBBING;
                return;
            }
            if (!this.level.isClientSide) {
                this.checkCollision();
            }
            if (this.inGround || this.onGround || this.horizontalCollision) {
                this.flightTime = 0;
                this.setDeltaMovement(Vec3.ZERO);
            }
            else {
                ++this.flightTime;
            }
        }
        else {
            if (this.currentState == FishHookState.HOOKED_IN_ENTITY) {
                if (this.hookedIn != null) {
                    if (this.hookedIn.removed) {
                        this.hookedIn = null;
                        this.currentState = FishHookState.FLYING;
                    }
                    else {
                        this.setPos(this.hookedIn.getX(), this.hookedIn.getY(0.8), this.hookedIn.getZ());
                    }
                }
                return;
            }
            if (this.currentState == FishHookState.BOBBING) {
                final Vec3 cvl5 = this.getDeltaMovement();
                double double6 = this.getY() + cvl5.y - fk3.getY() - float2;
                if (Math.abs(double6) < 0.01) {
                    double6 += Math.signum(double6) * 0.1;
                }
                this.setDeltaMovement(cvl5.x * 0.9, cvl5.y - double6 * this.random.nextFloat() * 0.2, cvl5.z * 0.9);
                if (!this.level.isClientSide && float2 > 0.0f) {
                    this.catchingFish(fk3);
                }
            }
        }
        if (!coj4.is(FluidTags.WATER)) {
            this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.03, 0.0));
        }
        this.move(MoverType.SELF, this.getDeltaMovement());
        this.updateRotation();
        final double double7 = 0.92;
        this.setDeltaMovement(this.getDeltaMovement().scale(0.92));
        this.reapplyPosition();
    }
    
    private boolean shouldStopFishing() {
        final ItemStack ben2 = this.owner.getMainHandItem();
        final ItemStack ben3 = this.owner.getOffhandItem();
        final boolean boolean4 = ben2.getItem() == Items.FISHING_ROD;
        final boolean boolean5 = ben3.getItem() == Items.FISHING_ROD;
        if (this.owner.removed || !this.owner.isAlive() || (!boolean4 && !boolean5) || this.distanceToSqr(this.owner) > 1024.0) {
            this.remove();
            return true;
        }
        return false;
    }
    
    private void updateRotation() {
        final Vec3 cvl2 = this.getDeltaMovement();
        final float float3 = Mth.sqrt(Entity.getHorizontalDistanceSqr(cvl2));
        this.yRot = (float)(Mth.atan2(cvl2.x, cvl2.z) * 57.2957763671875);
        this.xRot = (float)(Mth.atan2(cvl2.y, float3) * 57.2957763671875);
        while (this.xRot - this.xRotO < -180.0f) {
            this.xRotO -= 360.0f;
        }
        while (this.xRot - this.xRotO >= 180.0f) {
            this.xRotO += 360.0f;
        }
        while (this.yRot - this.yRotO < -180.0f) {
            this.yRotO -= 360.0f;
        }
        while (this.yRot - this.yRotO >= 180.0f) {
            this.yRotO += 360.0f;
        }
        this.xRot = Mth.lerp(0.2f, this.xRotO, this.xRot);
        this.yRot = Mth.lerp(0.2f, this.yRotO, this.yRot);
    }
    
    private void checkCollision() {
        final HitResult cvi2 = ProjectileUtil.getHitResult(this, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0), akq -> !akq.isSpectator() && (akq.isPickable() || akq instanceof ItemEntity) && (akq != this.owner || this.flightTime >= 5), ClipContext.Block.COLLIDER, true);
        if (cvi2.getType() != HitResult.Type.MISS) {
            if (cvi2.getType() == HitResult.Type.ENTITY) {
                this.hookedIn = ((EntityHitResult)cvi2).getEntity();
                this.setHookedEntity();
            }
            else {
                this.inGround = true;
            }
        }
    }
    
    private void setHookedEntity() {
        this.getEntityData().<Integer>set(FishingHook.DATA_HOOKED_ENTITY, this.hookedIn.getId() + 1);
    }
    
    private void catchingFish(final BlockPos fk) {
        final ServerLevel xd3 = (ServerLevel)this.level;
        int integer4 = 1;
        final BlockPos fk2 = fk.above();
        if (this.random.nextFloat() < 0.25f && this.level.isRainingAt(fk2)) {
            ++integer4;
        }
        if (this.random.nextFloat() < 0.5f && !this.level.canSeeSky(fk2)) {
            --integer4;
        }
        if (this.nibble > 0) {
            --this.nibble;
            if (this.nibble <= 0) {
                this.timeUntilLured = 0;
                this.timeUntilHooked = 0;
            }
            else {
                this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.2 * this.random.nextFloat() * this.random.nextFloat(), 0.0));
            }
        }
        else if (this.timeUntilHooked > 0) {
            this.timeUntilHooked -= integer4;
            if (this.timeUntilHooked > 0) {
                this.fishAngle += (float)(this.random.nextGaussian() * 4.0);
                final float float6 = this.fishAngle * 0.017453292f;
                final float float7 = Mth.sin(float6);
                final float float8 = Mth.cos(float6);
                final double double9 = this.getX() + float7 * this.timeUntilHooked * 0.1f;
                final double double10 = Mth.floor(this.getY()) + 1.0f;
                final double double11 = this.getZ() + float8 * this.timeUntilHooked * 0.1f;
                final Block bph15 = xd3.getBlockState(new BlockPos(double9, double10 - 1.0, double11)).getBlock();
                if (bph15 == Blocks.WATER) {
                    if (this.random.nextFloat() < 0.15f) {
                        xd3.<SimpleParticleType>sendParticles(ParticleTypes.BUBBLE, double9, double10 - 0.10000000149011612, double11, 1, float7, 0.1, float8, 0.0);
                    }
                    final float float9 = float7 * 0.04f;
                    final float float10 = float8 * 0.04f;
                    xd3.<SimpleParticleType>sendParticles(ParticleTypes.FISHING, double9, double10, double11, 0, float10, 0.01, -float9, 1.0);
                    xd3.<SimpleParticleType>sendParticles(ParticleTypes.FISHING, double9, double10, double11, 0, -float10, 0.01, float9, 1.0);
                }
            }
            else {
                final Vec3 cvl6 = this.getDeltaMovement();
                this.setDeltaMovement(cvl6.x, -0.4f * Mth.nextFloat(this.random, 0.6f, 1.0f), cvl6.z);
                this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25f, 1.0f + (this.random.nextFloat() - this.random.nextFloat()) * 0.4f);
                final double double12 = this.getY() + 0.5;
                xd3.<SimpleParticleType>sendParticles(ParticleTypes.BUBBLE, this.getX(), double12, this.getZ(), (int)(1.0f + this.getBbWidth() * 20.0f), this.getBbWidth(), 0.0, this.getBbWidth(), 0.20000000298023224);
                xd3.<SimpleParticleType>sendParticles(ParticleTypes.FISHING, this.getX(), double12, this.getZ(), (int)(1.0f + this.getBbWidth() * 20.0f), this.getBbWidth(), 0.0, this.getBbWidth(), 0.20000000298023224);
                this.nibble = Mth.nextInt(this.random, 20, 40);
            }
        }
        else if (this.timeUntilLured > 0) {
            this.timeUntilLured -= integer4;
            float float6 = 0.15f;
            if (this.timeUntilLured < 20) {
                float6 += (float)((20 - this.timeUntilLured) * 0.05);
            }
            else if (this.timeUntilLured < 40) {
                float6 += (float)((40 - this.timeUntilLured) * 0.02);
            }
            else if (this.timeUntilLured < 60) {
                float6 += (float)((60 - this.timeUntilLured) * 0.01);
            }
            if (this.random.nextFloat() < float6) {
                final float float7 = Mth.nextFloat(this.random, 0.0f, 360.0f) * 0.017453292f;
                final float float8 = Mth.nextFloat(this.random, 25.0f, 60.0f);
                final double double9 = this.getX() + Mth.sin(float7) * float8 * 0.1f;
                final double double10 = Mth.floor(this.getY()) + 1.0f;
                final double double11 = this.getZ() + Mth.cos(float7) * float8 * 0.1f;
                final Block bph15 = xd3.getBlockState(new BlockPos(double9, double10 - 1.0, double11)).getBlock();
                if (bph15 == Blocks.WATER) {
                    xd3.<SimpleParticleType>sendParticles(ParticleTypes.SPLASH, double9, double10, double11, 2 + this.random.nextInt(2), 0.10000000149011612, 0.0, 0.10000000149011612, 0.0);
                }
            }
            if (this.timeUntilLured <= 0) {
                this.fishAngle = Mth.nextFloat(this.random, 0.0f, 360.0f);
                this.timeUntilHooked = Mth.nextInt(this.random, 20, 80);
            }
        }
        else {
            this.timeUntilLured = Mth.nextInt(this.random, 100, 600);
            this.timeUntilLured -= this.lureSpeed * 20 * 5;
        }
    }
    
    public void addAdditionalSaveData(final CompoundTag jt) {
    }
    
    public void readAdditionalSaveData(final CompoundTag jt) {
    }
    
    public int retrieve(final ItemStack ben) {
        if (this.level.isClientSide || this.owner == null) {
            return 0;
        }
        int integer3 = 0;
        if (this.hookedIn != null) {
            this.bringInHookedEntity();
            CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)this.owner, ben, this, Collections.emptyList());
            this.level.broadcastEntityEvent(this, (byte)31);
            integer3 = ((this.hookedIn instanceof ItemEntity) ? 3 : 5);
        }
        else if (this.nibble > 0) {
            final LootContext.Builder a4 = new LootContext.Builder((ServerLevel)this.level).<BlockPos>withParameter(LootContextParams.BLOCK_POS, new BlockPos(this)).<ItemStack>withParameter(LootContextParams.TOOL, ben).withRandom(this.random).withLuck(this.luck + this.owner.getLuck());
            final LootTable csa5 = this.level.getServer().getLootTables().get(BuiltInLootTables.FISHING);
            final List<ItemStack> list6 = csa5.getRandomItems(a4.create(LootContextParamSets.FISHING));
            CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)this.owner, ben, this, list6);
            for (final ItemStack ben2 : list6) {
                final ItemEntity awb9 = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), ben2);
                final double double10 = this.owner.getX() - this.getX();
                final double double11 = this.owner.getY() - this.getY();
                final double double12 = this.owner.getZ() - this.getZ();
                final double double13 = 0.1;
                awb9.setDeltaMovement(double10 * 0.1, double11 * 0.1 + Math.sqrt(Math.sqrt(double10 * double10 + double11 * double11 + double12 * double12)) * 0.08, double12 * 0.1);
                this.level.addFreshEntity(awb9);
                this.owner.level.addFreshEntity(new ExperienceOrb(this.owner.level, this.owner.getX(), this.owner.getY() + 0.5, this.owner.getZ() + 0.5, this.random.nextInt(6) + 1));
                if (ben2.getItem().is(ItemTags.FISHES)) {
                    this.owner.awardStat(Stats.FISH_CAUGHT, 1);
                }
            }
            integer3 = 1;
        }
        if (this.inGround) {
            integer3 = 2;
        }
        this.remove();
        return integer3;
    }
    
    @Override
    public void handleEntityEvent(final byte byte1) {
        if (byte1 == 31 && this.level.isClientSide && this.hookedIn instanceof Player && ((Player)this.hookedIn).isLocalPlayer()) {
            this.bringInHookedEntity();
        }
        super.handleEntityEvent(byte1);
    }
    
    protected void bringInHookedEntity() {
        if (this.owner == null) {
            return;
        }
        final Vec3 cvl2 = new Vec3(this.owner.getX() - this.getX(), this.owner.getY() - this.getY(), this.owner.getZ() - this.getZ()).scale(0.1);
        this.hookedIn.setDeltaMovement(this.hookedIn.getDeltaMovement().add(cvl2));
    }
    
    @Override
    protected boolean isMovementNoisy() {
        return false;
    }
    
    @Override
    public void remove() {
        super.remove();
        if (this.owner != null) {
            this.owner.fishing = null;
        }
    }
    
    @Nullable
    public Player getOwner() {
        return this.owner;
    }
    
    @Override
    public boolean canChangeDimensions() {
        return false;
    }
    
    @Override
    public Packet<?> getAddEntityPacket() {
        final Entity akq2 = this.getOwner();
        return new ClientboundAddEntityPacket(this, (akq2 == null) ? this.getId() : akq2.getId());
    }
    
    static {
        DATA_HOOKED_ENTITY = SynchedEntityData.<Integer>defineId(FishingHook.class, EntityDataSerializers.INT);
    }
    
    enum FishHookState {
        FLYING, 
        HOOKED_IN_ENTITY, 
        BOBBING;
    }
}
