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

package com.hypixel.hytale.server.npc.util.expression.compile;

import javax.annotation.Nullable;
import java.util.EnumSet;
import java.text.ParseException;
import java.util.ArrayDeque;
import javax.annotation.Nonnull;
import java.util.Deque;

public class Parser
{
    public static final String MISMATCHED_CLOSING_BRACKET = "Mismatched closing bracket";
    public static final String TOO_MANY_OPERANDS = "Too many operands";
    public static final String NOT_ENOUGH_OPERANDS = "Not enough operands";
    public static final String EXPECTED_UNARY_OPERATOR = "Expected unary operator";
    public static final String EXPECTED_BINARY_OPERATOR = "Expected binary operator";
    public static final String MISSING_CLOSING_BRACKET = "Missing closing bracket";
    public static final String ILLEGAL_USE_OF_ARGUMENT_LIST = "Illegal use of argument list";
    private Lexer<Token> lexer;
    private LexerContext<Token> context;
    @Nonnull
    private Deque<ParsedToken> operatorStack;
    @Nonnull
    private Deque<ParsedToken> bracketStack;
    
    public Parser(final Lexer<Token> lexer) {
        this.operatorStack = new ArrayDeque<ParsedToken>();
        this.bracketStack = new ArrayDeque<ParsedToken>();
        this.lexer = lexer;
        this.context = new LexerContext<Token>();
    }
    
    @Nonnull
    private ParsedToken nextToken() throws ParseException {
        return ParsedToken.fromLexer(this.lexer, this.context);
    }
    
    public void parse(@Nonnull final String expression, @Nonnull final ParsedTokenConsumer tokenConsumer) throws ParseException {
        this.operatorStack.clear();
        this.bracketStack.clear();
        this.bracketStack.push(new ParsedToken(Token.END));
        this.context.init(expression);
        ParsedToken parsedToken = this.nextToken();
        Token token = parsedToken.token;
        Token lastToken = null;
        ParsedToken bracket = this.bracketStack.peek();
        while (!token.isEndToken()) {
            if (token.isOperand()) {
                tokenConsumer.pushOperand(parsedToken);
                final ParsedToken parsedToken2 = bracket;
                ++parsedToken2.operandCount;
            }
            else if (token.isOpenBracket()) {
                if (token == Token.OPEN_BRACKET) {
                    if (lastToken == Token.IDENTIFIER) {
                        parsedToken.isTuple = true;
                        parsedToken.isFunctionCall = true;
                    }
                }
                else if (token.isOpenTuple()) {
                    parsedToken.isTuple = true;
                    parsedToken.isFunctionCall = false;
                }
                this.operatorStack.push(parsedToken);
                this.bracketStack.push(parsedToken);
                bracket = this.bracketStack.peek();
            }
            else if (token.isCloseBracket()) {
                final Token otherBracket = token.getMatchingBracket();
                if (bracket.token != otherBracket) {
                    throw new ParseException("Mismatched closing bracket", parsedToken.tokenPosition);
                }
                for (ParsedToken first = this.operatorStack.pop(); !first.token.isOpenBracket(); first = this.operatorStack.pop()) {
                    bracket.operandCount = this.adjustOperandCount(first, bracket.operandCount);
                    tokenConsumer.processOperator(first);
                }
                this.validateOperandCount(bracket);
                int deltaArity;
                if (bracket.isFunctionCall) {
                    final ParsedToken parsedToken3 = bracket;
                    parsedToken3.tupleLength += bracket.operandCount;
                    tokenConsumer.processFunction(bracket.tupleLength);
                    deltaArity = 0;
                }
                else if (bracket.isTuple) {
                    final ParsedToken parsedToken4 = bracket;
                    parsedToken4.tupleLength += bracket.operandCount;
                    tokenConsumer.processTuple(bracket, bracket.tupleLength);
                    deltaArity = 1;
                }
                else {
                    deltaArity = 1;
                }
                this.bracketStack.pop();
                final ParsedToken parsedToken5;
                bracket = (parsedToken5 = this.bracketStack.peek());
                parsedToken5.operandCount += deltaArity;
            }
            else if (token.isList()) {
                if (!bracket.isTuple) {
                    throw new ParseException("Illegal use of argument list", parsedToken.tokenPosition);
                }
                for (ParsedToken first2 = this.peekOperator(); !first2.token.isOpenBracket(); first2 = this.peekOperator()) {
                    bracket.operandCount = this.adjustOperandCount(first2, bracket.operandCount);
                    tokenConsumer.processOperator(first2);
                    this.operatorStack.pop();
                }
                this.validateOperandCount(bracket);
                final ParsedToken parsedToken6 = bracket;
                ++parsedToken6.tupleLength;
                bracket.operandCount = 0;
            }
            else {
                if (!token.isOperator()) {
                    throw new RuntimeException("Internal parser error: " + String.valueOf(token));
                }
                final boolean mustBeUnary = lastToken == null || lastToken.containsAnyFlag(EnumSet.of(TokenFlags.OPERATOR, TokenFlags.LIST, TokenFlags.OPENING_BRACKET));
                if (token.canBeUnary() && mustBeUnary) {
                    token = token.getUnaryVariant();
                    parsedToken.token = token;
                }
                else {
                    if (mustBeUnary && !token.isUnary()) {
                        throw new ParseException("Expected unary operator", parsedToken.tokenPosition);
                    }
                    if (token.isUnary() && !mustBeUnary) {
                        throw new ParseException("Expected binary operator", parsedToken.tokenPosition);
                    }
                }
                for (ParsedToken stackToken = this.peekOperator(); this.hasLowerPrecedence(token, stackToken); stackToken = this.peekOperator()) {
                    bracket.operandCount = this.adjustOperandCount(stackToken, bracket.operandCount);
                    tokenConsumer.processOperator(stackToken);
                    this.operatorStack.pop();
                }
                this.operatorStack.push(parsedToken);
            }
            lastToken = token;
            parsedToken = this.nextToken();
            token = parsedToken.token;
        }
        if (bracket.token != Token.END) {
            throw new ParseException("Missing closing bracket", bracket.tokenPosition);
        }
        while (!this.operatorStack.isEmpty()) {
            parsedToken = this.operatorStack.pop();
            bracket.operandCount = this.adjustOperandCount(parsedToken, bracket.operandCount);
            tokenConsumer.processOperator(parsedToken);
        }
        this.validateOperandCount(bracket);
        tokenConsumer.done();
    }
    
    @Nullable
    public ParsedToken peekOperator() {
        return this.operatorStack.isEmpty() ? null : this.operatorStack.peek();
    }
    
    private void validateOperandCount(@Nonnull final ParsedToken bracket) throws ParseException {
        if (bracket.isTuple && bracket.tupleLength == 0 && bracket.operandCount == 0) {
            return;
        }
        if (bracket.operandCount <= 0) {
            throw new ParseException("Not enough operands", 0);
        }
        if (bracket.operandCount > 1) {
            throw new ParseException("Too many operands", 0);
        }
    }
    
    private int adjustOperandCount(@Nonnull final ParsedToken parsedToken, final int operandCount) throws ParseException {
        final int requiredOperands = this.arity(parsedToken.token);
        if (operandCount < requiredOperands) {
            throw new ParseException("Not enough operands", parsedToken.tokenPosition);
        }
        return operandCount - requiredOperands + 1;
    }
    
    private boolean hasLowerPrecedence(@Nonnull final Token token, @Nullable final ParsedToken stackToken) {
        if (stackToken == null || stackToken.token.isList() || stackToken.token.isOpenBracket()) {
            return false;
        }
        final int tokenPrecedence = token.getPrecedence();
        final int stackTokenPrecedence = stackToken.token.getPrecedence();
        return (tokenPrecedence == stackTokenPrecedence) ? (!token.isRightToLeft()) : (tokenPrecedence < stackTokenPrecedence);
    }
    
    private int arity(@Nonnull final Token operator) {
        if (!operator.isOperator()) {
            throw new RuntimeException("Arity only possible with operators");
        }
        return operator.isUnary() ? 1 : 2;
    }
    
    public static class ParsedToken
    {
        @Nullable
        public Token token;
        @Nullable
        public String tokenString;
        public double tokenNumber;
        public int tokenPosition;
        public int operandCount;
        public boolean isTuple;
        public boolean isFunctionCall;
        public int tupleLength;
        
        public ParsedToken(@Nonnull final LexerContext<Token> context) {
            this(context.getToken());
            this.tokenString = context.getTokenString();
            this.tokenNumber = context.getTokenNumber();
            this.tokenPosition = context.getTokenPosition();
        }
        
        public ParsedToken(final Token token) {
            this.token = token;
            this.tokenString = null;
            this.tokenNumber = 0.0;
            this.tokenPosition = 0;
            this.operandCount = 0;
            this.isTuple = false;
            this.isFunctionCall = false;
            this.tupleLength = 0;
        }
        
        @Nonnull
        static ParsedToken fromLexer(@Nonnull final Lexer<Token> lexer, @Nonnull final LexerContext<Token> context) throws ParseException {
            lexer.nextToken(context);
            return new ParsedToken(context);
        }
    }
    
    public interface ParsedTokenConsumer
    {
        void pushOperand(final ParsedToken p0);
        
        void processOperator(final ParsedToken p0) throws ParseException;
        
        void processFunction(final int p0) throws ParseException;
        
        void processTuple(final ParsedToken p0, final int p1);
        
        void done();
    }
}
