package net.minecraft.world.entity.monster;

import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.projectile.ShulkerBullet;
import net.minecraft.world.Difficulty;
import java.util.EnumSet;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.EntityDataSerializers;
import java.util.Random;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.ShulkerSharedHelper;
import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.List;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.piston.PistonHeadBlock;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.piston.PistonBaseBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.ai.control.BodyRotationControl;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.EntityType;
import net.minecraft.core.BlockPos;
import java.util.Optional;
import net.minecraft.core.Direction;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import java.util.UUID;
import net.minecraft.world.entity.animal.AbstractGolem;

public class Shulker extends AbstractGolem implements Enemy {
    private static final UUID COVERED_ARMOR_MODIFIER_UUID;
    private static final AttributeModifier COVERED_ARMOR_MODIFIER;
    protected static final EntityDataAccessor<Direction> DATA_ATTACH_FACE_ID;
    protected static final EntityDataAccessor<Optional<BlockPos>> DATA_ATTACH_POS_ID;
    protected static final EntityDataAccessor<Byte> DATA_PEEK_ID;
    protected static final EntityDataAccessor<Byte> DATA_COLOR_ID;
    private float currentPeekAmountO;
    private float currentPeekAmount;
    private BlockPos oldAttachPosition;
    private int clientSideTeleportInterpolation;
    
    public Shulker(final EntityType<? extends Shulker> aoq, final Level bqb) {
        super(aoq, bqb);
        this.oldAttachPosition = null;
        this.xpReward = 5;
    }
    
    @Override
    protected void registerGoals() {
        this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0f));
        this.goalSelector.addGoal(4, new ShulkerAttackGoal());
        this.goalSelector.addGoal(7, new ShulkerPeekGoal());
        this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
        this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]).setAlertOthers(new Class[0]));
        this.targetSelector.addGoal(2, new ShulkerNearestAttackGoal(this));
        this.targetSelector.addGoal(3, new ShulkerDefenseAttackGoal(this));
    }
    
    @Override
    protected boolean isMovementNoisy() {
        return false;
    }
    
    @Override
    public SoundSource getSoundSource() {
        return SoundSource.HOSTILE;
    }
    
    @Override
    protected SoundEvent getAmbientSound() {
        return SoundEvents.SHULKER_AMBIENT;
    }
    
    @Override
    public void playAmbientSound() {
        if (!this.isClosed()) {
            super.playAmbientSound();
        }
    }
    
    @Override
    protected SoundEvent getDeathSound() {
        return SoundEvents.SHULKER_DEATH;
    }
    
    @Override
    protected SoundEvent getHurtSound(final DamageSource anw) {
        if (this.isClosed()) {
            return SoundEvents.SHULKER_HURT_CLOSED;
        }
        return SoundEvents.SHULKER_HURT;
    }
    
    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.<Direction>define(Shulker.DATA_ATTACH_FACE_ID, Direction.DOWN);
        this.entityData.<Optional<BlockPos>>define(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>empty());
        this.entityData.<Byte>define(Shulker.DATA_PEEK_ID, (Byte)0);
        this.entityData.<Byte>define(Shulker.DATA_COLOR_ID, (Byte)16);
    }
    
    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 30.0);
    }
    
    @Override
    protected BodyRotationControl createBodyControl() {
        return new ShulkerBodyRotationControl(this);
    }
    
    @Override
    public void readAdditionalSaveData(final CompoundTag le) {
        super.readAdditionalSaveData(le);
        this.entityData.<Direction>set(Shulker.DATA_ATTACH_FACE_ID, Direction.from3DDataValue(le.getByte("AttachFace")));
        this.entityData.<Byte>set(Shulker.DATA_PEEK_ID, le.getByte("Peek"));
        this.entityData.<Byte>set(Shulker.DATA_COLOR_ID, le.getByte("Color"));
        if (le.contains("APX")) {
            final int integer3 = le.getInt("APX");
            final int integer4 = le.getInt("APY");
            final int integer5 = le.getInt("APZ");
            this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>of(new BlockPos(integer3, integer4, integer5)));
        }
        else {
            this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>empty());
        }
    }
    
    @Override
    public void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        le.putByte("AttachFace", (byte)this.entityData.<Direction>get(Shulker.DATA_ATTACH_FACE_ID).get3DDataValue());
        le.putByte("Peek", this.entityData.<Byte>get(Shulker.DATA_PEEK_ID));
        le.putByte("Color", this.entityData.<Byte>get(Shulker.DATA_COLOR_ID));
        final BlockPos fu3 = this.getAttachPosition();
        if (fu3 != null) {
            le.putInt("APX", fu3.getX());
            le.putInt("APY", fu3.getY());
            le.putInt("APZ", fu3.getZ());
        }
    }
    
    @Override
    public void tick() {
        super.tick();
        BlockPos fu2 = this.entityData.<Optional<BlockPos>>get(Shulker.DATA_ATTACH_POS_ID).orElse(null);
        if (fu2 == null && !this.level.isClientSide) {
            fu2 = this.blockPosition();
            this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>of(fu2));
        }
        if (this.isPassenger()) {
            fu2 = null;
            final float float3 = this.getVehicle().yRot;
            this.yRot = float3;
            this.yBodyRot = float3;
            this.yBodyRotO = float3;
            this.clientSideTeleportInterpolation = 0;
        }
        else if (!this.level.isClientSide) {
            final BlockState cfj3 = this.level.getBlockState(fu2);
            if (!cfj3.isAir()) {
                if (cfj3.is(Blocks.MOVING_PISTON)) {
                    final Direction fz4 = cfj3.<Direction>getValue((Property<Direction>)PistonBaseBlock.FACING);
                    if (this.level.isEmptyBlock(fu2.relative(fz4))) {
                        fu2 = fu2.relative(fz4);
                        this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>of(fu2));
                    }
                    else {
                        this.teleportSomewhere();
                    }
                }
                else if (cfj3.is(Blocks.PISTON_HEAD)) {
                    final Direction fz4 = cfj3.<Direction>getValue((Property<Direction>)PistonHeadBlock.FACING);
                    if (this.level.isEmptyBlock(fu2.relative(fz4))) {
                        fu2 = fu2.relative(fz4);
                        this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>of(fu2));
                    }
                    else {
                        this.teleportSomewhere();
                    }
                }
                else {
                    this.teleportSomewhere();
                }
            }
            final Direction fz4 = this.getAttachFace();
            if (!this.canAttachOnBlockFace(fu2, fz4)) {
                final Direction fz5 = this.findAttachableFace(fu2);
                if (fz5 != null) {
                    this.entityData.<Direction>set(Shulker.DATA_ATTACH_FACE_ID, fz5);
                }
                else {
                    this.teleportSomewhere();
                }
            }
        }
        final float float3 = this.getRawPeekAmount() * 0.01f;
        this.currentPeekAmountO = this.currentPeekAmount;
        if (this.currentPeekAmount > float3) {
            this.currentPeekAmount = Mth.clamp(this.currentPeekAmount - 0.05f, float3, 1.0f);
        }
        else if (this.currentPeekAmount < float3) {
            this.currentPeekAmount = Mth.clamp(this.currentPeekAmount + 0.05f, 0.0f, float3);
        }
        if (fu2 != null) {
            if (this.level.isClientSide) {
                if (this.clientSideTeleportInterpolation > 0 && this.oldAttachPosition != null) {
                    --this.clientSideTeleportInterpolation;
                }
                else {
                    this.oldAttachPosition = fu2;
                }
            }
            this.setPosAndOldPos(fu2.getX() + 0.5, fu2.getY(), fu2.getZ() + 0.5);
            final double double4 = 0.5 - Mth.sin((0.5f + this.currentPeekAmount) * 3.1415927f) * 0.5;
            final double double5 = 0.5 - Mth.sin((0.5f + this.currentPeekAmountO) * 3.1415927f) * 0.5;
            final Direction fz6 = this.getAttachFace().getOpposite();
            this.setBoundingBox(new AABB(this.getX() - 0.5, this.getY(), this.getZ() - 0.5, this.getX() + 0.5, this.getY() + 1.0, this.getZ() + 0.5).expandTowards(fz6.getStepX() * double4, fz6.getStepY() * double4, fz6.getStepZ() * double4));
            final double double6 = double4 - double5;
            if (double6 > 0.0) {
                final List<Entity> list11 = this.level.getEntities(this, this.getBoundingBox());
                if (!list11.isEmpty()) {
                    for (final Entity aom13 : list11) {
                        if (!(aom13 instanceof Shulker) && !aom13.noPhysics) {
                            aom13.move(MoverType.SHULKER, new Vec3(double6 * fz6.getStepX(), double6 * fz6.getStepY(), double6 * fz6.getStepZ()));
                        }
                    }
                }
            }
        }
    }
    
    @Override
    public void move(final MoverType apd, final Vec3 dem) {
        if (apd == MoverType.SHULKER_BOX) {
            this.teleportSomewhere();
        }
        else {
            super.move(apd, dem);
        }
    }
    
    @Override
    public void setPos(final double double1, final double double2, final double double3) {
        super.setPos(double1, double2, double3);
        if (this.entityData == null || this.tickCount == 0) {
            return;
        }
        final Optional<BlockPos> optional8 = this.entityData.<Optional<BlockPos>>get(Shulker.DATA_ATTACH_POS_ID);
        final Optional<BlockPos> optional9 = Optional.<BlockPos>of(new BlockPos(double1, double2, double3));
        if (!optional9.equals(optional8)) {
            this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, optional9);
            this.entityData.<Byte>set(Shulker.DATA_PEEK_ID, (Byte)0);
            this.hasImpulse = true;
        }
    }
    
    @Nullable
    protected Direction findAttachableFace(final BlockPos fu) {
        for (final Direction fz6 : Direction.values()) {
            if (this.canAttachOnBlockFace(fu, fz6)) {
                return fz6;
            }
        }
        return null;
    }
    
    private boolean canAttachOnBlockFace(final BlockPos fu, final Direction fz) {
        return this.level.loadedAndEntityCanStandOnFace(fu.relative(fz), this, fz.getOpposite()) && this.level.noCollision(this, ShulkerSharedHelper.openBoundingBox(fu, fz.getOpposite()));
    }
    
    protected boolean teleportSomewhere() {
        if (this.isNoAi() || !this.isAlive()) {
            return true;
        }
        final BlockPos fu2 = this.blockPosition();
        for (int integer3 = 0; integer3 < 5; ++integer3) {
            final BlockPos fu3 = fu2.offset(8 - this.random.nextInt(17), 8 - this.random.nextInt(17), 8 - this.random.nextInt(17));
            if (fu3.getY() > 0 && this.level.isEmptyBlock(fu3) && this.level.getWorldBorder().isWithinBounds(fu3) && this.level.noCollision(this, new AABB(fu3))) {
                final Direction fz5 = this.findAttachableFace(fu3);
                if (fz5 != null) {
                    this.entityData.<Direction>set(Shulker.DATA_ATTACH_FACE_ID, fz5);
                    this.playSound(SoundEvents.SHULKER_TELEPORT, 1.0f, 1.0f);
                    this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>of(fu3));
                    this.entityData.<Byte>set(Shulker.DATA_PEEK_ID, (Byte)0);
                    this.setTarget(null);
                    return true;
                }
            }
        }
        return false;
    }
    
    @Override
    public void aiStep() {
        super.aiStep();
        this.setDeltaMovement(Vec3.ZERO);
        if (!this.isNoAi()) {
            this.yBodyRotO = 0.0f;
            this.yBodyRot = 0.0f;
        }
    }
    
    @Override
    public void onSyncedDataUpdated(final EntityDataAccessor<?> tq) {
        if (Shulker.DATA_ATTACH_POS_ID.equals(tq) && this.level.isClientSide && !this.isPassenger()) {
            final BlockPos fu3 = this.getAttachPosition();
            if (fu3 != null) {
                if (this.oldAttachPosition == null) {
                    this.oldAttachPosition = fu3;
                }
                else {
                    this.clientSideTeleportInterpolation = 6;
                }
                this.setPosAndOldPos(fu3.getX() + 0.5, fu3.getY(), fu3.getZ() + 0.5);
            }
        }
        super.onSyncedDataUpdated(tq);
    }
    
    @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.lerpSteps = 0;
    }
    
    @Override
    public boolean hurt(final DamageSource anw, final float float2) {
        if (this.isClosed()) {
            final Entity aom4 = anw.getDirectEntity();
            if (aom4 instanceof AbstractArrow) {
                return false;
            }
        }
        if (super.hurt(anw, float2)) {
            if (this.getHealth() < this.getMaxHealth() * 0.5 && this.random.nextInt(4) == 0) {
                this.teleportSomewhere();
            }
            return true;
        }
        return false;
    }
    
    private boolean isClosed() {
        return this.getRawPeekAmount() == 0;
    }
    
    @Nullable
    @Override
    public AABB getCollideBox() {
        return this.isAlive() ? this.getBoundingBox() : null;
    }
    
    public Direction getAttachFace() {
        return this.entityData.<Direction>get(Shulker.DATA_ATTACH_FACE_ID);
    }
    
    @Nullable
    public BlockPos getAttachPosition() {
        return this.entityData.<Optional<BlockPos>>get(Shulker.DATA_ATTACH_POS_ID).orElse(null);
    }
    
    public void setAttachPosition(@Nullable final BlockPos fu) {
        this.entityData.<Optional<BlockPos>>set(Shulker.DATA_ATTACH_POS_ID, Optional.<BlockPos>ofNullable(fu));
    }
    
    public int getRawPeekAmount() {
        return this.entityData.<Byte>get(Shulker.DATA_PEEK_ID);
    }
    
    public void setRawPeekAmount(final int integer) {
        if (!this.level.isClientSide) {
            this.getAttribute(Attributes.ARMOR).removeModifier(Shulker.COVERED_ARMOR_MODIFIER);
            if (integer == 0) {
                this.getAttribute(Attributes.ARMOR).addPermanentModifier(Shulker.COVERED_ARMOR_MODIFIER);
                this.playSound(SoundEvents.SHULKER_CLOSE, 1.0f, 1.0f);
            }
            else {
                this.playSound(SoundEvents.SHULKER_OPEN, 1.0f, 1.0f);
            }
        }
        this.entityData.<Byte>set(Shulker.DATA_PEEK_ID, (byte)integer);
    }
    
    public float getClientPeekAmount(final float float1) {
        return Mth.lerp(float1, this.currentPeekAmountO, this.currentPeekAmount);
    }
    
    public int getClientSideTeleportInterpolation() {
        return this.clientSideTeleportInterpolation;
    }
    
    public BlockPos getOldAttachPosition() {
        return this.oldAttachPosition;
    }
    
    @Override
    protected float getStandingEyeHeight(final Pose apj, final EntityDimensions aon) {
        return 0.5f;
    }
    
    @Override
    public int getMaxHeadXRot() {
        return 180;
    }
    
    @Override
    public int getMaxHeadYRot() {
        return 180;
    }
    
    @Override
    public void push(final Entity aom) {
    }
    
    @Override
    public float getPickRadius() {
        return 0.0f;
    }
    
    public boolean hasValidInterpolationPositions() {
        return this.oldAttachPosition != null && this.getAttachPosition() != null;
    }
    
    @Nullable
    public DyeColor getColor() {
        final Byte byte2 = this.entityData.<Byte>get(Shulker.DATA_COLOR_ID);
        if (byte2 == 16 || byte2 > 15) {
            return null;
        }
        return DyeColor.byId(byte2);
    }
    
    static {
        COVERED_ARMOR_MODIFIER_UUID = UUID.fromString("7E0292F2-9434-48D5-A29F-9583AF7DF27F");
        COVERED_ARMOR_MODIFIER = new AttributeModifier(Shulker.COVERED_ARMOR_MODIFIER_UUID, "Covered armor bonus", 20.0, AttributeModifier.Operation.ADDITION);
        DATA_ATTACH_FACE_ID = SynchedEntityData.<Direction>defineId(Shulker.class, EntityDataSerializers.DIRECTION);
        DATA_ATTACH_POS_ID = SynchedEntityData.<Optional<BlockPos>>defineId(Shulker.class, EntityDataSerializers.OPTIONAL_BLOCK_POS);
        DATA_PEEK_ID = SynchedEntityData.<Byte>defineId(Shulker.class, EntityDataSerializers.BYTE);
        DATA_COLOR_ID = SynchedEntityData.<Byte>defineId(Shulker.class, EntityDataSerializers.BYTE);
    }
    
    class ShulkerBodyRotationControl extends BodyRotationControl {
        public ShulkerBodyRotationControl(final Mob aoz) {
            super(aoz);
        }
        
        @Override
        public void clientTick() {
        }
    }
    
    class ShulkerPeekGoal extends Goal {
        private int peekTime;
        
        private ShulkerPeekGoal() {
        }
        
        @Override
        public boolean canUse() {
            return Shulker.this.getTarget() == null && Shulker.this.random.nextInt(40) == 0;
        }
        
        @Override
        public boolean canContinueToUse() {
            return Shulker.this.getTarget() == null && this.peekTime > 0;
        }
        
        @Override
        public void start() {
            this.peekTime = 20 * (1 + Shulker.this.random.nextInt(3));
            Shulker.this.setRawPeekAmount(30);
        }
        
        @Override
        public void stop() {
            if (Shulker.this.getTarget() == null) {
                Shulker.this.setRawPeekAmount(0);
            }
        }
        
        @Override
        public void tick() {
            --this.peekTime;
        }
    }
    
    class ShulkerAttackGoal extends Goal {
        private int attackTime;
        
        public ShulkerAttackGoal() {
            this.setFlags(EnumSet.<Flag>of(Flag.MOVE, Flag.LOOK));
        }
        
        @Override
        public boolean canUse() {
            final LivingEntity aoy2 = Shulker.this.getTarget();
            return aoy2 != null && aoy2.isAlive() && Shulker.this.level.getDifficulty() != Difficulty.PEACEFUL;
        }
        
        @Override
        public void start() {
            this.attackTime = 20;
            Shulker.this.setRawPeekAmount(100);
        }
        
        @Override
        public void stop() {
            Shulker.this.setRawPeekAmount(0);
        }
        
        @Override
        public void tick() {
            if (Shulker.this.level.getDifficulty() == Difficulty.PEACEFUL) {
                return;
            }
            --this.attackTime;
            final LivingEntity aoy2 = Shulker.this.getTarget();
            Shulker.this.getLookControl().setLookAt(aoy2, 180.0f, 180.0f);
            final double double3 = Shulker.this.distanceToSqr(aoy2);
            if (double3 < 400.0) {
                if (this.attackTime <= 0) {
                    this.attackTime = 20 + Shulker.this.random.nextInt(10) * 20 / 2;
                    Shulker.this.level.addFreshEntity(new ShulkerBullet(Shulker.this.level, Shulker.this, aoy2, Shulker.this.getAttachFace().getAxis()));
                    Shulker.this.playSound(SoundEvents.SHULKER_SHOOT, 2.0f, (Shulker.this.random.nextFloat() - Shulker.this.random.nextFloat()) * 0.2f + 1.0f);
                }
            }
            else {
                Shulker.this.setTarget(null);
            }
            super.tick();
        }
    }
    
    class ShulkerNearestAttackGoal extends NearestAttackableTargetGoal<Player> {
        public ShulkerNearestAttackGoal(final Shulker bch2) {
            super(bch2, Player.class, true);
        }
        
        @Override
        public boolean canUse() {
            return Shulker.this.level.getDifficulty() != Difficulty.PEACEFUL && super.canUse();
        }
        
        @Override
        protected AABB getTargetSearchArea(final double double1) {
            final Direction fz4 = ((Shulker)this.mob).getAttachFace();
            if (fz4.getAxis() == Direction.Axis.X) {
                return this.mob.getBoundingBox().inflate(4.0, double1, double1);
            }
            if (fz4.getAxis() == Direction.Axis.Z) {
                return this.mob.getBoundingBox().inflate(double1, double1, 4.0);
            }
            return this.mob.getBoundingBox().inflate(double1, 4.0, double1);
        }
    }
    
    static class ShulkerDefenseAttackGoal extends NearestAttackableTargetGoal<LivingEntity> {
        public ShulkerDefenseAttackGoal(final Shulker bch) {
            super(bch, LivingEntity.class, 10, true, false, aoy -> aoy instanceof Enemy);
        }
        
        @Override
        public boolean canUse() {
            return this.mob.getTeam() != null && super.canUse();
        }
        
        @Override
        protected AABB getTargetSearchArea(final double double1) {
            final Direction fz4 = ((Shulker)this.mob).getAttachFace();
            if (fz4.getAxis() == Direction.Axis.X) {
                return this.mob.getBoundingBox().inflate(4.0, double1, double1);
            }
            if (fz4.getAxis() == Direction.Axis.Z) {
                return this.mob.getBoundingBox().inflate(double1, double1, 4.0);
            }
            return this.mob.getBoundingBox().inflate(double1, 4.0, double1);
        }
    }
}
