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

package com.hypixel.hytale.server.npc.asset.builder;

import it.unimi.dsi.fastutil.ints.IntSets;
import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.ints.IntCollection;
import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import java.util.Objects;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import com.hypixel.hytale.common.thread.ticking.Tickable;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.storage.AlarmStore;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.server.npc.util.Alarm;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import com.hypixel.hytale.server.npc.role.support.EntitySupport;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayDeque;
import com.hypixel.hytale.server.npc.valuestore.ValueStore;
import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator;
import com.hypixel.hytale.server.npc.role.support.RoleStats;
import com.hypixel.hytale.server.npc.util.expression.StdScope;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Map;
import java.util.BitSet;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntStack;
import com.hypixel.hytale.server.npc.util.expression.Scope;
import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventType;
import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import java.util.List;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import com.hypixel.hytale.server.npc.util.Timer;
import com.hypixel.hytale.server.npc.util.expression.ExecutionContext;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.entities.NPCEntity;

public class BuilderSupport
{
    private final BuilderManager builderManager;
    @Nonnull
    private final NPCEntity npcEntity;
    private final Holder<EntityStore> holder;
    private final ExecutionContext executionContext;
    private boolean requireLeashPosition;
    private final SlotMapper flagSlotMapper;
    private final SlotMapper beaconSlotMapper;
    private final SlotMapper targetSlotMapper;
    private final SlotMapper positionSlotMapper;
    private final ReferenceSlotMapper<Timer> timerSlotMapper;
    private final SlotMapper searchRaySlotMapper;
    private final SlotMapper parameterSlotMapper;
    @Nonnull
    private final Object2IntMap<String> instructionSlotMappings;
    private final Int2ObjectMap<String> instructionNameMappings;
    private final List<Instruction> instructions;
    private EventSlotMapper<BlockEventType> playerBlockEventSlotMapper;
    private EventSlotMapper<BlockEventType> npcBlockEventSlotMapper;
    private EventSlotMapper<EntityEventType> playerEntityEventSlotMapper;
    private EventSlotMapper<EntityEventType> npcEntityEventSlotMapper;
    private Scope globalScope;
    private int currentComponentIndex;
    private IntStack componentIndexStack;
    private int componentIndexSource;
    private int currentAttackIndex;
    private Int2IntMap componentLocalStateMachines;
    private BitSet localStateMachineAutoResetStates;
    private final StateMappingHelper stateHelper;
    private List<Map.Entry<StateMappingHelper, StatePair[]>> modifiedStateMap;
    private IntSet blackboardBlockSets;
    private IntSet blockSensorResetBlockSets;
    private boolean requiresAttitudeOverrideMemory;
    private boolean trackInteractions;
    private InstructionType currentInstructionContext;
    private ComponentContext currentComponentContext;
    @Nonnull
    private final StdScope sensorScope;
    @Nonnull
    private final Builder<?> roleBuilder;
    private final RoleStats roleStats;
    private StateEvaluator stateEvaluator;
    private ValueStore.Builder valueStoreBuilder;
    private final ArrayDeque<String> stateStack;
    
    public BuilderSupport(final BuilderManager builderManager, @Nonnull final NPCEntity npcEntity, final Holder<EntityStore> holder, final ExecutionContext executionContext, @Nonnull final Builder<?> roleBuilder, final RoleStats roleStats) {
        this.flagSlotMapper = new SlotMapper();
        this.beaconSlotMapper = new SlotMapper();
        this.targetSlotMapper = new SlotMapper(true);
        this.positionSlotMapper = new SlotMapper();
        this.timerSlotMapper = new ReferenceSlotMapper<Timer>(Timer::new);
        this.searchRaySlotMapper = new SlotMapper();
        this.parameterSlotMapper = new SlotMapper();
        this.instructionNameMappings = new Int2ObjectOpenHashMap<String>();
        this.instructions = new ObjectArrayList<Instruction>();
        this.currentInstructionContext = InstructionType.Component;
        this.stateStack = new ArrayDeque<String>();
        this.builderManager = builderManager;
        this.npcEntity = npcEntity;
        this.holder = holder;
        this.executionContext = executionContext;
        this.roleBuilder = roleBuilder;
        this.stateHelper = roleBuilder.getStateMappingHelper();
        this.roleStats = roleStats;
        this.sensorScope = EntitySupport.createScope(npcEntity);
        (this.instructionSlotMappings = new Object2IntOpenHashMap<String>()).defaultReturnValue(Integer.MIN_VALUE);
    }
    
    public BuilderManager getBuilderManager() {
        return this.builderManager;
    }
    
    @Nonnull
    public NPCEntity getEntity() {
        return this.npcEntity;
    }
    
    public Holder<EntityStore> getHolder() {
        return this.holder;
    }
    
    public ExecutionContext getExecutionContext() {
        return this.executionContext;
    }
    
    @Nonnull
    public Builder<?> getParentSpawnable() {
        return this.roleBuilder;
    }
    
    public void setScope(final Scope scope) {
        this.getExecutionContext().setScope(scope);
    }
    
    public void setGlobalScope(final Scope scope) {
        this.globalScope = scope;
    }
    
    public Scope getGlobalScope() {
        return this.globalScope;
    }
    
    public void setRequireLeashPosition() {
        this.requireLeashPosition = true;
    }
    
    public int getFlagSlot(final String name) {
        return this.flagSlotMapper.getSlot(name);
    }
    
    public Timer getTimerByName(final String name) {
        return this.timerSlotMapper.getReference(name);
    }
    
    public int getBeaconMessageSlot(final String name) {
        return this.beaconSlotMapper.getSlot(name);
    }
    
    public int getTargetSlot(final String name) {
        return this.targetSlotMapper.getSlot(name);
    }
    
    public Alarm getAlarm(final String name) {
        final NPCEntity npc = this.holder.getComponent(NPCEntity.getComponentType());
        final AlarmStore alarmStore = npc.getAlarmStore();
        return alarmStore.get(this.npcEntity, name);
    }
    
    @Nullable
    public Object2IntMap<String> getTargetSlotMappings() {
        return this.targetSlotMapper.getSlotMappings();
    }
    
    @Nullable
    public Int2ObjectMap<String> getTargetSlotToNameMap() {
        return this.targetSlotMapper.getNameMap();
    }
    
    public int getPositionSlot(final String name) {
        return this.positionSlotMapper.getSlot(name);
    }
    
    public int getParameterSlot(final String name) {
        return this.parameterSlotMapper.getSlot(name);
    }
    
    public int getSearchRaySlot(final String name) {
        return this.searchRaySlotMapper.getSlot(name);
    }
    
    @Nullable
    public Vector3d[] allocatePositionSlots() {
        return allocatePositionSlots(this.positionSlotMapper);
    }
    
    public boolean requiresLeashPosition() {
        return this.requireLeashPosition;
    }
    
    public StateEvaluator getStateEvaluator() {
        return this.stateEvaluator;
    }
    
    public void setStateEvaluator(final StateEvaluator stateEvaluator) {
        this.stateEvaluator = stateEvaluator;
    }
    
    public boolean[] allocateFlags() {
        final int slotCount = this.flagSlotMapper.slotCount();
        if (slotCount == 0) {
            return null;
        }
        return new boolean[slotCount];
    }
    
    @Nullable
    public Tickable[] allocateTimers() {
        final List<Timer> referenceList = this.timerSlotMapper.getReferenceList();
        if (referenceList.isEmpty()) {
            return null;
        }
        return referenceList.toArray(Tickable[]::new);
    }
    
    @Nullable
    public Vector3d[] allocateSearchRayPositionSlots() {
        return allocatePositionSlots(this.searchRaySlotMapper);
    }
    
    @Nonnull
    public StdScope getSensorScope() {
        return this.sensorScope;
    }
    
    public void setToNewComponent() {
        if (this.componentIndexStack == null) {
            this.componentIndexStack = new IntArrayList();
        }
        this.componentIndexStack.push(this.currentComponentIndex);
        this.currentComponentIndex = this.componentIndexSource++;
    }
    
    public void addComponentLocalStateMachine(final int defaultState) {
        if (this.componentLocalStateMachines == null) {
            (this.componentLocalStateMachines = new Int2IntOpenHashMap()).defaultReturnValue(Integer.MIN_VALUE);
        }
        this.componentLocalStateMachines.put(this.getComponentIndex(), defaultState);
    }
    
    public int getComponentIndex() {
        return this.currentComponentIndex;
    }
    
    public void popComponent() {
        this.currentComponentIndex = this.componentIndexStack.popInt();
    }
    
    public boolean hasComponentLocalStateMachines() {
        return this.componentLocalStateMachines != null;
    }
    
    public Int2IntMap getComponentLocalStateMachines() {
        return this.componentLocalStateMachines;
    }
    
    public void setLocalStateMachineAutoReset() {
        if (this.localStateMachineAutoResetStates == null) {
            this.localStateMachineAutoResetStates = new BitSet();
        }
        this.localStateMachineAutoResetStates.set(this.getComponentIndex());
    }
    
    public BitSet getLocalStateMachineAutoResetStates() {
        return this.localStateMachineAutoResetStates;
    }
    
    public StateMappingHelper getStateHelper() {
        return this.stateHelper;
    }
    
    @Nullable
    public Object2IntMap<String> getBeaconSlotMappings() {
        return this.beaconSlotMapper.getSlotMappings();
    }
    
    public boolean hasBlockEventSupport() {
        return this.playerBlockEventSlotMapper != null || this.npcBlockEventSlotMapper != null;
    }
    
    public EventSlotMapper<BlockEventType> getPlayerBlockEventSlotMapper() {
        return this.playerBlockEventSlotMapper;
    }
    
    public EventSlotMapper<BlockEventType> getNPCBlockEventSlotMapper() {
        return this.npcBlockEventSlotMapper;
    }
    
    public boolean hasEntityEventSupport() {
        return this.playerEntityEventSlotMapper != null || this.npcEntityEventSlotMapper != null;
    }
    
    public EventSlotMapper<EntityEventType> getPlayerEntityEventSlotMapper() {
        return this.playerEntityEventSlotMapper;
    }
    
    public EventSlotMapper<EntityEventType> getNPCEntityEventSlotMapper() {
        return this.npcEntityEventSlotMapper;
    }
    
    public int getInstructionSlot(@Nullable final String name) {
        int slot = this.instructionSlotMappings.getInt(name);
        if (slot == Integer.MIN_VALUE) {
            slot = this.instructions.size();
            if (name != null && !name.isEmpty()) {
                this.instructionSlotMappings.put(name, slot);
                this.instructionNameMappings.put(slot, name);
            }
            this.instructions.add(null);
        }
        return slot;
    }
    
    public void putInstruction(final int slot, final Instruction instruction) {
        Objects.requireNonNull(instruction, "Instruction cannot be null when putting instruction");
        if (slot < 0 || slot >= this.instructions.size()) {
            throw new IllegalArgumentException("Slot for putting instruction must be >= 0 and < the size of the list");
        }
        if (this.instructions.get(slot) != null) {
            throw new IllegalStateException(String.format("Duplicate instruction with name: %s", this.instructionNameMappings.get(slot)));
        }
        this.instructions.set(slot, instruction);
    }
    
    @Nonnull
    public Instruction[] getInstructionSlotMappings() {
        final Instruction[] slots = this.instructions.toArray(Instruction[]::new);
        for (int i = 0; i < slots.length; ++i) {
            final Instruction instruction = slots[i];
            if (instruction == null) {
                throw new IllegalStateException("Instruction: " + (String)this.instructionNameMappings.get(i) + " doesn't exist");
            }
        }
        return slots;
    }
    
    public void setModifiedStateMap(@Nonnull final StateMappingHelper helper, @Nonnull final StatePair[] map) {
        if (this.modifiedStateMap == null) {
            this.modifiedStateMap = new ObjectArrayList<Map.Entry<StateMappingHelper, StatePair[]>>();
        }
        this.modifiedStateMap.add(Map.entry(helper, map));
    }
    
    @Nonnull
    public StatePair getMappedStatePair(int index) {
        StatePair result = null;
        for (int i = this.modifiedStateMap.size() - 1; i >= 0; --i) {
            final Map.Entry<StateMappingHelper, StatePair[]> entry = this.modifiedStateMap.get(i);
            result = entry.getValue()[index];
            index = entry.getKey().getComponentImportStateIndex(result.getFullStateName());
            if (index < 0) {
                break;
            }
        }
        Objects.requireNonNull(result, "Result should not be null after iterating mapped state pairs");
        return result;
    }
    
    public void popModifiedStateMap() {
        this.modifiedStateMap.removeLast();
    }
    
    public void requireBlockTypeBlackboard(final int blockSet) {
        if (this.blackboardBlockSets == null) {
            this.blackboardBlockSets = new IntOpenHashSet();
        }
        this.blackboardBlockSets.add(blockSet);
    }
    
    public void registerBlockSensorResetAction(final int blockSet) {
        if (this.blockSensorResetBlockSets == null) {
            this.blockSensorResetBlockSets = new IntOpenHashSet();
        }
        this.blockSensorResetBlockSets.add(blockSet);
    }
    
    public boolean requiresBlockTypeBlackboard() {
        return this.blackboardBlockSets != null;
    }
    
    @Nonnull
    public IntList getBlockTypeBlackboardBlockSets() {
        if (this.blockSensorResetBlockSets != null) {
            this.blockSensorResetBlockSets.forEach(blockSet -> {
                if (!this.blackboardBlockSets.contains(blockSet)) {
                    new IllegalStateException(String.format("No block sensors match BlockSet %s in ResetBlockSensors action", BlockSet.getAssetMap().getAsset(blockSet).getId()));
                    throw;
                }
                else {
                    return;
                }
            });
        }
        final IntArrayList blockSets = new IntArrayList();
        blockSets.addAll(this.blackboardBlockSets);
        blockSets.trim();
        return IntLists.unmodifiable(blockSets);
    }
    
    public int getBlockEventSlot(final BlockEventType type, final int blockSet, final double maxRange, final boolean player) {
        if (player) {
            if (this.playerBlockEventSlotMapper == null) {
                this.playerBlockEventSlotMapper = new EventSlotMapper<BlockEventType>(BlockEventType.class, BlockEventType.VALUES);
            }
            return this.playerBlockEventSlotMapper.getEventSlot(type, blockSet, maxRange);
        }
        if (this.npcBlockEventSlotMapper == null) {
            this.npcBlockEventSlotMapper = new EventSlotMapper<BlockEventType>(BlockEventType.class, BlockEventType.VALUES);
        }
        return this.npcBlockEventSlotMapper.getEventSlot(type, blockSet, maxRange);
    }
    
    @Nullable
    public IntSet getBlockChangeSets(final BlockEventType type) {
        final IntSet playerEventSets = (this.playerBlockEventSlotMapper != null) ? this.playerBlockEventSlotMapper.getEventSets().get(type) : null;
        final IntSet npcEventSets = (this.npcBlockEventSlotMapper != null) ? this.npcBlockEventSlotMapper.getEventSets().get(type) : null;
        if (playerEventSets == null && npcEventSets == null) {
            return null;
        }
        final IntOpenHashSet set = new IntOpenHashSet();
        if (playerEventSets != null) {
            set.addAll(playerEventSets);
        }
        if (npcEventSets != null) {
            set.addAll(npcEventSets);
        }
        set.trim();
        return IntSets.unmodifiable(set);
    }
    
    public int getEntityEventSlot(final EntityEventType type, final int npcGroup, final double maxRange, final boolean player) {
        if (player) {
            if (this.playerEntityEventSlotMapper == null) {
                this.playerEntityEventSlotMapper = new EventSlotMapper<EntityEventType>(EntityEventType.class, EntityEventType.VALUES);
            }
            return this.playerEntityEventSlotMapper.getEventSlot(type, npcGroup, maxRange);
        }
        if (this.npcEntityEventSlotMapper == null) {
            this.npcEntityEventSlotMapper = new EventSlotMapper<EntityEventType>(EntityEventType.class, EntityEventType.VALUES);
        }
        return this.npcEntityEventSlotMapper.getEventSlot(type, npcGroup, maxRange);
    }
    
    @Nullable
    public IntSet getEventNPCGroups(final EntityEventType type) {
        final IntSet playerEventSets = (this.playerEntityEventSlotMapper != null) ? this.playerEntityEventSlotMapper.getEventSets().get(type) : null;
        final IntSet npcEventSets = (this.npcEntityEventSlotMapper != null) ? this.npcEntityEventSlotMapper.getEventSets().get(type) : null;
        if (playerEventSets == null && npcEventSets == null) {
            return null;
        }
        final IntOpenHashSet set = new IntOpenHashSet();
        if (playerEventSets != null) {
            set.addAll(playerEventSets);
        }
        if (npcEventSets != null) {
            set.addAll(npcEventSets);
        }
        set.trim();
        return IntSets.unmodifiable(set);
    }
    
    public void requireAttitudeOverrideMemory() {
        this.requiresAttitudeOverrideMemory = true;
    }
    
    public void trackInteractions() {
        this.trackInteractions = true;
    }
    
    public boolean isTrackInteractions() {
        return this.trackInteractions;
    }
    
    public boolean requiresAttitudeOverrideMemory() {
        return this.requiresAttitudeOverrideMemory;
    }
    
    public void setCurrentInstructionContext(final InstructionType context) {
        this.currentInstructionContext = context;
    }
    
    public InstructionType getCurrentInstructionContext() {
        return this.currentInstructionContext;
    }
    
    public ComponentContext getCurrentComponentContext() {
        return this.currentComponentContext;
    }
    
    public void setCurrentComponentContext(final ComponentContext currentComponentContext) {
        this.currentComponentContext = currentComponentContext;
    }
    
    public RoleStats getRoleStats() {
        return this.roleStats;
    }
    
    public int getNextAttackIndex() {
        return this.currentAttackIndex++;
    }
    
    public int getValueStoreStringSlot(final String name) {
        if (this.valueStoreBuilder == null) {
            this.valueStoreBuilder = new ValueStore.Builder();
        }
        return this.valueStoreBuilder.getStringSlot(name);
    }
    
    public int getValueStoreIntSlot(final String name) {
        if (this.valueStoreBuilder == null) {
            this.valueStoreBuilder = new ValueStore.Builder();
        }
        return this.valueStoreBuilder.getIntSlot(name);
    }
    
    public int getValueStoreDoubleSlot(final String name) {
        if (this.valueStoreBuilder == null) {
            this.valueStoreBuilder = new ValueStore.Builder();
        }
        return this.valueStoreBuilder.getDoubleSlot(name);
    }
    
    public ValueStore.Builder getValueStoreBuilder() {
        return this.valueStoreBuilder;
    }
    
    @Nullable
    public String getCurrentStateName() {
        return this.stateStack.peek();
    }
    
    public void pushCurrentStateName(@Nonnull final String currentStateName) {
        this.stateStack.push(currentStateName);
    }
    
    public void popCurrentStateName() {
        this.stateStack.pop();
    }
    
    @Nullable
    private static Vector3d[] allocatePositionSlots(@Nonnull final SlotMapper mapper) {
        final int slotCount = mapper.slotCount();
        if (slotCount == 0) {
            return null;
        }
        final Vector3d[] slots = new Vector3d[slotCount];
        for (int i = 0; i < slots.length; ++i) {
            slots[i] = new Vector3d();
        }
        return slots;
    }
}
