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

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

import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import javax.annotation.Nullable;
import java.text.ParseException;
import javax.annotation.Nonnull;
import java.util.stream.Stream;
import java.util.function.Supplier;

public class Lexer<Token extends Supplier<String>>
{
    public static final String UNTERMINATED_STRING = "Unterminated string";
    public static final String INVALID_NUMBER_FORMAT = "Invalid number format";
    public static final String INVALID_CHARACTER_IN_EXPRESSION = "Invalid character in expression :";
    private final Token tokenEnd;
    private final Token tokenIdent;
    private final Token tokenString;
    private final Token tokenNumber;
    private final CharacterSequenceMatcher<Token> characterSequenceMatcher;
    
    public Lexer(final Token tokenEnd, final Token tokenIdent, final Token tokenString, final Token tokenNumber, @Nonnull final Stream<Token> operators) {
        this.tokenEnd = tokenEnd;
        this.tokenIdent = tokenIdent;
        this.tokenString = tokenString;
        this.tokenNumber = tokenNumber;
        this.characterSequenceMatcher = new CharacterSequenceMatcher<Token>();
        operators.forEach(token -> this.characterSequenceMatcher.addToken((Token)token, token.get()));
    }
    
    public Token nextToken(@Nonnull final LexerContext<Token> context) throws ParseException {
        context.resetToken();
        if (!context.eatWhiteSpace()) {
            return context.setToken(this.tokenEnd);
        }
        char ch = context.currentChar();
        if (Character.isLetter(ch) || ch == '_') {
            context.parseIdent(ch);
            return context.setToken(this.tokenIdent);
        }
        if (context.isNumber(ch)) {
            context.parseNumber(ch);
            return context.setToken(this.tokenNumber);
        }
        if (ch == '\"' || ch == '\'') {
            context.parseString(ch);
            return context.setToken(this.tokenString);
        }
        CharacterSequenceMatcher<Token> lastTerminal = null;
        CharacterSequenceMatcher<Token> matcher = this.characterSequenceMatcher.matchLetter(ch);
        int lastValidPosition = context.getPosition();
        while (matcher != null) {
            if (matcher.token != null) {
                lastValidPosition = context.getPosition();
                lastTerminal = matcher;
            }
            ch = context.addTokenCharacter(ch);
            if (!context.haveChar()) {
                break;
            }
            matcher = matcher.matchLetter(ch);
        }
        if (lastTerminal != null) {
            context.adjustPosition(lastValidPosition + 1);
            return context.setToken(lastTerminal.token);
        }
        throw new ParseException("Invalid character in expression :" + ch, context.getTokenPosition());
    }
    
    protected static class CharacterSequenceMatcher<Token>
    {
        @Nullable
        public Token token;
        public char letter;
        @Nullable
        public List<CharacterSequenceMatcher<Token>> children;
        
        public CharacterSequenceMatcher() {
            this.token = null;
            this.letter = '\0';
            this.children = null;
        }
        
        public CharacterSequenceMatcher(final char letter) {
            this.token = null;
            this.letter = letter;
            this.children = null;
        }
        
        protected void addToken(final Token token, final int depth, @Nonnull final String text, final int maxDepth) {
            final char ch = text.charAt(depth);
            if (this.children == null) {
                this.children = new ObjectArrayList<CharacterSequenceMatcher<Token>>();
                this.append(token, depth, text, maxDepth, ch);
                return;
            }
            int index;
            int size;
            for (index = 0, size = this.children.size(); index < size && this.children.get(index).letter < ch; ++index) {}
            if (index == size) {
                this.append(token, depth, text, maxDepth, ch);
            }
            else {
                final CharacterSequenceMatcher<Token> child = this.children.get(index);
                if (child.letter == ch) {
                    if (depth == maxDepth) {
                        if (child.token != null) {
                            throw new RuntimeException("Duplicate operator " + text);
                        }
                        child.token = token;
                    }
                    else {
                        child.addToken(token, depth + 1, text, maxDepth);
                    }
                }
                else {
                    final CharacterSequenceMatcher<Token> lookup = new CharacterSequenceMatcher<Token>(ch);
                    this.children.add(index, lookup);
                    this.addTail(token, depth, text, maxDepth, lookup);
                }
            }
        }
        
        protected void addToken(final Token token, @Nonnull final String text) {
            this.addToken(token, 0, text, text.length() - 1);
        }
        
        private void append(final Token token, final int depth, @Nonnull final String text, final int maxDepth, final char ch) {
            final CharacterSequenceMatcher<Token> lookup = new CharacterSequenceMatcher<Token>(ch);
            this.children.add(lookup);
            this.addTail(token, depth, text, maxDepth, lookup);
        }
        
        private void addTail(final Token token, final int depth, @Nonnull final String text, final int maxDepth, @Nonnull final CharacterSequenceMatcher<Token> lookup) {
            if (depth == maxDepth) {
                lookup.token = token;
            }
            else {
                lookup.addToken(token, depth + 1, text, maxDepth);
            }
        }
        
        @Nullable
        protected CharacterSequenceMatcher<Token> matchLetter(final char ch) {
            if (this.children != null) {
                for (int index = 0, size = this.children.size(); index < size; ++index) {
                    final CharacterSequenceMatcher<Token> characterSequenceMatcher = this.children.get(index);
                    final char letter = characterSequenceMatcher.letter;
                    if (letter == ch) {
                        return characterSequenceMatcher;
                    }
                    if (letter > ch) {
                        return null;
                    }
                }
            }
            return null;
        }
    }
}
