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

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

import java.util.BitSet;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator;
import com.google.gson.JsonObject;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.Objects;
import javax.annotation.Nonnull;
import java.util.function.BiConsumer;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.ArrayDeque;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nullable;

public class StateMappingHelper
{
    public static final String DEFAULT_STATE = "start";
    public static final String DEFAULT_SUB_STATE = "Default";
    public static final String DEFAULT_STATE_PARAMETER = "DefaultState";
    public static final String STATE_CHANGE_RESET_PARAMETER = "ResetOnStateChange";
    @Nullable
    private StateMap mainStateMap;
    private int[] allMainStates;
    @Nullable
    private Int2ObjectOpenHashMap<IStateMap> subStateMap;
    private int depth;
    @Nullable
    private ArrayDeque<StateDepth> currentParentState;
    private boolean component;
    private boolean hasStateEvaluator;
    private boolean requiresStateEvaluator;
    private String defaultSubState;
    private String defaultComponentLocalState;
    private int defaultComponentLocalStateIndex;
    private boolean componentLocalStateAutoReset;
    private Object2IntOpenHashMap<String> componentImportStateMappings;
    private SingletonStateMap singletonDefaultStateMap;
    
    public StateMappingHelper() {
        this.mainStateMap = new StateMap();
        this.subStateMap = new Int2ObjectOpenHashMap<IStateMap>();
        this.currentParentState = new ArrayDeque<StateDepth>();
        this.component = true;
    }
    
    public int[] getAllMainStates() {
        return this.allMainStates;
    }
    
    public int getHighestSubStateIndex(final int mainStateIndex) {
        return this.subStateMap.get(mainStateIndex).size() - 1;
    }
    
    public void getAndPutSensorIndex(final String state, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        this.currentParentState.push(new StateDepth(this.depth, state));
        final StateMap mainStateMap = this.mainStateMap;
        Objects.requireNonNull(mainStateMap);
        this.getAndPutIndex(state, subState, setter, mainStateMap::getAndPutSensorIndex, (i, s) -> {
            final IStateMap helper = this.initialiseDefaultSubStates(i);
            return Integer.valueOf(helper.getAndPutSensorIndex(s));
        });
    }
    
    public void getAndPutSetterIndex(final String state, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        final StateMap mainStateMap = this.mainStateMap;
        Objects.requireNonNull(mainStateMap);
        this.getAndPutIndex(state, subState, setter, mainStateMap::getAndPutSetterIndex, (i, s) -> {
            final IStateMap helper = this.initialiseDefaultSubStates(i);
            return Integer.valueOf(helper.getAndPutSetterIndex(s));
        });
    }
    
    public void getAndPutStateRequirerIndex(final String state, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        final StateMap mainStateMap = this.mainStateMap;
        Objects.requireNonNull(mainStateMap);
        this.getAndPutIndex(state, subState, setter, mainStateMap::getAndPutRequirerIndex, (i, s) -> {
            final IStateMap helper = this.initialiseDefaultSubStates(i);
            return Integer.valueOf(helper.getAndPutRequirerIndex(s));
        });
    }
    
    private void getAndPutIndex(final String state, @Nullable final String subState, @Nonnull final BiConsumer<Integer, Integer> setter, @Nonnull final Function<String, Integer> mainStateFunction, @Nonnull final BiFunction<Integer, String, Integer> subStateFunction) {
        final Integer index = mainStateFunction.apply(state);
        if (subState == null) {
            setter.accept(index, -1);
            return;
        }
        final Integer subStateIndex = subStateFunction.apply(index, subState);
        setter.accept(index, subStateIndex);
    }
    
    @Nonnull
    private IStateMap initialiseDefaultSubStates(final int index) {
        return this.subStateMap.computeIfAbsent(index, v -> {
            final StateMap map = new StateMap();
            map.getAndPutSensorIndex(this.defaultSubState);
            map.getAndPutSetterIndex(this.defaultSubState);
            return map;
        });
    }
    
    public void validate(final String configName, @Nonnull final List<String> errors) {
        this.mainStateMap.validate(configName, null, errors);
        this.subStateMap.forEach((i, v) -> v.validate(configName, this.mainStateMap.getStateName(i), errors));
        if (!this.hasStateEvaluator && this.requiresStateEvaluator) {
            errors.add(String.format("%s: Expects a state evaluator but does not have one defined", configName));
        }
    }
    
    public int getStateIndex(final String state) {
        return this.mainStateMap.getStateIndex(state);
    }
    
    public int getSubStateIndex(final int index, final String subState) {
        return this.subStateMap.get(index).getStateIndex(subState);
    }
    
    public String getStateName(final int index) {
        return this.mainStateMap.getStateName(index);
    }
    
    public String getSubStateName(final int index, final int subState) {
        return this.subStateMap.get(index).getStateName(subState);
    }
    
    @Nullable
    public String getCurrentParentState() {
        if (this.currentParentState.isEmpty()) {
            return null;
        }
        return this.currentParentState.peek().state;
    }
    
    public void increaseDepth() {
        ++this.depth;
    }
    
    public void decreaseDepth() {
        --this.depth;
        if (!this.currentParentState.isEmpty() && this.depth < this.currentParentState.peek().depth) {
            this.currentParentState.pop();
        }
    }
    
    public void setDefaultSubState(final String subState) {
        this.defaultSubState = subState;
    }
    
    public String getDefaultSubState() {
        return this.defaultSubState;
    }
    
    public void setNotComponent() {
        this.mainStateMap.getAndPutSensorIndex("start");
        this.mainStateMap.getAndPutSetterIndex("start");
        this.component = false;
    }
    
    public boolean isComponent() {
        return this.component;
    }
    
    public boolean hasComponentStates() {
        return this.component && this.mainStateMap != null;
    }
    
    public void initialiseComponentState(@Nonnull final BuilderSupport support) {
        support.setToNewComponent();
        support.addComponentLocalStateMachine(this.defaultComponentLocalStateIndex);
        if (this.componentLocalStateAutoReset) {
            support.setLocalStateMachineAutoReset();
        }
    }
    
    public void popComponentState(@Nonnull final BuilderSupport support) {
        support.popComponent();
    }
    
    public void readComponentDefaultLocalState(@Nonnull final JsonObject data) {
        final String state = BuilderBase.readString(data, "DefaultState", null);
        if (state != null) {
            final StateStringValidator validator = StateStringValidator.get();
            if (!validator.test(state)) {
                throw new IllegalStateException(validator.errorMessage(state));
            }
            if (validator.hasMainState()) {
                throw new IllegalStateException(String.format("Default component local state must be defined with a '.' prefix: %s", validator.getMainState()));
            }
            this.defaultComponentLocalState = validator.getSubState();
            this.defaultComponentLocalStateIndex = this.mainStateMap.getAndPutSetterIndex(this.defaultComponentLocalState);
        }
        final JsonElement resetValue = data.get("ResetOnStateChange");
        if (resetValue != null) {
            this.componentLocalStateAutoReset = BuilderBase.expectBooleanElement(resetValue, "ResetOnStateChange");
        }
    }
    
    public boolean hasDefaultLocalState() {
        return this.defaultComponentLocalState != null;
    }
    
    public String getDefaultLocalState() {
        return this.defaultComponentLocalState;
    }
    
    public void setComponentImportStateMappings(@Nonnull final JsonArray states) {
        (this.componentImportStateMappings = new Object2IntOpenHashMap<String>()).defaultReturnValue(Integer.MIN_VALUE);
        final StateStringValidator validator = StateStringValidator.mainStateOnly();
        for (int i = 0; i < states.size(); ++i) {
            final String string = states.get(i).getAsString();
            if (!validator.test(string)) {
                throw new IllegalStateException(validator.errorMessage(string));
            }
            this.getAndPutSensorIndex(validator.getMainState(), null, (m, s) -> {});
            this.componentImportStateMappings.put(validator.getMainState(), i);
        }
        this.componentImportStateMappings.trim();
    }
    
    public int getComponentImportStateIndex(final String state) {
        return (this.componentImportStateMappings == null) ? Integer.MIN_VALUE : this.componentImportStateMappings.getInt(state);
    }
    
    public int importedStateCount() {
        return (this.componentImportStateMappings == null) ? 0 : this.componentImportStateMappings.size();
    }
    
    public void setRequiresStateEvaluator() {
        this.requiresStateEvaluator = true;
    }
    
    public void setHasStateEvaluator() {
        this.hasStateEvaluator = true;
    }
    
    public void optimise() {
        this.currentParentState = null;
        if (this.mainStateMap.isEmpty()) {
            this.mainStateMap = null;
            this.subStateMap = null;
            return;
        }
        final ObjectIterator<Int2ObjectMap.Entry<IStateMap>> iterator = Int2ObjectMaps.fastIterator(this.subStateMap);
        while (iterator.hasNext()) {
            final Int2ObjectMap.Entry<IStateMap> next = iterator.next();
            final IStateMap map = next.getValue();
            if (map.size() == 1) {
                if (this.singletonDefaultStateMap == null) {
                    this.singletonDefaultStateMap = new SingletonStateMap(this.defaultSubState);
                }
                next.setValue(this.singletonDefaultStateMap);
            }
            else {
                map.optimise();
            }
        }
        this.subStateMap.trim();
        this.mainStateMap.optimise();
        this.allMainStates = this.mainStateMap.stateNameMap.keySet().toIntArray();
    }
    
    private static class StateDepth
    {
        private final int depth;
        private final String state;
        
        private StateDepth(final int depth, final String state) {
            this.depth = depth;
            this.state = state;
        }
    }
    
    private static class StateMap implements IStateMap
    {
        private final Int2ObjectOpenHashMap<String> stateNameMap;
        @Nonnull
        private final Object2IntOpenHashMap<String> stateIndexMap;
        private int stateIndexSource;
        @Nullable
        private BitSet stateSensors;
        @Nullable
        private BitSet stateSetters;
        @Nullable
        private BitSet stateRequirers;
        
        private StateMap() {
            this.stateNameMap = new Int2ObjectOpenHashMap<String>();
            this.stateSensors = new BitSet();
            this.stateSetters = new BitSet();
            this.stateRequirers = new BitSet();
            (this.stateIndexMap = new Object2IntOpenHashMap<String>()).defaultReturnValue(Integer.MIN_VALUE);
        }
        
        private int getOrCreateIndex(final String name) {
            int index = this.stateIndexMap.getInt(name);
            if (index == Integer.MIN_VALUE) {
                index = this.stateIndexSource++;
                this.stateIndexMap.put(name, index);
                this.stateNameMap.put(index, name);
            }
            return index;
        }
        
        @Override
        public int getAndPutSensorIndex(final String state) {
            final int index = this.getOrCreateIndex(state);
            this.stateSensors.set(index);
            return index;
        }
        
        @Override
        public int getAndPutSetterIndex(final String targetState) {
            final int index = this.getOrCreateIndex(targetState);
            this.stateSetters.set(index);
            return index;
        }
        
        @Override
        public int getAndPutRequirerIndex(final String targetState) {
            final int index = this.getOrCreateIndex(targetState);
            this.stateRequirers.set(index);
            return index;
        }
        
        @Override
        public int getStateIndex(final String state) {
            Objects.requireNonNull(state, "State must not be null when fetching index");
            return this.stateIndexMap.getInt(state);
        }
        
        @Override
        public String getStateName(final int index) {
            return this.stateNameMap.get(index);
        }
        
        @Override
        public void validate(final String configName, @Nullable final String parent, @Nonnull final List<String> errors) {
            this.stateSetters.xor(this.stateSensors);
            if (this.stateSetters.cardinality() > 0) {
                errors.add(String.format("%s: State sensor or State setter action/motion exists without accompanying state/setter: %s%s", configName, (parent != null) ? parent : "", this.stateNameMap.get(this.stateSetters.nextSetBit(0))));
            }
            this.stateRequirers.andNot(this.stateSensors);
            if (this.stateRequirers.cardinality() > 0) {
                errors.add(String.format("%s: State required by a parameter does not exist: %s%s", configName, (parent != null) ? parent : "", this.stateNameMap.get(this.stateRequirers.nextSetBit(0))));
            }
        }
        
        @Override
        public boolean isEmpty() {
            return this.stateNameMap.isEmpty();
        }
        
        @Override
        public int size() {
            return this.stateNameMap.size();
        }
        
        @Override
        public void optimise() {
            this.stateSensors = null;
            this.stateSetters = null;
            this.stateRequirers = null;
            this.stateNameMap.trim();
            this.stateIndexMap.trim();
        }
    }
    
    private static class SingletonStateMap implements IStateMap
    {
        private final String stateName;
        
        private SingletonStateMap(final String name) {
            this.stateName = name;
        }
        
        @Override
        public int getAndPutSensorIndex(final String state) {
            return 0;
        }
        
        @Override
        public int getAndPutSetterIndex(final String targetState) {
            return 0;
        }
        
        @Override
        public int getAndPutRequirerIndex(final String targetState) {
            return 0;
        }
        
        @Override
        public int getStateIndex(@Nonnull final String state) {
            if (!state.equals(this.stateName)) {
                return Integer.MIN_VALUE;
            }
            return 0;
        }
        
        @Override
        public String getStateName(final int index) {
            return this.stateName;
        }
        
        @Override
        public void validate(final String configName, final String parent, final List<String> errors) {
        }
        
        @Override
        public boolean isEmpty() {
            return false;
        }
        
        @Override
        public int size() {
            return 1;
        }
        
        @Override
        public void optimise() {
        }
    }
    
    private interface IStateMap
    {
        int getAndPutSensorIndex(final String p0);
        
        int getAndPutSetterIndex(final String p0);
        
        int getAndPutRequirerIndex(final String p0);
        
        int getStateIndex(final String p0);
        
        String getStateName(final int p0);
        
        void validate(final String p0, final String p1, final List<String> p2);
        
        boolean isEmpty();
        
        int size();
        
        void optimise();
    }
}
