package com.Polarice3.Goety.common.entities.ally.illager;

import com.Polarice3.Goety.common.entities.neutral.Owned;
import com.Polarice3.Goety.common.network.ModNetwork;
import com.Polarice3.Goety.common.network.server.SLightningBoltPacket;
import com.Polarice3.Goety.common.network.server.SThunderBoltPacket;
import com.Polarice3.Goety.config.AttributesConfig;
import com.Polarice3.Goety.init.ModSounds;
import com.Polarice3.Goety.utils.*;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Predicate;

public class CrusherServant extends AbstractIllagerServant {
    protected static final EntityDataAccessor<Byte> DATA_FLAGS_ID = SynchedEntityData.defineId(CrusherServant.class, EntityDataSerializers.BYTE);
    private static final EntityDataAccessor<Integer> ANIM_STATE = SynchedEntityData.defineId(CrusherServant.class, EntityDataSerializers.INT);
    protected static final EntityDataAccessor<Boolean> STORM = SynchedEntityData.defineId(CrusherServant.class, EntityDataSerializers.BOOLEAN);
    public int attackTick;
    public boolean isRunning = false;
    public AnimationState idleAnimationState = new AnimationState();
    public AnimationState attackAnimationState = new AnimationState();

    public CrusherServant(EntityType<? extends Owned> type, Level worldIn) {
        super(type, worldIn);
    }

    protected void registerGoals() {
        super.registerGoals();
        this.goalSelector.addGoal(1, new MeleeGoal());
        this.goalSelector.addGoal(4, new AttackGoal(1.0D));
    }

    public static AttributeSupplier.Builder setCustomAttributes() {
        return Mob.createMobAttributes()
                .add(Attributes.MAX_HEALTH, AttributesConfig.CrusherHealth.get())
                .add(Attributes.ARMOR, AttributesConfig.CrusherArmor.get())
                .add(Attributes.FOLLOW_RANGE, 16.0D)
                .add(Attributes.MOVEMENT_SPEED, 0.35D)
                .add(Attributes.KNOCKBACK_RESISTANCE, 0.75D)
                .add(Attributes.ATTACK_DAMAGE, AttributesConfig.CrusherDamage.get());
    }

    public void setConfigurableAttributes(){
        MobUtil.setBaseAttributes(this.getAttribute(Attributes.MAX_HEALTH), AttributesConfig.CrusherHealth.get());
        MobUtil.setBaseAttributes(this.getAttribute(Attributes.ARMOR), AttributesConfig.CrusherArmor.get());
        MobUtil.setBaseAttributes(this.getAttribute(Attributes.ATTACK_DAMAGE), AttributesConfig.CrusherDamage.get());
    }

    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.define(DATA_FLAGS_ID, (byte)0);
        this.entityData.define(ANIM_STATE, 0);
        this.entityData.define(STORM, false);
    }

    @Override
    public void addAdditionalSaveData(CompoundTag pCompound) {
        super.addAdditionalSaveData(pCompound);
        pCompound.putBoolean("Storm", this.isStorm());
    }

    @Override
    public void readAdditionalSaveData(CompoundTag pCompound) {
        super.readAdditionalSaveData(pCompound);
        if (pCompound.contains("Storm")){
            this.setStorm(pCompound.getBoolean("Storm"));
        }
    }

    public void setAnimationState(String input) {
        this.setAnimationState(this.getAnimationState(input));
    }

    public void setAnimationState(int id) {
        this.entityData.set(ANIM_STATE, id);
    }

    public int getAnimationState(String animation) {
        if (Objects.equals(animation, "idle")){
            return 1;
        } else if (Objects.equals(animation, "attack")){
            return 2;
        } else {
            return 0;
        }
    }

    public List<AnimationState> getAllAnimations(){
        List<AnimationState> animationStates = new ArrayList<>();
        animationStates.add(this.idleAnimationState);
        animationStates.add(this.attackAnimationState);
        return animationStates;
    }

    public void stopAllAnimations(){
        for (AnimationState animationState : this.getAllAnimations()){
            animationState.stop();
        }
    }

    public void stopMostAnimation(AnimationState exception){
        for (AnimationState state : this.getAllAnimations()){
            if (state != exception){
                state.stop();
            }
        }
    }

    public int getCurrentAnimation(){
        return this.entityData.get(ANIM_STATE);
    }

    public void onSyncedDataUpdated(EntityDataAccessor<?> accessor) {
        if (ANIM_STATE.equals(accessor)) {
            if (this.level.isClientSide){
                switch (this.entityData.get(ANIM_STATE)){
                    case 0:
                        break;
                    case 1:
                        this.idleAnimationState.start(this.tickCount);
                        this.stopMostAnimation(this.idleAnimationState);
                        break;
                    case 2:
                        this.attackAnimationState.start(this.tickCount);
                        this.stopMostAnimation(this.attackAnimationState);
                        break;
                }
            }
        }
    }

    @Override
    public void thunderHit(ServerLevel p_19927_, LightningBolt p_19928_) {
        if (!this.isStorm()) {
            this.setStorm(true);
        } else {
            this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, 600));
            this.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 600));
        }
    }

    public void tick() {
        super.tick();
        if (this.level.isClientSide){
            if (this.isAlive()){
                if (this.getCurrentAnimation() != this.getAnimationState("attack")) {
                    this.setAnimationState("idle");
                    this.isRunning = this.isAggressive();
                }
            }
        }
        if (this.isMeleeAttacking()) {
            ++this.attackTick;
        }
    }

    private boolean getFlag(int mask) {
        int i = this.entityData.get(DATA_FLAGS_ID);
        return (i & mask) != 0;
    }

    private void setFlag(int mask, boolean value) {
        int i = this.entityData.get(DATA_FLAGS_ID);
        if (value) {
            i = i | mask;
        } else {
            i = i & ~mask;
        }

        this.entityData.set(DATA_FLAGS_ID, (byte)(i & 255));
    }

    public boolean isMeleeAttacking() {
        return this.getFlag(1);
    }

    public void setMeleeAttacking(boolean attacking) {
        this.setFlag(1, attacking);
        this.attackTick = 0;
        this.level.broadcastEntityEvent(this, (byte) 5);
    }

    public boolean isStorm(){
        return this.entityData.get(STORM);
    }

    public void setStorm(boolean storm){
        this.entityData.set(STORM, storm);
    }

    @Override
    public float getVoicePitch() {
        return 0.75F;
    }

    protected SoundEvent getAmbientSound() {
        return SoundEvents.VINDICATOR_AMBIENT;
    }

    protected SoundEvent getDeathSound() {
        return SoundEvents.VINDICATOR_DEATH;
    }

    protected SoundEvent getHurtSound(DamageSource pDamageSource) {
        return SoundEvents.VINDICATOR_HURT;
    }

    public boolean isMoving() {
        return this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6D;
    }

    public void handleEntityEvent(byte p_21375_) {
        if (p_21375_ == 5){
            this.attackTick = 0;
        } else if (p_21375_ == 6){
            this.setAggressive(true);
        } else if (p_21375_ == 7){
            this.setAggressive(false);
        } else if (p_21375_ == 8){
            this.setMeleeAttacking(true);
        } else if (p_21375_ == 9){
            this.setMeleeAttacking(false);
        } else {
            super.handleEntityEvent(p_21375_);
        }
    }

    @Override
    public SoundEvent getCelebrateSound() {
        return SoundEvents.VINDICATOR_CELEBRATE;
    }

    public Vec3 getHorizontalLookAngle() {
        return this.calculateViewVector(0, this.getYRot());
    }

    public double getAttackReachSqr(LivingEntity enemy) {
        return (double)(this.getBbWidth() * 4.0F * this.getBbWidth() * 4.0F + enemy.getBbWidth());
    }

    public boolean targetClose(LivingEntity enemy, double distToEnemySqr){
        double reach = this.getAttackReachSqr(enemy);
        return distToEnemySqr <= reach || this.getBoundingBox().intersects(enemy.getBoundingBox());
    }

    @Override
    public int xpReward() {
        if (this.isStorm()){
            return super.xpReward() * 3;
        }
        return super.xpReward();
    }

    class AttackGoal extends MeleeAttackGoal {
        private final double moveSpeed;
        private int delayCounter;

        public AttackGoal(double moveSpeed) {
            super(CrusherServant.this, moveSpeed, true);
            this.moveSpeed = moveSpeed;
        }

        @Override
        public boolean canUse() {
            return CrusherServant.this.getTarget() != null
                    && CrusherServant.this.getTarget().isAlive();
        }

        @Override
        public void start() {
            CrusherServant.this.setAggressive(true);
            CrusherServant.this.level.broadcastEntityEvent(CrusherServant.this, (byte) 6);
            this.delayCounter = 0;
        }

        @Override
        public void stop() {
            CrusherServant.this.getNavigation().stop();
            CrusherServant.this.setAggressive(false);
            CrusherServant.this.level.broadcastEntityEvent(CrusherServant.this, (byte) 7);
        }

        @Override
        public void tick() {
            LivingEntity livingentity = CrusherServant.this.getTarget();
            if (livingentity == null) {
                return;
            }

            CrusherServant.this.getLookControl().setLookAt(livingentity, CrusherServant.this.getMaxHeadYRot(), CrusherServant.this.getMaxHeadXRot());

            if (--this.delayCounter <= 0) {
                this.delayCounter = 10;
                CrusherServant.this.getNavigation().moveTo(livingentity, this.moveSpeed);
            }

            this.checkAndPerformAttack(livingentity, CrusherServant.this.distanceToSqr(livingentity.getX(), livingentity.getY(), livingentity.getZ()));
        }

        @Override
        protected void checkAndPerformAttack(@NotNull LivingEntity enemy, double distToEnemySqr) {
            if (CrusherServant.this.targetClose(enemy, distToEnemySqr)) {
                if (!CrusherServant.this.isMeleeAttacking()) {
                    CrusherServant.this.setMeleeAttacking(true);
                    CrusherServant.this.level.broadcastEntityEvent(CrusherServant.this, (byte) 8);
                }
            }
        }

    }

    class MeleeGoal extends Goal {
        private float yRot;

        public MeleeGoal() {
            this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE));
        }

        @Override
        public boolean canUse() {
            return CrusherServant.this.getTarget() != null
                    && CrusherServant.this.isMeleeAttacking();
        }

        @Override
        public boolean canContinueToUse() {
            return CrusherServant.this.attackTick < MathHelper.secondsToTicks(1.3333F);
        }

        @Override
        public void start() {
            CrusherServant.this.setMeleeAttacking(true);
            CrusherServant.this.level.broadcastEntityEvent(CrusherServant.this, (byte) 8);
            if (CrusherServant.this.getTarget() != null){
                MobUtil.instaLook(CrusherServant.this, CrusherServant.this.getTarget());
            }
            this.yRot = CrusherServant.this.yBodyRot;
        }

        @Override
        public void stop() {
            CrusherServant.this.setAnimationState("idle");
            CrusherServant.this.setMeleeAttacking(false);
            CrusherServant.this.level.broadcastEntityEvent(CrusherServant.this, (byte) 9);
        }

        @Override
        public void tick() {
            CrusherServant.this.setYRot(this.yRot);
            CrusherServant.this.yBodyRot = this.yRot;
            CrusherServant.this.getNavigation().stop();
            if (CrusherServant.this.attackTick == 1) {
                CrusherServant.this.playSound(SoundEvents.VINDICATOR_AMBIENT, 1.0F, CrusherServant.this.isStorm() ? 0.75F : 1.25F);
                CrusherServant.this.setAnimationState("attack");
            }
            if (CrusherServant.this.attackTick == 11){
                CrusherServant.this.playSound(ModSounds.HAMMER_SWING.get());
            }
            if (CrusherServant.this.attackTick == 13) {
                AABB aabb = MobUtil.makeAttackRange(CrusherServant.this.getX() + CrusherServant.this.getHorizontalLookAngle().x * 2,
                        CrusherServant.this.getY(),
                        CrusherServant.this.getZ() + CrusherServant.this.getHorizontalLookAngle().z * 2, 3, 3, 3);
                for (LivingEntity target : CrusherServant.this.level.getEntitiesOfClass(LivingEntity.class, aabb)) {
                    if (target != CrusherServant.this && !MobUtil.areAllies(CrusherServant.this, target)) {
                        this.hurtTarget(target);
                    }
                }
                CrusherServant.this.playSound(ModSounds.HAMMER_IMPACT.get());
                CrusherServant.this.playSound(ModSounds.DIRT_DEBRIS.get());
                if (CrusherServant.this.isStorm()){
                    CrusherServant.this.playSound(ModSounds.THUNDER_STRIKE_FAST.get());
                }
                if (CrusherServant.this.level instanceof ServerLevel serverLevel){
                    BlockPos blockPos = BlockPos.containing(CrusherServant.this.getX() + CrusherServant.this.getHorizontalLookAngle().x * 2, CrusherServant.this.getY() - 1.0F, CrusherServant.this.getZ() + CrusherServant.this.getHorizontalLookAngle().z * 2);
                    BlockParticleOption option = new BlockParticleOption(ParticleTypes.BLOCK, serverLevel.getBlockState(blockPos));
                    for (int i = 0; i < 8; ++i) {
                        ServerParticleUtil.circularParticles(serverLevel, option, CrusherServant.this.getX() + CrusherServant.this.getHorizontalLookAngle().x * 2, CrusherServant.this.getY() + 0.25D, CrusherServant.this.getZ() + CrusherServant.this.getHorizontalLookAngle().z * 2, 1.5F);
                    }
                }
            }
        }

        public void hurtTarget(Entity target) {
            CrusherServant.this.doHurtTarget(target);
            if (CrusherServant.this.level instanceof ServerLevel serverLevel) {
                if (CrusherServant.this.isStorm() && target instanceof LivingEntity livingEntity) {
                    BlockHitResult rayTraceResult = this.blockResult(serverLevel, CrusherServant.this, 16);
                    Optional<BlockPos> lightningRod = BlockFinder.findLightningRod(serverLevel, BlockPos.containing(rayTraceResult.getLocation()), 16);
                    if (lightningRod.isPresent()) {
                        BlockPos blockPos1 = lightningRod.get();
                        ModNetwork.sendToALL(new SLightningBoltPacket(new Vec3(blockPos1.getX(), blockPos1.getY() + 250, blockPos1.getZ()), new Vec3(blockPos1.getX(), blockPos1.getY(), blockPos1.getZ()), 10));
                        serverLevel.playSound(null, CrusherServant.this.getX(), CrusherServant.this.getY(), CrusherServant.this.getZ(), ModSounds.THUNDERBOLT.get(), CrusherServant.this.getSoundSource(), 1.0F, 1.0F);
                    } else {
                        Vec3 vec31 = new Vec3(livingEntity.getX(), livingEntity.getY() + livingEntity.getBbHeight() / 2, livingEntity.getZ());
                        ModNetwork.sendToALL(new SLightningBoltPacket(new Vec3(livingEntity.getX(), livingEntity.getY() + 250, livingEntity.getZ()), vec31, 10));
                        chain(livingEntity, CrusherServant.this);
                        serverLevel.playSound(null, CrusherServant.this.getX(), CrusherServant.this.getY(), CrusherServant.this.getZ(), ModSounds.THUNDERBOLT.get(), CrusherServant.this.getSoundSource(), 1.0F, 1.0F);
                    }
                }
            }
        }

        public void chain(LivingEntity pTarget, LivingEntity pAttacker) {
            double range = 6;
            Level level = pAttacker.level;
            float oDamage = (float) pAttacker.getAttributeValue(Attributes.ATTACK_DAMAGE);

            List<Entity> harmed = new ArrayList<>();
            Predicate<Entity> selector = entity -> entity instanceof LivingEntity livingEntity && livingEntity != pAttacker && !harmed.contains(livingEntity) && MobUtil.canAttack(CrusherServant.this, livingEntity);
            LivingEntity prevTarget = pTarget;

            float damage = level.isThundering() ? oDamage : oDamage / 2.0F;

            for (int i = 0; i < 4; i++) {
                AABB aabb = new AABB(Vec3Util.subtract(prevTarget.position(), range), Vec3Util.add(prevTarget.position(), range));
                List<Entity> entities = level.getEntities(prevTarget, aabb, selector);
                if (!entities.isEmpty()) {
                    LivingEntity target = (LivingEntity) entities.get(level.getRandom().nextInt(entities.size()));
                    if (target.hurt(ModDamageSource.directShock(pAttacker), damage)) {
                        if (prevTarget != target) {
                            Vec3 vec3 = prevTarget.getEyePosition();
                            Vec3 vec31 = target.getEyePosition();
                            ModNetwork.sendToALL(new SThunderBoltPacket(vec3, vec31, 8));
                        }
                    }

                    harmed.add(target);
                    prevTarget = target;
                    damage--;
                }
            }
        }

        public BlockHitResult blockResult(Level worldIn, Entity entity, double range) {
            float f = entity.getXRot();
            float f1 = entity.getYRot();
            Vec3 vector3d = entity.getEyePosition(1.0F);
            float f2 = Mth.cos(-f1 * ((float)Math.PI / 180F) - (float)Math.PI);
            float f3 = Mth.sin(-f1 * ((float)Math.PI / 180F) - (float)Math.PI);
            float f4 = -Mth.cos(-f * ((float)Math.PI / 180F));
            float f5 = Mth.sin(-f * ((float)Math.PI / 180F));
            float f6 = f3 * f4;
            float f7 = f2 * f4;
            Vec3 vector3d1 = vector3d.add((double)f6 * range, (double)f5 * range, (double)f7 * range);
            return worldIn.clip(new ClipContext(vector3d, vector3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, entity));
        }

        @Override
        public boolean requiresUpdateEveryTick() {
            return true;
        }
    }
}
