package net.minecraft.world.entity.monster;

import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.level.block.Block;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import java.util.Random;
import net.minecraft.world.level.LevelReader;
import net.minecraft.util.Mth;
import net.minecraft.world.level.GameRules;
import java.util.EnumSet;
import net.minecraft.world.entity.ai.targeting.TargetingConditions;
import net.minecraft.util.TimeUtil;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.world.damagesource.IndirectEntityDamageSource;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.tags.FluidTags;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.item.ItemStack;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import javax.annotation.Nullable;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.EntityType;
import net.minecraft.util.IntRange;
import net.minecraft.world.entity.LivingEntity;
import java.util.function.Predicate;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Optional;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import java.util.UUID;
import net.minecraft.world.entity.NeutralMob;

public class EnderMan extends Monster implements NeutralMob {
    private static final UUID SPEED_MODIFIER_ATTACKING_UUID;
    private static final AttributeModifier SPEED_MODIFIER_ATTACKING;
    private static final EntityDataAccessor<Optional<BlockState>> DATA_CARRY_STATE;
    private static final EntityDataAccessor<Boolean> DATA_CREEPY;
    private static final EntityDataAccessor<Boolean> DATA_STARED_AT;
    private static final Predicate<LivingEntity> ENDERMITE_SELECTOR;
    private int lastStareSound;
    private int targetChangeTime;
    private static final IntRange PERSISTENT_ANGER_TIME;
    private int remainingPersistentAngerTime;
    private UUID persistentAngerTarget;
    
    public EnderMan(final EntityType<? extends EnderMan> aoq, final Level bqb) {
        super(aoq, bqb);
        this.lastStareSound = Integer.MIN_VALUE;
        this.maxUpStep = 1.0f;
        this.setPathfindingMalus(BlockPathTypes.WATER, -1.0f);
    }
    
    @Override
    protected void registerGoals() {
        this.goalSelector.addGoal(0, new FloatGoal(this));
        this.goalSelector.addGoal(1, new EndermanFreezeWhenLookedAt(this));
        this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false));
        this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0f));
        this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0f));
        this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
        this.goalSelector.addGoal(10, new EndermanLeaveBlockGoal(this));
        this.goalSelector.addGoal(11, new EndermanTakeBlockGoal(this));
        this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
        this.targetSelector.addGoal(2, new EndermanLookForPlayerGoal(this));
        this.targetSelector.addGoal(3, new HurtByTargetGoal(this, new Class[0]));
        this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, EnderMan.ENDERMITE_SELECTOR));
        this.targetSelector.addGoal(5, new ResetUniversalAngerTargetGoal<>(this, false));
    }
    
    public static AttributeSupplier.Builder createAttributes() {
        return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 40.0).add(Attributes.MOVEMENT_SPEED, 0.30000001192092896).add(Attributes.ATTACK_DAMAGE, 7.0).add(Attributes.FOLLOW_RANGE, 64.0);
    }
    
    @Override
    public void setTarget(@Nullable final LivingEntity aoy) {
        super.setTarget(aoy);
        final AttributeInstance apt3 = this.getAttribute(Attributes.MOVEMENT_SPEED);
        if (aoy == null) {
            this.targetChangeTime = 0;
            this.entityData.<Boolean>set(EnderMan.DATA_CREEPY, false);
            this.entityData.<Boolean>set(EnderMan.DATA_STARED_AT, false);
            apt3.removeModifier(EnderMan.SPEED_MODIFIER_ATTACKING);
        }
        else {
            this.targetChangeTime = this.tickCount;
            this.entityData.<Boolean>set(EnderMan.DATA_CREEPY, true);
            if (!apt3.hasModifier(EnderMan.SPEED_MODIFIER_ATTACKING)) {
                apt3.addTransientModifier(EnderMan.SPEED_MODIFIER_ATTACKING);
            }
        }
    }
    
    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.<Optional<BlockState>>define(EnderMan.DATA_CARRY_STATE, Optional.<BlockState>empty());
        this.entityData.<Boolean>define(EnderMan.DATA_CREEPY, false);
        this.entityData.<Boolean>define(EnderMan.DATA_STARED_AT, false);
    }
    
    @Override
    public void startPersistentAngerTimer() {
        this.setRemainingPersistentAngerTime(EnderMan.PERSISTENT_ANGER_TIME.randomValue(this.random));
    }
    
    @Override
    public void setRemainingPersistentAngerTime(final int integer) {
        this.remainingPersistentAngerTime = integer;
    }
    
    @Override
    public int getRemainingPersistentAngerTime() {
        return this.remainingPersistentAngerTime;
    }
    
    @Override
    public void setPersistentAngerTarget(@Nullable final UUID uUID) {
        this.persistentAngerTarget = uUID;
    }
    
    @Override
    public UUID getPersistentAngerTarget() {
        return this.persistentAngerTarget;
    }
    
    public void playStareSound() {
        if (this.tickCount >= this.lastStareSound + 400) {
            this.lastStareSound = this.tickCount;
            if (!this.isSilent()) {
                this.level.playLocalSound(this.getX(), this.getEyeY(), this.getZ(), SoundEvents.ENDERMAN_STARE, this.getSoundSource(), 2.5f, 1.0f, false);
            }
        }
    }
    
    @Override
    public void onSyncedDataUpdated(final EntityDataAccessor<?> tq) {
        if (EnderMan.DATA_CREEPY.equals(tq) && this.hasBeenStaredAt() && this.level.isClientSide) {
            this.playStareSound();
        }
        super.onSyncedDataUpdated(tq);
    }
    
    @Override
    public void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        final BlockState cfj3 = this.getCarriedBlock();
        if (cfj3 != null) {
            le.put("carriedBlockState", NbtUtils.writeBlockState(cfj3));
        }
        this.addPersistentAngerSaveData(le);
    }
    
    @Override
    public void readAdditionalSaveData(final CompoundTag le) {
        super.readAdditionalSaveData(le);
        BlockState cfj3 = null;
        if (le.contains("carriedBlockState", 10)) {
            cfj3 = NbtUtils.readBlockState(le.getCompound("carriedBlockState"));
            if (cfj3.isAir()) {
                cfj3 = null;
            }
        }
        this.setCarriedBlock(cfj3);
        this.readPersistentAngerSaveData((ServerLevel)this.level, le);
    }
    
    private boolean isLookingAtMe(final Player bec) {
        final ItemStack bki3 = bec.inventory.armor.get(3);
        if (bki3.getItem() == Blocks.CARVED_PUMPKIN.asItem()) {
            return false;
        }
        final Vec3 dem4 = bec.getViewVector(1.0f).normalize();
        Vec3 dem5 = new Vec3(this.getX() - bec.getX(), this.getEyeY() - bec.getEyeY(), this.getZ() - bec.getZ());
        final double double6 = dem5.length();
        dem5 = dem5.normalize();
        final double double7 = dem4.dot(dem5);
        return double7 > 1.0 - 0.025 / double6 && bec.canSee(this);
    }
    
    @Override
    protected float getStandingEyeHeight(final Pose apj, final EntityDimensions aon) {
        return 2.55f;
    }
    
    @Override
    public void aiStep() {
        if (this.level.isClientSide) {
            for (int integer2 = 0; integer2 < 2; ++integer2) {
                this.level.addParticle(ParticleTypes.PORTAL, this.getRandomX(0.5), this.getRandomY() - 0.25, this.getRandomZ(0.5), (this.random.nextDouble() - 0.5) * 2.0, -this.random.nextDouble(), (this.random.nextDouble() - 0.5) * 2.0);
            }
        }
        this.jumping = false;
        if (!this.level.isClientSide) {
            this.updatePersistentAnger((ServerLevel)this.level, true);
        }
        super.aiStep();
    }
    
    @Override
    public boolean isSensitiveToWater() {
        return true;
    }
    
    @Override
    protected void customServerAiStep() {
        if (this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) {
            final float float2 = this.getBrightness();
            if (float2 > 0.5f && this.level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0f < (float2 - 0.4f) * 2.0f) {
                this.setTarget(null);
                this.teleport();
            }
        }
        super.customServerAiStep();
    }
    
    protected boolean teleport() {
        if (this.level.isClientSide() || !this.isAlive()) {
            return false;
        }
        final double double2 = this.getX() + (this.random.nextDouble() - 0.5) * 64.0;
        final double double3 = this.getY() + (this.random.nextInt(64) - 32);
        final double double4 = this.getZ() + (this.random.nextDouble() - 0.5) * 64.0;
        return this.teleport(double2, double3, double4);
    }
    
    private boolean teleportTowards(final Entity aom) {
        Vec3 dem3 = new Vec3(this.getX() - aom.getX(), this.getY(0.5) - aom.getEyeY(), this.getZ() - aom.getZ());
        dem3 = dem3.normalize();
        final double double4 = 16.0;
        final double double5 = this.getX() + (this.random.nextDouble() - 0.5) * 8.0 - dem3.x * 16.0;
        final double double6 = this.getY() + (this.random.nextInt(16) - 8) - dem3.y * 16.0;
        final double double7 = this.getZ() + (this.random.nextDouble() - 0.5) * 8.0 - dem3.z * 16.0;
        return this.teleport(double5, double6, double7);
    }
    
    private boolean teleport(final double double1, final double double2, final double double3) {
        final BlockPos.MutableBlockPos a8 = new BlockPos.MutableBlockPos(double1, double2, double3);
        while (a8.getY() > 0 && !this.level.getBlockState(a8).getMaterial().blocksMotion()) {
            a8.move(Direction.DOWN);
        }
        final BlockState cfj9 = this.level.getBlockState(a8);
        final boolean boolean10 = cfj9.getMaterial().blocksMotion();
        final boolean boolean11 = cfj9.getFluidState().is(FluidTags.WATER);
        if (!boolean10 || boolean11) {
            return false;
        }
        final boolean boolean12 = this.randomTeleport(double1, double2, double3, true);
        if (boolean12 && !this.isSilent()) {
            this.level.playSound(null, this.xo, this.yo, this.zo, SoundEvents.ENDERMAN_TELEPORT, this.getSoundSource(), 1.0f, 1.0f);
            this.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0f, 1.0f);
        }
        return boolean12;
    }
    
    @Override
    protected SoundEvent getAmbientSound() {
        return this.isCreepy() ? SoundEvents.ENDERMAN_SCREAM : SoundEvents.ENDERMAN_AMBIENT;
    }
    
    @Override
    protected SoundEvent getHurtSound(final DamageSource anw) {
        return SoundEvents.ENDERMAN_HURT;
    }
    
    @Override
    protected SoundEvent getDeathSound() {
        return SoundEvents.ENDERMAN_DEATH;
    }
    
    @Override
    protected void dropCustomDeathLoot(final DamageSource anw, final int integer, final boolean boolean3) {
        super.dropCustomDeathLoot(anw, integer, boolean3);
        final BlockState cfj5 = this.getCarriedBlock();
        if (cfj5 != null) {
            this.spawnAtLocation(cfj5.getBlock());
        }
    }
    
    public void setCarriedBlock(@Nullable final BlockState cfj) {
        this.entityData.<Optional<BlockState>>set(EnderMan.DATA_CARRY_STATE, Optional.<BlockState>ofNullable(cfj));
    }
    
    @Nullable
    public BlockState getCarriedBlock() {
        return this.entityData.<Optional<BlockState>>get(EnderMan.DATA_CARRY_STATE).orElse(null);
    }
    
    @Override
    public boolean hurt(final DamageSource anw, final float float2) {
        if (this.isInvulnerableTo(anw)) {
            return false;
        }
        if (anw instanceof IndirectEntityDamageSource) {
            for (int integer4 = 0; integer4 < 64; ++integer4) {
                if (this.teleport()) {
                    return true;
                }
            }
            return false;
        }
        final boolean boolean4 = super.hurt(anw, float2);
        if (!this.level.isClientSide() && this.random.nextInt(10) != 0) {
            this.teleport();
        }
        return boolean4;
    }
    
    public boolean isCreepy() {
        return this.entityData.<Boolean>get(EnderMan.DATA_CREEPY);
    }
    
    public boolean hasBeenStaredAt() {
        return this.entityData.<Boolean>get(EnderMan.DATA_STARED_AT);
    }
    
    public void setBeingStaredAt() {
        this.entityData.<Boolean>set(EnderMan.DATA_STARED_AT, true);
    }
    
    @Override
    public boolean requiresCustomPersistence() {
        return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
    }
    
    static {
        SPEED_MODIFIER_ATTACKING_UUID = UUID.fromString("020E0DFB-87AE-4653-9556-831010E291A0");
        SPEED_MODIFIER_ATTACKING = new AttributeModifier(EnderMan.SPEED_MODIFIER_ATTACKING_UUID, "Attacking speed boost", 0.15000000596046448, AttributeModifier.Operation.ADDITION);
        DATA_CARRY_STATE = SynchedEntityData.<Optional<BlockState>>defineId(EnderMan.class, EntityDataSerializers.BLOCK_STATE);
        DATA_CREEPY = SynchedEntityData.<Boolean>defineId(EnderMan.class, EntityDataSerializers.BOOLEAN);
        DATA_STARED_AT = SynchedEntityData.<Boolean>defineId(EnderMan.class, EntityDataSerializers.BOOLEAN);
        ENDERMITE_SELECTOR = (aoy -> aoy instanceof Endermite && aoy.isPlayerSpawned());
        PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
    }
    
    static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal<Player> {
        private final EnderMan enderman;
        private Player pendingTarget;
        private int aggroTime;
        private int teleportTime;
        private final TargetingConditions startAggroTargetConditions;
        private final TargetingConditions continueAggroTargetConditions;
        
        public EndermanLookForPlayerGoal(final EnderMan bbr) {
            super(bbr, Player.class, false);
            this.continueAggroTargetConditions = new TargetingConditions().allowUnseeable();
            this.enderman = bbr;
            this.startAggroTargetConditions = new TargetingConditions().range(this.getFollowDistance()).selector(aoy -> bbr.isLookingAtMe(aoy));
        }
        
        @Override
        public boolean canUse() {
            this.pendingTarget = this.enderman.level.getNearestPlayer(this.startAggroTargetConditions, this.enderman);
            return this.pendingTarget != null;
        }
        
        @Override
        public void start() {
            this.aggroTime = 5;
            this.teleportTime = 0;
            this.enderman.setBeingStaredAt();
        }
        
        @Override
        public void stop() {
            this.pendingTarget = null;
            super.stop();
        }
        
        @Override
        public boolean canContinueToUse() {
            if (this.pendingTarget == null) {
                return (this.target != null && this.continueAggroTargetConditions.test(this.enderman, this.target)) || super.canContinueToUse();
            }
            if (!this.enderman.isLookingAtMe(this.pendingTarget)) {
                return false;
            }
            this.enderman.lookAt(this.pendingTarget, 10.0f, 10.0f);
            return true;
        }
        
        @Override
        public void tick() {
            if (this.enderman.getTarget() == null) {
                super.setTarget(null);
            }
            if (this.pendingTarget != null) {
                if (--this.aggroTime <= 0) {
                    this.target = this.pendingTarget;
                    this.pendingTarget = null;
                    super.start();
                }
            }
            else {
                if (this.target != null && !this.enderman.isPassenger()) {
                    if (this.enderman.isLookingAtMe((Player)this.target)) {
                        if (this.target.distanceToSqr(this.enderman) < 16.0) {
                            this.enderman.teleport();
                        }
                        this.teleportTime = 0;
                    }
                    else if (this.target.distanceToSqr(this.enderman) > 256.0 && this.teleportTime++ >= 30 && this.enderman.teleportTowards(this.target)) {
                        this.teleportTime = 0;
                    }
                }
                super.tick();
            }
        }
    }
    
    static class EndermanFreezeWhenLookedAt extends Goal {
        private final EnderMan enderman;
        private LivingEntity target;
        
        public EndermanFreezeWhenLookedAt(final EnderMan bbr) {
            this.enderman = bbr;
            this.setFlags(EnumSet.<Flag>of(Flag.JUMP, Flag.MOVE));
        }
        
        @Override
        public boolean canUse() {
            this.target = this.enderman.getTarget();
            if (!(this.target instanceof Player)) {
                return false;
            }
            final double double2 = this.target.distanceToSqr(this.enderman);
            return double2 <= 256.0 && this.enderman.isLookingAtMe((Player)this.target);
        }
        
        @Override
        public void start() {
            this.enderman.getNavigation().stop();
        }
        
        @Override
        public void tick() {
            this.enderman.getLookControl().setLookAt(this.target.getX(), this.target.getEyeY(), this.target.getZ());
        }
    }
    
    static class EndermanLeaveBlockGoal extends Goal {
        private final EnderMan enderman;
        
        public EndermanLeaveBlockGoal(final EnderMan bbr) {
            this.enderman = bbr;
        }
        
        @Override
        public boolean canUse() {
            return this.enderman.getCarriedBlock() != null && this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.enderman.getRandom().nextInt(2000) == 0;
        }
        
        @Override
        public void tick() {
            final Random random2 = this.enderman.getRandom();
            final LevelAccessor bqc3 = this.enderman.level;
            final int integer4 = Mth.floor(this.enderman.getX() - 1.0 + random2.nextDouble() * 2.0);
            final int integer5 = Mth.floor(this.enderman.getY() + random2.nextDouble() * 2.0);
            final int integer6 = Mth.floor(this.enderman.getZ() - 1.0 + random2.nextDouble() * 2.0);
            final BlockPos fu7 = new BlockPos(integer4, integer5, integer6);
            final BlockState cfj8 = bqc3.getBlockState(fu7);
            final BlockPos fu8 = fu7.below();
            final BlockState cfj9 = bqc3.getBlockState(fu8);
            final BlockState cfj10 = this.enderman.getCarriedBlock();
            if (cfj10 != null && this.canPlaceBlock(bqc3, fu7, cfj10, cfj8, cfj9, fu8)) {
                bqc3.setBlock(fu7, cfj10, 3);
                this.enderman.setCarriedBlock(null);
            }
        }
        
        private boolean canPlaceBlock(final LevelReader bqd, final BlockPos fu2, final BlockState cfj3, final BlockState cfj4, final BlockState cfj5, final BlockPos fu6) {
            return cfj4.isAir() && !cfj5.isAir() && cfj5.isCollisionShapeFullBlock(bqd, fu6) && cfj3.canSurvive(bqd, fu2);
        }
    }
    
    static class EndermanTakeBlockGoal extends Goal {
        private final EnderMan enderman;
        
        public EndermanTakeBlockGoal(final EnderMan bbr) {
            this.enderman = bbr;
        }
        
        @Override
        public boolean canUse() {
            return this.enderman.getCarriedBlock() == null && this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.enderman.getRandom().nextInt(20) == 0;
        }
        
        @Override
        public void tick() {
            final Random random2 = this.enderman.getRandom();
            final Level bqb3 = this.enderman.level;
            final int integer4 = Mth.floor(this.enderman.getX() - 2.0 + random2.nextDouble() * 4.0);
            final int integer5 = Mth.floor(this.enderman.getY() + random2.nextDouble() * 3.0);
            final int integer6 = Mth.floor(this.enderman.getZ() - 2.0 + random2.nextDouble() * 4.0);
            final BlockPos fu7 = new BlockPos(integer4, integer5, integer6);
            final BlockState cfj8 = bqb3.getBlockState(fu7);
            final Block bvr9 = cfj8.getBlock();
            final Vec3 dem10 = new Vec3(Mth.floor(this.enderman.getX()) + 0.5, integer5 + 0.5, Mth.floor(this.enderman.getZ()) + 0.5);
            final Vec3 dem11 = new Vec3(integer4 + 0.5, integer5 + 0.5, integer6 + 0.5);
            final BlockHitResult deh12 = bqb3.clip(new ClipContext(dem10, dem11, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
            final boolean boolean13 = deh12.getBlockPos().equals(fu7);
            if (bvr9.is(BlockTags.ENDERMAN_HOLDABLE) && boolean13) {
                this.enderman.setCarriedBlock(cfj8);
                bqb3.removeBlock(fu7, false);
            }
        }
    }
}
