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

package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator;

import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import com.hypixel.hytale.server.npc.decisionmaker.core.Option;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.npc.valuestore.ValueStore;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.Iterator;
import com.hypixel.hytale.server.npc.asset.builder.StateMappingHelper;
import java.util.Comparator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import com.hypixel.hytale.math.random.RandomExtra;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.builtin.npccombatactionevaluator.CombatActionEvaluatorSystems;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.builtin.npccombatactionevaluator.NPCCombatActionEvaluatorPlugin;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.component.Ref;
import java.util.Map;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import java.util.function.Function;
import javax.annotation.Nullable;
import java.util.List;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions.CombatActionOption;
import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator;

public class CombatActionEvaluator extends Evaluator<CombatActionOption> implements Component<EntityStore>
{
    protected static final float NO_TIMEOUT = Float.MAX_VALUE;
    protected RunOption runOption;
    protected double minRunUtility;
    protected long lastRunNanos;
    protected int runInState;
    protected float predictability;
    protected double minActionUtility;
    protected final Int2ObjectMap<List<OptionHolder>> optionsBySubState;
    protected final Int2ObjectMap<CombatActionEvaluatorConfig.BasicAttacks> basicAttacksBySubState;
    protected int currentBasicAttackSubState;
    protected CombatActionEvaluatorConfig.BasicAttacks currentBasicAttackSet;
    @Nullable
    protected String currentBasicAttack;
    protected Function<InteractionContext, Map<String, String>> currentBasicAttacksInteractionVarsGetter;
    protected boolean currentBasicAttackDamageFriendlies;
    protected int nextBasicAttackIndex;
    protected double basicAttackCooldown;
    @Nullable
    protected Ref<EntityStore> basicAttackTarget;
    protected double basicAttackTimeout;
    @Nullable
    protected Ref<EntityStore> primaryTarget;
    @Nullable
    protected Ref<EntityStore> previousTarget;
    @Nullable
    protected CombatOptionHolder currentAction;
    @Nullable
    protected double[] postExecutionDistanceRange;
    protected int markedTargetSlot;
    protected int minRangeSlot;
    protected int maxRangeSlot;
    protected int positioningAngleSlot;
    @Nullable
    protected String currentInteraction;
    protected Function<InteractionContext, Map<String, String>> currentInteractionVarsGetter;
    protected InteractionType currentInteractionType;
    protected float chargeFor;
    protected boolean currentDamageFriendlies;
    protected boolean requireAiming;
    protected boolean positionFirst;
    protected double chargeDistance;
    protected float timeout;
    protected final EvaluationContext evaluationContext;
    
    public static ComponentType<EntityStore, CombatActionEvaluator> getComponentType() {
        return NPCCombatActionEvaluatorPlugin.get().getCombatActionEvaluatorComponentType();
    }
    
    public CombatActionEvaluator(@Nonnull final Role role, @Nonnull final CombatActionEvaluatorConfig config, @Nonnull final CombatActionEvaluatorSystems.CombatConstructionData data) {
        this.lastRunNanos = CombatActionEvaluator.NOT_USED;
        this.optionsBySubState = new Int2ObjectOpenHashMap<List<OptionHolder>>();
        this.basicAttacksBySubState = new Int2ObjectOpenHashMap<CombatActionEvaluatorConfig.BasicAttacks>();
        this.currentBasicAttackSubState = Integer.MIN_VALUE;
        this.evaluationContext = new EvaluationContext();
        (this.runOption = new RunOption(config.getRunConditions())).sortConditions();
        this.minRunUtility = config.getMinRunUtility();
        this.minActionUtility = config.getMinActionUtility();
        this.predictability = (float)RandomExtra.randomRange(config.getPredictabilityRange());
        final StateMappingHelper stateHelper = role.getStateSupport().getStateHelper();
        final String activeState = data.getCombatState();
        this.runInState = stateHelper.getStateIndex(activeState);
        this.markedTargetSlot = data.getMarkedTargetSlot();
        this.minRangeSlot = data.getMinRangeSlot();
        this.maxRangeSlot = data.getMaxRangeSlot();
        this.positioningAngleSlot = data.getPositioningAngleSlot();
        final Map<String, String> availableActions = config.getAvailableActions();
        final Map<String, OptionHolder> wrappedAvailableActions = new Object2ObjectOpenHashMap<String, OptionHolder>();
        for (final Map.Entry<String, String> action : availableActions.entrySet()) {
            final CombatActionOption option = CombatActionOption.getAssetMap().getAsset(action.getValue());
            if (option == null) {
                throw new IllegalStateException(String.format("Option %s does not exist!", action.getValue()));
            }
            option.sortConditions();
            final CombatOptionHolder holder = switch (option.getActionTarget()) {
                default -> throw new MatchException(null, null);
                case Self -> new SelfCombatOptionHolder(option);
                case Hostile,  Friendly -> new MultipleTargetCombatOptionHolder(option);
            };
            wrappedAvailableActions.put(action.getKey(), holder);
        }
        final Map<String, CombatActionEvaluatorConfig.ActionSet> actionSets = config.getActionSets();
        for (final Map.Entry<String, CombatActionEvaluatorConfig.ActionSet> subState : actionSets.entrySet()) {
            final int subStateIndex = stateHelper.getSubStateIndex(this.runInState, subState.getKey());
            if (subStateIndex == Integer.MIN_VALUE) {
                throw new IllegalStateException(String.format("No such state for combat evaluator: %s.%s", activeState, subState.getKey()));
            }
            final CombatActionEvaluatorConfig.ActionSet actionSet = subState.getValue();
            this.basicAttacksBySubState.put(subStateIndex, actionSet.getBasicAttacks());
            final List<OptionHolder> optionList = this.optionsBySubState.computeIfAbsent(subStateIndex, k -> new ObjectArrayList());
            final String[] combatActions2;
            final String[] combatActions = combatActions2 = actionSet.getCombatActions();
            for (final String action2 : combatActions2) {
                final OptionHolder wrappedAction = wrappedAvailableActions.get(action2);
                if (wrappedAction == null) {
                    throw new IllegalStateException(String.format("No action with name '%s' defined in AvailableActions!", action2));
                }
                optionList.add(wrappedAction);
            }
        }
        for (final List<OptionHolder> optionList2 : this.optionsBySubState.values()) {
            optionList2.sort(Comparator.comparingDouble(OptionHolder::getWeightCoefficient).reversed());
        }
    }
    
    protected CombatActionEvaluator() {
        this.lastRunNanos = CombatActionEvaluator.NOT_USED;
        this.optionsBySubState = new Int2ObjectOpenHashMap<List<OptionHolder>>();
        this.basicAttacksBySubState = new Int2ObjectOpenHashMap<CombatActionEvaluatorConfig.BasicAttacks>();
        this.currentBasicAttackSubState = Integer.MIN_VALUE;
        this.evaluationContext = new EvaluationContext();
    }
    
    public RunOption getRunOption() {
        return this.runOption;
    }
    
    public double getMinRunUtility() {
        return this.minRunUtility;
    }
    
    @Nonnull
    public EvaluationContext getEvaluationContext() {
        return this.evaluationContext;
    }
    
    public long getLastRunNanos() {
        return this.lastRunNanos;
    }
    
    public void setLastRunNanos(final long lastRunNanos) {
        this.lastRunNanos = lastRunNanos;
    }
    
    public int getRunInState() {
        return this.runInState;
    }
    
    @Nonnull
    public Int2ObjectMap<List<OptionHolder>> getOptionsBySubState() {
        return this.optionsBySubState;
    }
    
    public CombatActionEvaluatorConfig.BasicAttacks getBasicAttacks(final int subState) {
        return this.basicAttacksBySubState.get(subState);
    }
    
    public void setCurrentBasicAttackSet(final int subState, final CombatActionEvaluatorConfig.BasicAttacks attacks) {
        if (subState != this.currentBasicAttackSubState) {
            this.nextBasicAttackIndex = 0;
            this.currentBasicAttackSubState = subState;
            this.currentBasicAttackSet = attacks;
        }
    }
    
    @Nullable
    public String getCurrentBasicAttack() {
        return this.currentBasicAttack;
    }
    
    public CombatActionEvaluatorConfig.BasicAttacks getCurrentBasicAttackSet() {
        return this.currentBasicAttackSet;
    }
    
    public void setCurrentBasicAttack(final String attack, final boolean damageFriendlies, final Function<InteractionContext, Map<String, String>> interactionVarsGetter) {
        this.currentBasicAttack = attack;
        this.currentBasicAttacksInteractionVarsGetter = interactionVarsGetter;
        this.currentBasicAttackDamageFriendlies = damageFriendlies;
    }
    
    public int getNextBasicAttackIndex() {
        return this.nextBasicAttackIndex;
    }
    
    public void setNextBasicAttackIndex(final int next) {
        this.nextBasicAttackIndex = next;
    }
    
    public boolean canUseBasicAttack(final int selfIndex, final ArchetypeChunk<EntityStore> archetypeChunk, final CommandBuffer<EntityStore> commandBuffer) {
        return this.basicAttackCooldown <= 0.0 && (this.currentAction == null || this.currentAction.getOption().isBasicAttackAllowed(selfIndex, archetypeChunk, commandBuffer, this));
    }
    
    public void tickBasicAttackCoolDown(final float dt) {
        if (this.basicAttackCooldown > 0.0) {
            this.basicAttackCooldown -= dt;
        }
    }
    
    @Nullable
    public Ref<EntityStore> getBasicAttackTarget() {
        return this.basicAttackTarget;
    }
    
    public void setBasicAttackTarget(final Ref<EntityStore> target) {
        this.basicAttackTarget = target;
    }
    
    public boolean tickBasicAttackTimeout(final float dt) {
        final double basicAttackTimeout = this.basicAttackTimeout - dt;
        this.basicAttackTimeout = basicAttackTimeout;
        return basicAttackTimeout <= 0.0;
    }
    
    public void setBasicAttackTimeout(final double timeout) {
        this.basicAttackTimeout = timeout;
    }
    
    @Nullable
    public Ref<EntityStore> getPrimaryTarget() {
        return this.primaryTarget;
    }
    
    public void clearPrimaryTarget() {
        this.primaryTarget = null;
    }
    
    public void setActiveOptions(final List<OptionHolder> options) {
        this.options = options;
    }
    
    public int getMarkedTargetSlot() {
        return this.markedTargetSlot;
    }
    
    public int getMaxRangeSlot() {
        return this.maxRangeSlot;
    }
    
    public int getMinRangeSlot() {
        return this.minRangeSlot;
    }
    
    public int getPositioningAngleSlot() {
        return this.positioningAngleSlot;
    }
    
    @Nullable
    public String getCurrentAttack() {
        if (this.currentBasicAttack != null) {
            return this.currentBasicAttack;
        }
        return this.currentInteraction;
    }
    
    public float getChargeFor() {
        if (this.currentBasicAttack != null) {
            return 0.0f;
        }
        return this.chargeFor;
    }
    
    public InteractionType getCurrentInteractionType() {
        if (this.currentBasicAttack != null) {
            return InteractionType.Primary;
        }
        return this.currentInteractionType;
    }
    
    public Function<InteractionContext, Map<String, String>> getCurrentInteractionVarsGetter() {
        if (this.currentBasicAttack != null) {
            return this.currentBasicAttacksInteractionVarsGetter;
        }
        return this.currentInteractionVarsGetter;
    }
    
    public boolean shouldDamageFriendlies() {
        if (this.currentBasicAttack != null) {
            return this.currentBasicAttackDamageFriendlies;
        }
        return this.currentDamageFriendlies;
    }
    
    public boolean requiresAiming() {
        return this.currentBasicAttack != null || this.requireAiming;
    }
    
    public boolean shouldPositionFirst() {
        return this.currentBasicAttack == null && this.positionFirst;
    }
    
    public double getChargeDistance() {
        if (this.currentBasicAttack != null) {
            return 0.0;
        }
        return this.chargeDistance;
    }
    
    public void setCurrentInteraction(final String currentInteraction, final InteractionType interactionType, final float chargeFor, final boolean damageFriendlies, final boolean requireAiming, final boolean positionFirst, final double chargeDistance, final Function<InteractionContext, Map<String, String>> interactionVarsGetter) {
        this.currentInteraction = currentInteraction;
        this.currentInteractionType = interactionType;
        this.chargeFor = chargeFor;
        this.currentDamageFriendlies = damageFriendlies;
        this.requireAiming = requireAiming;
        this.positionFirst = positionFirst;
        this.currentInteractionVarsGetter = interactionVarsGetter;
        this.chargeDistance = chargeDistance;
    }
    
    @Nullable
    public CombatOptionHolder getCurrentAction() {
        return this.currentAction;
    }
    
    public double[] consumePostExecutionDistanceRange() {
        final double[] distance = this.postExecutionDistanceRange;
        this.postExecutionDistanceRange = null;
        return distance;
    }
    
    public void setTimeout(final float timeout) {
        this.timeout = timeout;
    }
    
    public void clearTimeout() {
        this.timeout = Float.MAX_VALUE;
    }
    
    public boolean hasTimedOut(final float dt) {
        if (this.timeout != Float.MAX_VALUE) {
            final float timeout = this.timeout - dt;
            this.timeout = timeout;
            if (timeout <= 0.0f) {
                return true;
            }
        }
        return false;
    }
    
    public void selectNextCombatAction(final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, final CommandBuffer<EntityStore> commandBuffer, @Nonnull final Role role, final ValueStore valueStore) {
        this.evaluationContext.setPredictability(this.predictability);
        this.evaluationContext.setMinimumUtility(this.minActionUtility);
        final CombatOptionHolder option = (CombatOptionHolder)this.evaluate(index, archetypeChunk, commandBuffer, this.evaluationContext);
        if (option == null) {
            return;
        }
        final Ref<EntityStore> target = option.getOptionTarget();
        if (target != null) {
            if (option.getOption().getActionTarget() == CombatActionOption.Target.Friendly) {
                this.previousTarget = this.primaryTarget;
            }
            this.primaryTarget = target;
            role.getMarkedEntitySupport().setMarkedEntity(this.markedTargetSlot, this.primaryTarget);
        }
        this.currentAction = option;
        this.currentAction.getOption().execute(index, archetypeChunk, commandBuffer, role, this, valueStore);
        if (option.getOption().cancelBasicAttackOnSelect()) {
            this.clearCurrentBasicAttack();
        }
    }
    
    public void completeCurrentAction(final boolean forceClearAbility, final boolean clearBasicAttack) {
        if (forceClearAbility || this.currentBasicAttack == null) {
            this.terminateCurrentAction();
            this.setLastRunNanos(System.nanoTime());
        }
        if (clearBasicAttack) {
            this.clearCurrentBasicAttack();
        }
    }
    
    public void terminateCurrentAction() {
        this.currentInteraction = null;
        this.chargeFor = 0.0f;
        if (this.currentAction != null) {
            this.currentAction.setLastUsedNanos(System.nanoTime());
            final CombatActionOption option = this.currentAction.getOption();
            if (option.getActionTarget() == CombatActionOption.Target.Friendly) {
                this.primaryTarget = this.previousTarget;
                this.previousTarget = null;
            }
            this.postExecutionDistanceRange = option.getPostExecuteDistanceRange();
            this.currentAction = null;
        }
    }
    
    public void clearCurrentBasicAttack() {
        if (this.currentBasicAttackSet != null) {
            this.basicAttackCooldown = RandomExtra.randomRange(this.currentBasicAttackSet.getCooldownRange());
        }
        this.currentBasicAttack = null;
        this.basicAttackTarget = null;
    }
    
    @Override
    public void setupNPC(final Role role) {
        for (final List<OptionHolder> optionList : this.optionsBySubState.values()) {
            for (final OptionHolder option : optionList) {
                final CombatActionOption opt = option.getOption();
                opt.setupNPC(role);
            }
        }
    }
    
    @Override
    public void setupNPC(final Holder<EntityStore> holder) {
        for (final List<OptionHolder> optionList : this.optionsBySubState.values()) {
            for (final OptionHolder option : optionList) {
                final CombatActionOption opt = option.getOption();
                opt.setupNPC(holder);
            }
        }
    }
    
    @Nonnull
    @Override
    public Component<EntityStore> clone() {
        final CombatActionEvaluator evaluator = new CombatActionEvaluator();
        evaluator.options = this.options;
        evaluator.runOption = this.runOption;
        evaluator.minRunUtility = this.minRunUtility;
        evaluator.minActionUtility = this.minActionUtility;
        evaluator.predictability = this.predictability;
        evaluator.runInState = this.runInState;
        evaluator.optionsBySubState.putAll((Map<?, ?>)this.optionsBySubState);
        evaluator.lastRunNanos = this.lastRunNanos;
        evaluator.markedTargetSlot = this.markedTargetSlot;
        evaluator.minRangeSlot = this.minRangeSlot;
        evaluator.maxRangeSlot = this.maxRangeSlot;
        evaluator.positioningAngleSlot = this.positioningAngleSlot;
        evaluator.primaryTarget = this.primaryTarget;
        evaluator.previousTarget = this.previousTarget;
        evaluator.currentAction = this.currentAction;
        evaluator.currentInteraction = this.currentInteraction;
        evaluator.chargeFor = this.chargeFor;
        evaluator.timeout = this.timeout;
        evaluator.basicAttacksBySubState.putAll((Map<?, ?>)this.basicAttacksBySubState);
        evaluator.nextBasicAttackIndex = this.nextBasicAttackIndex;
        evaluator.basicAttackCooldown = this.basicAttackCooldown;
        evaluator.currentBasicAttackSet = this.currentBasicAttackSet;
        evaluator.currentBasicAttack = this.currentBasicAttack;
        evaluator.basicAttackTimeout = this.basicAttackTimeout;
        evaluator.basicAttackTarget = this.basicAttackTarget;
        evaluator.currentBasicAttackSubState = this.currentBasicAttackSubState;
        evaluator.currentInteractionType = this.currentInteractionType;
        evaluator.currentBasicAttackDamageFriendlies = this.currentBasicAttackDamageFriendlies;
        evaluator.currentDamageFriendlies = this.currentDamageFriendlies;
        evaluator.requireAiming = this.requireAiming;
        return evaluator;
    }
    
    public static class RunOption extends Option
    {
        protected RunOption(final String[] conditions) {
            this.conditions = conditions;
        }
    }
    
    public abstract class CombatOptionHolder extends OptionHolder
    {
        protected long lastUsedNanos;
        
        protected CombatOptionHolder(final CombatActionEvaluator this$0, final CombatActionOption option) {
            this$0.super(option);
            this.lastUsedNanos = Evaluator.NOT_USED;
        }
        
        public void setLastUsedNanos(final long lastUsedNanos) {
            this.lastUsedNanos = lastUsedNanos;
        }
        
        @Nullable
        public Ref<EntityStore> getOptionTarget() {
            return null;
        }
    }
    
    public class SelfCombatOptionHolder extends CombatOptionHolder
    {
        protected SelfCombatOptionHolder(final CombatActionOption option) {
            super(option);
        }
        
        @Override
        public double calculateUtility(final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, final CommandBuffer<EntityStore> commandBuffer, @Nonnull final EvaluationContext context) {
            context.setLastUsedNanos(this.lastUsedNanos);
            return this.utility = this.option.calculateUtility(index, archetypeChunk, CombatActionEvaluator.this.primaryTarget, commandBuffer, context);
        }
    }
    
    public class MultipleTargetCombatOptionHolder extends CombatOptionHolder
    {
        protected List<Ref<EntityStore>> targets;
        protected final DoubleList targetUtilities;
        @Nullable
        protected Ref<EntityStore> pickedTarget;
        
        protected MultipleTargetCombatOptionHolder(final CombatActionEvaluator this$0, final CombatActionOption option) {
            this$0.super(option);
            this.targetUtilities = new DoubleArrayList();
        }
        
        @Override
        public double calculateUtility(final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, final CommandBuffer<EntityStore> commandBuffer, @Nonnull final EvaluationContext context) {
            context.setLastUsedNanos(this.lastUsedNanos);
            final TargetMemory targetMemory = archetypeChunk.getComponent(index, TargetMemory.getComponentType());
            this.targets = switch (((CombatActionOption)this.option).getActionTarget()) {
                default -> throw new MatchException(null, null);
                case Self -> throw new IllegalStateException("Self option should not be wrapped in a MultipleTargetCombatOptionHolder!");
                case Hostile -> targetMemory.getKnownHostilesList();
                case Friendly -> targetMemory.getKnownFriendliesList();
            };
            this.targetUtilities.clear();
            this.pickedTarget = null;
            this.utility = 0.0;
            for (int i = 0; i < this.targets.size(); ++i) {
                final double targetUtility = this.option.calculateUtility(index, archetypeChunk, this.targets.get(i), commandBuffer, context);
                this.targetUtilities.add(i, targetUtility);
                if (targetUtility > this.utility) {
                    this.utility = targetUtility;
                    this.pickedTarget = this.targets.get(i);
                }
            }
            return this.utility;
        }
        
        @Override
        public double getTotalUtility(final double threshold) {
            double utility = 0.0;
            for (int i = 0; i < this.targets.size(); ++i) {
                final double targetUtility = this.targetUtilities.getDouble(i);
                if (targetUtility >= threshold) {
                    utility += targetUtility;
                }
            }
            return utility;
        }
        
        @Override
        public double tryPick(double currentWeight, final double threshold) {
            for (int i = 0; i < this.targets.size(); ++i) {
                final double targetUtility = this.targetUtilities.getDouble(i);
                if (targetUtility >= threshold) {
                    currentWeight -= targetUtility;
                    if (currentWeight <= 0.0) {
                        this.pickedTarget = this.targets.get(i);
                        break;
                    }
                }
            }
            return currentWeight;
        }
        
        @Override
        public Ref<EntityStore> getOptionTarget() {
            return this.pickedTarget;
        }
    }
}
