// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.npc.corecomponents.combat;

import java.util.function.Supplier;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.server.core.modules.projectile.config.BallisticDataProvider;
import com.hypixel.hytale.server.core.entity.InteractionChain;
import com.hypixel.hytale.math.vector.Vector3f;
import java.util.Set;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider;
import com.hypixel.hytale.server.npc.interactions.NPCInteractionSimulationHandler;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.math.random.RandomExtra;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.npc.role.support.CombatSupport;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import com.hypixel.hytale.server.npc.util.AimingData;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.core.modules.interaction.InteractionModule;
import com.hypixel.hytale.server.core.entity.InteractionManager;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionAttack;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.StringParameterProvider;
import java.util.Map;
import com.hypixel.hytale.protocol.InteractionType;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.SingleCollector;
import com.hypixel.hytale.server.npc.corecomponents.ActionBase;

public class ActionAttack extends ActionBase
{
    @Nonnull
    public static final ThreadLocal<SingleCollector<BallisticData>> THREAD_LOCAL_COLLECTOR;
    protected final int id;
    @Nullable
    protected String attack;
    protected final InteractionType interactionType;
    protected final float chargeFor;
    protected final double[] attackPauseRange;
    protected final double[] aimingTimeRange;
    protected final double meleeConeAngle;
    protected final BallisticMode ballisticMode;
    protected final boolean checkLineOfSight;
    protected final boolean avoidFriendlyFire;
    protected final boolean damageFriendlies;
    protected final boolean skipAiming;
    protected final double chargeDistance;
    protected final int attackParameterSlot;
    @Nullable
    protected final Map<String, String> interactionVars;
    protected boolean attackReady;
    @Nullable
    protected String attackInteraction;
    protected boolean ballisticShort;
    protected StringParameterProvider cachedAttackProvider;
    protected boolean initialised;
    protected double aimingTimeRemaining;
    protected Role ownerRole;
    
    public ActionAttack(@Nonnull final BuilderActionAttack builderActionAttack, @Nonnull final BuilderSupport builderSupport) {
        super(builderActionAttack);
        this.id = builderSupport.getNextAttackIndex();
        this.attack = builderActionAttack.getAttack(builderSupport);
        this.interactionType = builderActionAttack.getAttackType(builderSupport).getInteractionType();
        this.chargeFor = builderActionAttack.getChargeTime(builderSupport);
        this.attackPauseRange = builderActionAttack.getAttackPauseRange(builderSupport);
        this.aimingTimeRange = builderActionAttack.getAimingTimeRange(builderSupport);
        this.meleeConeAngle = builderActionAttack.getMeleeConeAngle();
        this.ballisticMode = builderActionAttack.getBallisticMode();
        this.checkLineOfSight = builderActionAttack.isCheckLineOfSight();
        this.avoidFriendlyFire = builderActionAttack.isAvoidFriendlyFire();
        this.damageFriendlies = builderActionAttack.isDamageFriendlies();
        this.skipAiming = builderActionAttack.isSkipAiming();
        this.chargeDistance = builderActionAttack.getChargeDistance(builderSupport);
        this.attackParameterSlot = builderActionAttack.getAttackParameterSlot(builderSupport);
        this.interactionVars = builderActionAttack.getInteractionVars();
    }
    
    @Override
    public boolean canExecute(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final InfoProvider sensorInfo, final double dt, @Nonnull final Store<EntityStore> store) {
        return super.canExecute(ref, role, sensorInfo, dt, store) && !role.getCombatSupport().isExecutingAttack();
    }
    
    @Override
    public boolean execute(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, @Nullable final InfoProvider sensorInfo, final double dt, @Nonnull final Store<EntityStore> store) {
        super.execute(ref, role, sensorInfo, dt, store);
        this.ownerRole = role;
        final CombatSupport combatSupport = role.getCombatSupport();
        final InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent());
        assert interactionManagerComponent != null;
        final NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType());
        assert npcComponent != null;
        if (!this.initialised) {
            final ParameterProvider parameterProvider = (this.attackParameterSlot >= 0 && sensorInfo != null) ? sensorInfo.getParameterProvider(this.attackParameterSlot) : null;
            if (parameterProvider instanceof final StringParameterProvider stringParameterProvider) {
                this.cachedAttackProvider = stringParameterProvider;
            }
            this.initialised = true;
        }
        if (this.cachedAttackProvider != null) {
            this.attackInteraction = this.cachedAttackProvider.getStringParameter();
        }
        final AimingData aimingDataInfo = (sensorInfo != null) ? sensorInfo.getPassedExtraInfo(AimingData.class) : null;
        final AimingData aimingData = (aimingDataInfo != null && aimingDataInfo.isClaimedBy(this.id)) ? aimingDataInfo : null;
        if (!this.attackReady) {
            this.attackReady = true;
            this.aimingTimeRemaining = this.newAimingTime();
            final String nextOverride = combatSupport.getNextAttackOverride();
            if (nextOverride != null) {
                if (!RootInteraction.getAssetMap().getKeysForTag(CombatSupport.ATTACK_TAG_INDEX).contains(nextOverride)) {
                    this.attackReady = false;
                    NPCPlugin.get().getLogger().at(Level.WARNING).atMostEvery(1, TimeUnit.MINUTES).log("NPC: %s using interaction %s that is not tagged as an 'Attack' usable by NPCs", npcComponent.getRoleName(), nextOverride);
                    return true;
                }
                this.attackInteraction = nextOverride;
            }
            else if (this.attack == null || this.attack.isEmpty()) {
                final ItemStack itemInHand = npcComponent.getInventory().getItemInHand();
                final InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, this.interactionType, store);
                final String interaction = context.getRootInteractionId(this.interactionType);
                if (interaction == null) {
                    this.attackReady = false;
                    NPCPlugin.get().getLogger().at(Level.WARNING).atMostEvery(1, TimeUnit.MINUTES).log("NPC: %s using nonexistent interaction of type %s using weapon %s in Attack action", npcComponent.getRoleName(), this.interactionType, (itemInHand == null) ? "empty" : itemInHand.getItemId());
                    return true;
                }
                final Set<String> validKeys = RootInteraction.getAssetMap().getKeysForTag(CombatSupport.ATTACK_TAG_INDEX);
                if (!validKeys.contains(interaction)) {
                    this.attackReady = false;
                    NPCPlugin.get().getLogger().at(Level.WARNING).atMostEvery(1, TimeUnit.MINUTES).log("NPC: %s with weapon %s, using interaction of type %s that is not tagged as an 'Attack' usable by NPCs", npcComponent.getRoleName(), (itemInHand == null) ? "empty" : itemInHand.getItemId(), this.interactionType);
                    return true;
                }
                this.attackInteraction = interaction;
            }
            else {
                this.attackInteraction = this.attack;
            }
            if (!this.skipAiming && aimingData != null) {
                final SingleCollector<BallisticData> collector = ActionAttack.THREAD_LOCAL_COLLECTOR.get();
                interactionManagerComponent.walkChain(ref, collector, InteractionType.Primary, RootInteraction.getAssetMap().getAsset(this.attackInteraction), store);
                final BallisticData ballisticData = collector.getResult();
                if (ballisticData != null) {
                    aimingData.requireBallistic(ballisticData);
                    aimingData.setUseFlatTrajectory(this.ballisticShort = switch (this.ballisticMode.ordinal()) {
                        default -> throw new MatchException(null, null);
                        case 0 -> true;
                        case 1 -> false;
                        case 2 -> !this.ballisticShort;
                        case 3 -> RandomExtra.randomBoolean();
                    });
                }
                else {
                    if (this.chargeDistance > 0.0) {
                        aimingData.setChargeDistance(this.chargeDistance);
                        aimingData.setDesiredHitAngle(this.meleeConeAngle);
                    }
                    aimingData.requireCloseCombat();
                }
                return false;
            }
        }
        final TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType());
        assert headRotationComponent != null;
        final Vector3f rotation = (aimingData != null && aimingData.getChargeDistance() > 0.0) ? transformComponent.getRotation() : headRotationComponent.getRotation();
        if (this.hasTimeForAiming(dt) && aimingData != null && !aimingData.isOnTarget(rotation.getYaw(), rotation.getPitch(), this.meleeConeAngle)) {
            aimingData.clearSolution();
            return false;
        }
        final Ref<EntityStore> target = (aimingData != null) ? aimingData.getTarget() : null;
        if (this.checkLineOfSight && (target == null || !role.getPositionCache().hasLineOfSight(ref, target, store))) {
            if (aimingData != null) {
                aimingData.clearSolution();
            }
            return true;
        }
        if (this.avoidFriendlyFire && target != null && role.getPositionCache().isFriendlyBlockingLineOfSight(ref, target, store)) {
            aimingData.clearSolution();
            return true;
        }
        ((NPCInteractionSimulationHandler)interactionManagerComponent.getInteractionSimulationHandler()).requestChargeTime(this.chargeFor);
        final InteractionContext context2 = InteractionContext.forInteraction(interactionManagerComponent, ref, this.interactionType, store);
        context2.setInteractionVarsGetter(this::getInteractionVars);
        final InteractionChain chain = interactionManagerComponent.initChain(this.interactionType, context2, RootInteraction.getRootInteractionOrUnknown(this.attackInteraction), false);
        interactionManagerComponent.queueExecuteChain(chain);
        combatSupport.setExecutingAttack(chain, this.damageFriendlies, this.newAttackPause());
        if (aimingData != null) {
            aimingData.setHaveAttacked(true);
        }
        this.attackReady = false;
        return true;
    }
    
    @Override
    public void activate(final Role role, @Nullable final InfoProvider infoProvider) {
        super.activate(role, infoProvider);
        if (infoProvider == null) {
            return;
        }
        final AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class);
        if (aimingData != null) {
            aimingData.tryClaim(this.id);
        }
    }
    
    @Override
    public void deactivate(final Role role, @Nullable final InfoProvider infoProvider) {
        super.deactivate(role, infoProvider);
        if (infoProvider == null) {
            return;
        }
        final AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class);
        if (aimingData != null) {
            aimingData.release();
        }
    }
    
    protected boolean hasTimeForAiming(final double dt) {
        if (this.aimingTimeRemaining > 0.0) {
            this.aimingTimeRemaining -= dt;
            return true;
        }
        return false;
    }
    
    protected double newAimingTime() {
        return (this.aimingTimeRange[1] > 0.0) ? RandomExtra.randomRange(this.aimingTimeRange) : 0.0;
    }
    
    protected double newAttackPause() {
        return RandomExtra.randomRange(this.attackPauseRange[0], this.attackPauseRange[1]);
    }
    
    @Nullable
    private Map<String, String> getInteractionVars(final InteractionContext c) {
        if (this.interactionVars == null) {
            return this.ownerRole.getInteractionVars();
        }
        return this.interactionVars;
    }
    
    static {
        THREAD_LOCAL_COLLECTOR = ThreadLocal.withInitial(() -> new SingleCollector((collectorTag, interactionContext, interaction) -> {
            if (interaction instanceof final BallisticDataProvider ballisticDataProvider) {
                if (!(!Interaction.getAssetMap().getKeysForTag(CombatSupport.AIMING_REFERENCE_TAG_INDEX).contains(interaction.getId()))) {
                    return ballisticDataProvider.getBallisticData();
                }
            }
            return null;
        }));
    }
    
    public enum BallisticMode implements Supplier<String>
    {
        Short("Shorter flight curve"), 
        Long("Longer flight curve"), 
        Alternate("Alternate between long and short"), 
        Random("Random long or short");
        
        private final String description;
        
        private BallisticMode(final String description) {
            this.description = description;
        }
        
        @Override
        public String get() {
            return this.description;
        }
    }
    
    public enum AttackType implements Supplier<String>
    {
        Primary(InteractionType.Primary, "Primary attack"), 
        Secondary(InteractionType.Secondary, "Secondary attack"), 
        Ability1(InteractionType.Ability1, "Ability 1"), 
        Ability2(InteractionType.Ability2, "Ability 2"), 
        Ability3(InteractionType.Ability3, "Ability 3");
        
        private final String description;
        private final InteractionType interactionType;
        
        private AttackType(final InteractionType interactionType, final String description) {
            this.interactionType = interactionType;
            this.description = description;
        }
        
        @Override
        public String get() {
            return this.description;
        }
        
        public InteractionType getInteractionType() {
            return this.interactionType;
        }
    }
}
