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

package com.hypixel.hytale.server.core.command.system;

import java.util.Collection;
import java.util.Map;
import it.unimi.dsi.fastutil.objects.ObjectSortedSet;
import javax.annotation.Nullable;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.Message;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import javax.annotation.Nonnull;
import java.util.HashSet;

public class ParserContext
{
    private static final HashSet<String> SPECIAL_TOKENS;
    private static final int MAX_LIST_ITEMS = 10;
    @Nonnull
    private final String inputString;
    @Nonnull
    private final BooleanArrayList parameterForwardingMap;
    @Nonnull
    private final Int2ObjectMap<String> preOptionalSingleValueTokens;
    @Nonnull
    private final Int2ObjectMap<PreOptionalListContext> preOptionalListTokens;
    @Nonnull
    private final Object2ObjectLinkedOpenHashMap<String, List<List<String>>> optionalArgs;
    private String lastInsertedOptionalArgName;
    private int numPreOptSingleValueTokensBeforeListTokens;
    private int subCommandIndex;
    private static final Pattern ARG_NAME_PATTERN;
    private static final Matcher ARG_NAME_MATCHER;
    private static final Pattern ARG_NAME_AND_VALUE_PATTERN;
    private static final Matcher ARG_NAME_AND_VALUE_MATCHER;
    
    public ParserContext(@Nonnull final List<String> tokens, @Nonnull final ParseResult parseResult) {
        this.inputString = String.join(" ", tokens);
        this.parameterForwardingMap = new BooleanArrayList();
        this.preOptionalSingleValueTokens = new Int2ObjectOpenHashMap<String>();
        this.preOptionalListTokens = new Int2ObjectOpenHashMap<PreOptionalListContext>();
        this.optionalArgs = new Object2ObjectLinkedOpenHashMap<String, List<List<String>>>();
        this.contextualizeTokens(tokens, parseResult);
    }
    
    @Nonnull
    public static ParserContext of(@Nonnull final List<String> tokens, @Nonnull final ParseResult parseResult) {
        return new ParserContext(tokens, parseResult);
    }
    
    private void contextualizeTokens(@Nonnull final List<String> tokens, @Nonnull final ParseResult parseResult) {
        boolean beganParsingOptionals = false;
        boolean inList = false;
        boolean isSingleValueList = false;
        boolean wasLastTokenASpecialValue = false;
        boolean hasEnteredListBefore = false;
        for (int i = 0; i < tokens.size(); ++i) {
            final String token = tokens.get(i);
            if (inList) {
                hasEnteredListBefore = true;
            }
            if (ParserContext.SPECIAL_TOKENS.contains(token)) {
                final boolean isListEndingAndStartingNew = tokens.get(i - 1).equals(Tokenizer.MULTI_ARG_END) && !inList && token.equals(Tokenizer.MULTI_ARG_BEGIN);
                if (wasLastTokenASpecialValue && !isListEndingAndStartingNew) {
                    final StringBuilder stringBuilder = new StringBuilder();
                    for (int i2 = 0; i2 < tokens.size(); ++i2) {
                        stringBuilder.append(tokens.get(i2)).append(" ");
                        if (i2 == i) {
                            stringBuilder.append(" <--- *HERE!* ");
                        }
                    }
                    parseResult.fail(Message.translation("server.commands.parsing.error.cantDoublePlaceSpecialTokens"), Message.raw(stringBuilder.toString()));
                    return;
                }
                wasLastTokenASpecialValue = true;
            }
            else {
                wasLastTokenASpecialValue = false;
            }
            ParserContext.ARG_NAME_MATCHER.reset(token);
            if (ParserContext.ARG_NAME_MATCHER.lookingAt()) {
                beganParsingOptionals = true;
                this.addNewOptionalArg(ParserContext.ARG_NAME_MATCHER.group(1));
                ParserContext.ARG_NAME_AND_VALUE_MATCHER.reset(token);
                if (ParserContext.ARG_NAME_AND_VALUE_MATCHER.matches()) {
                    this.appendOptionalParameter(ParserContext.ARG_NAME_AND_VALUE_MATCHER.group(2), parseResult);
                    if (parseResult.failed()) {
                        return;
                    }
                }
            }
            else if (beganParsingOptionals) {
                this.appendOptionalParameter(token, parseResult);
                if (parseResult.failed()) {
                    return;
                }
            }
            else if (token.equals(Tokenizer.MULTI_ARG_BEGIN)) {
                inList = true;
                isSingleValueList = false;
                this.parameterForwardingMap.add(true);
                this.preOptionalListTokens.put(this.parameterForwardingMap.size() - 1, new PreOptionalListContext());
            }
            else if (token.equals(Tokenizer.MULTI_ARG_END)) {
                inList = false;
            }
            else if (inList) {
                this.preOptionalListTokens.get(this.parameterForwardingMap.size() - 1).addToken(token, parseResult);
                if (parseResult.failed()) {
                    return;
                }
                if (isSingleValueList && !wasLastTokenASpecialValue && tokens.size() > i + 1 && !tokens.get(i + 1).equals(Tokenizer.MULTI_ARG_SEPARATOR)) {
                    inList = false;
                }
            }
            else if (tokens.size() > i + 1 && tokens.get(i + 1).equals(Tokenizer.MULTI_ARG_SEPARATOR)) {
                inList = true;
                isSingleValueList = true;
                this.parameterForwardingMap.add(true);
                this.preOptionalListTokens.put(this.parameterForwardingMap.size() - 1, new PreOptionalListContext().addToken(token, parseResult));
                if (parseResult.failed()) {
                    return;
                }
            }
            else {
                if (!hasEnteredListBefore) {
                    ++this.numPreOptSingleValueTokensBeforeListTokens;
                }
                this.parameterForwardingMap.add(false);
                this.preOptionalSingleValueTokens.put(this.parameterForwardingMap.size() - 1, token);
            }
        }
        if (inList && !isSingleValueList) {
            parseResult.fail(Message.translation("server.commands.parsing.error.endCommandWithOpenList").param("listEndToken", Tokenizer.MULTI_ARG_END));
        }
    }
    
    public void addNewOptionalArg(String name) {
        name = name.toLowerCase();
        this.lastInsertedOptionalArgName = name;
        this.optionalArgs.put(name, new ObjectArrayList<List<String>>());
    }
    
    public void appendOptionalParameter(@Nonnull final String value, @Nonnull final ParseResult parseResult) {
        if (this.optionalArgs.isEmpty() || this.lastInsertedOptionalArgName == null) {
            parseResult.fail(Message.translation("server.commands.parsing.error.noOptionalParameterToAddValueTo"));
            return;
        }
        final List<List<String>> args = this.optionalArgs.get(this.lastInsertedOptionalArgName);
        if (value.equals(Tokenizer.MULTI_ARG_BEGIN) || value.equals(Tokenizer.MULTI_ARG_END)) {
            return;
        }
        if (value.equals(Tokenizer.MULTI_ARG_SEPARATOR)) {
            args.add(new ObjectArrayList<String>());
        }
        else if (args.isEmpty()) {
            final ObjectArrayList<String> values = new ObjectArrayList<String>();
            values.add(value);
            args.add(values);
        }
        else {
            args.getLast().add(value);
        }
    }
    
    @Nonnull
    public String getInputString() {
        return this.inputString;
    }
    
    public boolean isListToken(int index) {
        index += this.subCommandIndex;
        return this.parameterForwardingMap.size() > index && this.parameterForwardingMap.getBoolean(index);
    }
    
    public int getNumPreOptSingleValueTokensBeforeListTokens() {
        return this.numPreOptSingleValueTokensBeforeListTokens - this.subCommandIndex;
    }
    
    public int getNumPreOptionalTokens() {
        int numPreOptionalTokens = 0;
        numPreOptionalTokens += this.preOptionalSingleValueTokens.size();
        for (final PreOptionalListContext value : this.preOptionalListTokens.values()) {
            numPreOptionalTokens += value.numTokensPerArgument;
        }
        return numPreOptionalTokens - this.subCommandIndex;
    }
    
    public String getPreOptionalSingleValueToken(int index) {
        index += this.subCommandIndex;
        return this.preOptionalSingleValueTokens.get(index);
    }
    
    public PreOptionalListContext getPreOptionalListToken(int index) {
        index += this.subCommandIndex;
        return this.preOptionalListTokens.get(index);
    }
    
    @Nullable
    public String getFirstToken() {
        if (this.parameterForwardingMap.size() <= this.subCommandIndex) {
            return null;
        }
        if (!this.parameterForwardingMap.getBoolean(this.subCommandIndex)) {
            return this.preOptionalSingleValueTokens.get(this.subCommandIndex);
        }
        final PreOptionalListContext preOptionalListContext = this.preOptionalListTokens.get(this.subCommandIndex);
        if (preOptionalListContext.tokens.isEmpty()) {
            return null;
        }
        return preOptionalListContext.tokens.getFirst();
    }
    
    @Nonnull
    public ObjectSortedSet<Map.Entry<String, List<List<String>>>> getOptionalArgs() {
        return this.optionalArgs.entrySet();
    }
    
    public boolean isHelpSpecified() {
        return this.optionalArgs.containsKey("help") || this.optionalArgs.containsKey("?");
    }
    
    public boolean isConfirmationSpecified() {
        return this.optionalArgs.containsKey("confirm");
    }
    
    public void convertToSubCommand() {
        ++this.subCommandIndex;
    }
    
    static {
        SPECIAL_TOKENS = new HashSet<String>(List.of(Tokenizer.MULTI_ARG_BEGIN, Tokenizer.MULTI_ARG_END, Tokenizer.MULTI_ARG_SEPARATOR));
        ARG_NAME_PATTERN = Pattern.compile("--(\\w*)");
        ARG_NAME_MATCHER = ParserContext.ARG_NAME_PATTERN.matcher("");
        ARG_NAME_AND_VALUE_PATTERN = Pattern.compile("--(\\w+)=\"*(.*)\"*");
        ARG_NAME_AND_VALUE_MATCHER = ParserContext.ARG_NAME_AND_VALUE_PATTERN.matcher("");
    }
    
    public static class PreOptionalListContext
    {
        private final List<String> tokens;
        private boolean hasReachedFirstMultiArgSeparator;
        private int numTokensPerArgument;
        private int numTokensSinceLastSeparator;
        private int numberOfListItems;
        
        public PreOptionalListContext() {
            this.tokens = new ObjectArrayList<String>();
            this.hasReachedFirstMultiArgSeparator = false;
            this.numTokensPerArgument = 0;
            this.numTokensSinceLastSeparator = 0;
            this.numberOfListItems = 0;
        }
        
        @Nullable
        public PreOptionalListContext addToken(@Nonnull final String token, @Nonnull final ParseResult parseResult) {
            if (token.equals(Tokenizer.MULTI_ARG_SEPARATOR)) {
                if (!this.hasReachedFirstMultiArgSeparator) {
                    this.hasReachedFirstMultiArgSeparator = true;
                    this.numTokensSinceLastSeparator = 0;
                    ++this.numberOfListItems;
                    this.verifyNumberOfListItems(parseResult);
                    return this;
                }
                if (this.numTokensSinceLastSeparator != this.numTokensPerArgument) {
                    this.tokens.add(token);
                    parseResult.fail(Message.translation("server.commands.parsing.error.allArgumentsInListNeedSameLength").param("error", this.getStringRepresentation(true)));
                    return null;
                }
                this.numTokensSinceLastSeparator = 0;
                ++this.numberOfListItems;
                this.verifyNumberOfListItems(parseResult);
                return this;
            }
            else {
                ++this.numTokensSinceLastSeparator;
                if (this.numberOfListItems == 0) {
                    ++this.numTokensPerArgument;
                }
                if (this.hasReachedFirstMultiArgSeparator && this.numTokensSinceLastSeparator > this.numTokensPerArgument) {
                    this.tokens.add(token);
                    parseResult.fail(Message.translation("server.commands.parsing.error.allArgumentsInListNeedSameLength").param("error", this.getStringRepresentation(true)));
                    return null;
                }
                this.tokens.add(token);
                return this;
            }
        }
        
        @Nonnull
        private String getStringRepresentation(final boolean asTooLongFailure) {
            final StringBuilder stringBuilder = new StringBuilder(Tokenizer.MULTI_ARG_BEGIN);
            for (int i = 0; i < this.tokens.size(); ++i) {
                if (i != 0 && i % this.numTokensPerArgument == 0 && i != this.tokens.size() - 1) {
                    stringBuilder.append(" ").append(Tokenizer.MULTI_ARG_SEPARATOR);
                }
                stringBuilder.append(" ").append(this.tokens.get(i));
            }
            if (asTooLongFailure) {
                stringBuilder.append("<-- *HERE* ... ]");
            }
            else {
                stringBuilder.append(" ]");
            }
            return stringBuilder.toString();
        }
        
        public void verifyNumberOfListItems(@Nonnull final ParseResult parseResult) {
            if (this.numberOfListItems > 10) {
                parseResult.fail(Message.translation("server.commands.parsing.error.tooManyListItems").param("amount", 10));
            }
        }
        
        @Nonnull
        public String[] getTokens() {
            return this.tokens.toArray(String[]::new);
        }
        
        public int getNumTokensPerArgument() {
            return this.numTokensPerArgument;
        }
        
        public int getNumberOfListItems() {
            return this.numberOfListItems;
        }
    }
}
