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

package com.hypixel.hytale.builtin.buildertools.scriptedbrushes;

import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.BrushOperationSetting;
import java.util.Collection;
import com.hypixel.hytale.server.core.util.message.MessageFormat;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Set;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import java.util.logging.Level;
import java.util.Iterator;
import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation;
import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.component.ComponentAccessor;
import javax.annotation.Nullable;
import java.util.function.Consumer;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation;
import java.util.List;
import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation;
import javax.annotation.Nonnull;
import java.util.Map;
import com.hypixel.hytale.logger.HytaleLogger;

public class BrushConfigCommandExecutor
{
    private static final HytaleLogger LOGGER;
    @Nonnull
    private final Map<String, Integer> persistentStoredVariables;
    private final BrushConfig brushConfig;
    @Nonnull
    private final Map<String, GlobalBrushOperation> globalOperations;
    private int currentOperationIndex;
    @Nonnull
    private final List<SequenceBrushOperation> sequentialOperations;
    private boolean inDebugSteppingMode;
    private boolean printOperations;
    private boolean enableBreakpoints;
    private DebugOutputTarget debugOutputTarget;
    private boolean breakOnError;
    @Nonnull
    private final Map<String, BrushConfig> brushConfigStoredSnapshots;
    private boolean allowOverwritingSavedSnapshots;
    @Nonnull
    private final Map<String, Integer> storedIndexes;
    private boolean ignoreExistingBrushData;
    private BrushConfigEditStore edit;
    private long startTime;
    
    public BrushConfigCommandExecutor(final BrushConfig brushConfig) {
        this.currentOperationIndex = 0;
        this.debugOutputTarget = DebugOutputTarget.Chat;
        this.allowOverwritingSavedSnapshots = true;
        this.persistentStoredVariables = new Object2IntOpenHashMap<String>();
        this.sequentialOperations = new ObjectArrayList<SequenceBrushOperation>();
        this.globalOperations = new Object2ObjectOpenHashMap<String, GlobalBrushOperation>();
        this.brushConfigStoredSnapshots = new Object2ObjectOpenHashMap<String, BrushConfig>();
        this.storedIndexes = new Object2IntOpenHashMap<String>();
        this.brushConfig = brushConfig;
    }
    
    public void resetInternalState() {
        this.currentOperationIndex = 0;
        this.brushConfigStoredSnapshots.clear();
        this.ignoreExistingBrushData = false;
        this.printOperations = false;
        this.inDebugSteppingMode = false;
        this.enableBreakpoints = false;
        this.debugOutputTarget = DebugOutputTarget.Chat;
        this.breakOnError = false;
        this.startTime = System.nanoTime();
        this.storedIndexes.clear();
    }
    
    public void execute(@Nonnull final Ref<EntityStore> ref, @Nonnull final World world, @Nonnull final Vector3i origin, final boolean isHoldDownInteraction, @Nonnull final InteractionType interactionType, @Nullable final Consumer<BrushConfig> existingBrushDataLoadingConsumer, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType());
        assert playerComponent != null;
        final UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType());
        assert uuidComponent != null;
        this.resetInternalState();
        this.brushConfig.resetToDefaultValues();
        this.brushConfig.beginExecution(origin, isHoldDownInteraction, interactionType);
        final PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid());
        if (!isHoldDownInteraction) {
            prototypePlayerBuilderToolSettings.getIgnoredPaintOperations().clear();
        }
        this.edit = new BrushConfigEditStore(prototypePlayerBuilderToolSettings.addIgnoredPaintOperation(), this.brushConfig, world);
        for (int i = 0; i < this.sequentialOperations.size(); ++i) {
            this.sequentialOperations.get(i).resetInternalState();
            this.sequentialOperations.get(i).preExecutionModifyBrushConfig(this, i);
        }
        for (final GlobalBrushOperation globalOperation : this.globalOperations.values()) {
            globalOperation.resetInternalState();
            globalOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor);
        }
        if (!this.ignoreExistingBrushData && existingBrushDataLoadingConsumer != null) {
            existingBrushDataLoadingConsumer.accept(this.brushConfig);
        }
        if (!this.inDebugSteppingMode) {
            while (this.brushConfig.isCurrentlyExecuting() && !this.inDebugSteppingMode && this.step(ref, false, componentAccessor).equals(BrushConfig.BCExecutionStatus.Continue)) {}
        }
    }
    
    public void execute(@Nonnull final Ref<EntityStore> ref, final World world, final Vector3i origin, final boolean isHoldDownInteraction, final InteractionType interactionType, final ComponentAccessor<EntityStore> componentAccessor) {
        this.execute(ref, world, origin, isHoldDownInteraction, interactionType, null, componentAccessor);
    }
    
    @Nonnull
    public BrushConfig.BCExecutionStatus step(final Ref<EntityStore> ref, final boolean placePreviewAfterStep, final ComponentAccessor<EntityStore> componentAccessor) {
        if (!this.brushConfig.isCurrentlyExecuting()) {
            return BrushConfig.BCExecutionStatus.Error;
        }
        if (this.sequentialOperations.isEmpty()) {
            this.brushConfig.setErrorFlag("No operations to execute");
            return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
        }
        try {
            final SequenceBrushOperation brushOperation = this.sequentialOperations.get(this.currentOperationIndex);
            if (this.printOperations) {
                BrushConfigCommandExecutor.LOGGER.at(Level.INFO).log("[%d] %s", this.currentOperationIndex, brushOperation.getName());
                final PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
                if (playerRefComponent != null) {
                    playerRefComponent.sendMessage(Message.translation("server.builderTools.brushConfig.debug.operationExecuting").param("index", this.currentOperationIndex).param("name", brushOperation.getName()));
                }
            }
            brushOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor);
            if (this.brushConfig.isHasExecutionContextEncounteredError() || !brushOperation.doesOperateOnBlocks()) {
                return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
            }
            for (int numModifyBlockIterations = brushOperation.getNumModifyBlockIterations(), i = 0; i < numModifyBlockIterations; ++i) {
                brushOperation.beginIterationIndex(i);
                ToolOperation.executeShapeOperation(this.brushConfig.getOriginAfterOffset().x, this.brushConfig.getOriginAfterOffset().y, this.brushConfig.getOriginAfterOffset().z, (x, y, z, unused) -> brushOperation.modifyBlocks(ref, this.brushConfig, this, this.edit, x, y, z, componentAccessor), this.brushConfig.getShape(), this.brushConfig.getShapeWidth(), this.brushConfig.getShapeHeight(), this.brushConfig.getShapeThickness(), this.brushConfig.isCapped());
                this.edit.flushCurrentEditsToPrevious();
            }
        }
        catch (final Exception e) {
            e.printStackTrace();
            this.brushConfig.setErrorFlag(e.getMessage());
        }
        return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
    }
    
    @Nonnull
    private BrushConfig.BCExecutionStatus completeStep(final Ref<EntityStore> ref, final boolean placePreviewAfterStep, final ComponentAccessor<EntityStore> componentAccessor) {
        if (this.brushConfig.isHasExecutionContextEncounteredError()) {
            if (this.breakOnError) {
                final PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
                if (playerRefComponent != null) {
                    playerRefComponent.sendMessage(Message.translation("server.builderTools.brushConfig.debug.breakOnErrorTriggered").param("index", this.currentOperationIndex));
                }
                BrushConfigCommandExecutor.LOGGER.at(Level.INFO).log("[Breakpoint] Error at operation #%d - Entering step-through mode", this.currentOperationIndex);
                this.inDebugSteppingMode = true;
                this.brushConfig.clearError();
                return BrushConfig.BCExecutionStatus.Continue;
            }
            this.exitExecution(ref, componentAccessor);
            return BrushConfig.BCExecutionStatus.Error;
        }
        else {
            ++this.currentOperationIndex;
            if (this.currentOperationIndex >= this.sequentialOperations.size()) {
                this.exitExecution(ref, componentAccessor);
                return BrushConfig.BCExecutionStatus.Complete;
            }
            final Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType());
            assert playerComponent != null;
            final PlayerRef playerRefComponent2 = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
            assert playerRefComponent2 != null;
            if (placePreviewAfterStep) {
                final BrushConfigEditStore returnEdit = this.edit;
                BuilderToolsPlugin.getState(playerComponent, playerRefComponent2).addToQueue((r, s, c) -> s.placeBrushConfig(r, this.startTime, returnEdit, c));
            }
            return BrushConfig.BCExecutionStatus.Continue;
        }
    }
    
    public void exitExecution(final Ref<EntityStore> ref, final ComponentAccessor<EntityStore> componentAccessor) {
        final Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType());
        assert playerComponent != null;
        final PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
        assert playerRefComponent != null;
        if (this.brushConfig.isHasExecutionContextEncounteredError() && this.currentOperationIndex < this.sequentialOperations.size()) {
            this.sendExecutionErrorMessage(playerRefComponent, this.sequentialOperations.get(this.currentOperationIndex));
        }
        if (this.currentOperationIndex != 0) {
            final BrushConfigEditStore returnEdit = this.edit;
            BuilderToolsPlugin.getState(playerComponent, playerRefComponent).addToQueue((r, s, compAccess) -> s.placeBrushConfig(r, this.startTime, returnEdit, compAccess));
        }
        this.brushConfig.endExecution();
    }
    
    private void sendExecutionErrorMessage(final PlayerRef playerRef, @Nonnull final SequenceBrushOperation brushOperation) {
        playerRef.sendMessage(Message.translation("server.builderTools.brushConfig.executionError").param("index", this.currentOperationIndex).param("type", brushOperation.getName()).param("error", this.brushConfig.getExecutionErrorMessage()));
        final Message header = Message.translation("server.builderTools.brushConfig.settings.header");
        final Set<Message> items = brushOperation.getRegisteredOperationSettings().values().stream().map(value -> Message.translation("server.builderTools.brushConfig.settings.item").param("name", value.getName()).param("value", value.getValueString())).collect((Collector<? super Object, ?, Set<Message>>)Collectors.toSet());
        playerRef.sendMessage(MessageFormat.list(header, items));
    }
    
    public void storeOperatingIndex(String name, final int index) {
        name = name.toLowerCase();
        if (this.storedIndexes.containsKey(name)) {
            this.brushConfig.setErrorFlag("You already have a stored index with the name: '" + name + "'.");
            return;
        }
        if (index >= this.sequentialOperations.size()) {
            this.brushConfig.setErrorFlag("Tried to store an index greater than the total size of sequential operations. Name: '" + name + "', index: '" + index + "'.");
            return;
        }
        this.storedIndexes.put(name, index);
    }
    
    public void loadOperatingIndex(final String name) {
        this.loadOperatingIndex(name, true);
    }
    
    public void loadOperatingIndex(String name, final boolean allowFutureJump) {
        name = name.toLowerCase();
        if (!this.storedIndexes.containsKey(name)) {
            this.brushConfig.setErrorFlag("Could not find a stored index with the name: '" + name + "'.");
            return;
        }
        final int newIndex = this.storedIndexes.get(name);
        if (!allowFutureJump && newIndex > this.currentOperationIndex) {
            this.brushConfig.setErrorFlag("This operation does not allow you to jump to an operation in the future, only the past. Index name: " + name + "'.");
            return;
        }
        this.currentOperationIndex = newIndex;
    }
    
    public void clearAllPersistentVariables() {
        this.persistentStoredVariables.clear();
    }
    
    public void clearPersistentVariable(String variableName) {
        variableName = variableName.toLowerCase();
        if (!this.persistentStoredVariables.containsKey(variableName)) {
            this.brushConfig.setErrorFlag("Could not find a stored persistent variable with the name: '" + variableName + "'.");
            return;
        }
        this.persistentStoredVariables.remove(variableName);
    }
    
    public void setPersistentVariable(String variableName, final int value) {
        variableName = variableName.toLowerCase();
        this.persistentStoredVariables.put(variableName, value);
    }
    
    public int getPersistentVariableOrDefault(String variableName, final int defaultValue) {
        variableName = variableName.toLowerCase();
        if (!this.persistentStoredVariables.containsKey(variableName)) {
            return defaultValue;
        }
        return this.persistentStoredVariables.get(variableName);
    }
    
    public void storeBrushConfigSnapshot(@Nonnull final String name) {
        this.brushConfigStoredSnapshots.put(name.toLowerCase(), new BrushConfig(this.brushConfig));
    }
    
    public void loadBrushConfigSnapshot(String name, @Nonnull final BrushConfig.DataSettingFlags... dataToLoad) {
        name = name.toLowerCase();
        if (!this.brushConfigStoredSnapshots.containsKey(name)) {
            this.brushConfig.setErrorFlag("Could not find a stored brush config snapshot with the name: '" + name + "'.");
            return;
        }
        final BrushConfig loadedBrushConfig = this.brushConfigStoredSnapshots.get(name);
        for (final BrushConfig.DataSettingFlags dataLoadFlag : dataToLoad) {
            dataLoadFlag.loadData(this.brushConfig, loadedBrushConfig);
        }
    }
    
    public void setAllowOverwritingSavedSnapshots(final boolean allowOverwritingSavedSnapshots) {
        this.allowOverwritingSavedSnapshots = allowOverwritingSavedSnapshots;
    }
    
    @Nonnull
    public List<SequenceBrushOperation> getSequentialOperations() {
        return this.sequentialOperations;
    }
    
    @Nonnull
    public Map<String, GlobalBrushOperation> getGlobalOperations() {
        return this.globalOperations;
    }
    
    public boolean isIgnoreExistingBrushData() {
        return this.ignoreExistingBrushData;
    }
    
    public boolean isInDebugSteppingMode() {
        return this.inDebugSteppingMode;
    }
    
    public BrushConfigEditStore getEdit() {
        return this.edit;
    }
    
    public void setInDebugSteppingMode(final boolean inDebugSteppingMode) {
        this.inDebugSteppingMode = inDebugSteppingMode;
    }
    
    public void setPrintOperations(final boolean printOperations) {
        this.printOperations = printOperations;
    }
    
    public void setIgnoreExistingBrushData(final boolean ignoreExistingBrushData) {
        this.ignoreExistingBrushData = ignoreExistingBrushData;
    }
    
    public void setCurrentlyExecutingActionIndex(final int newCurrentOperationIndex) {
        if (newCurrentOperationIndex < 0) {
            this.brushConfig.setErrorFlag("Cannot set a negative operation index: " + newCurrentOperationIndex);
            return;
        }
        if (newCurrentOperationIndex >= this.sequentialOperations.size()) {
            this.brushConfig.setErrorFlag("Cannot set an operation index higher than the highest operation index: " + newCurrentOperationIndex + ". Highest operation index: " + (this.sequentialOperations.size() - 1));
            return;
        }
        this.currentOperationIndex = newCurrentOperationIndex - 1;
    }
    
    public int getCurrentOperationIndex() {
        return this.currentOperationIndex;
    }
    
    public boolean isEnableBreakpoints() {
        return this.enableBreakpoints;
    }
    
    public void setEnableBreakpoints(final boolean enableBreakpoints) {
        this.enableBreakpoints = enableBreakpoints;
    }
    
    public DebugOutputTarget getDebugOutputTarget() {
        return this.debugOutputTarget;
    }
    
    public void setDebugOutputTarget(final DebugOutputTarget debugOutputTarget) {
        this.debugOutputTarget = debugOutputTarget;
    }
    
    public boolean isBreakOnError() {
        return this.breakOnError;
    }
    
    public void setBreakOnError(final boolean breakOnError) {
        this.breakOnError = breakOnError;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public enum DebugOutputTarget
    {
        Chat, 
        Console, 
        Both;
    }
}
