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

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

import com.hypixel.hytale.common.util.ArrayUtil;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;

public class StdScope implements Scope
{
    protected static final SymbolStringArray VAR_EMPTY_STRING_ARRAY;
    protected static final SymbolNumberArray VAR_EMPTY_NUMBER_ARRAY;
    protected static final SymbolBooleanArray VAR_EMPTY_BOOLEAN_ARRAY;
    protected static final SymbolStringArray VAR_NULL_STRING_ARRAY;
    protected static final SymbolNumberArray VAR_NULL_NUMBER_ARRAY;
    protected static final SymbolBooleanArray VAR_NULL_BOOLEAN_ARRAY;
    protected static final SymbolString VAR_NULL_STRING;
    protected static final SymbolString VAR_EMPTY_STRING;
    protected static final SymbolBoolean VAR_BOOLEAN_TRUE;
    protected static final SymbolBoolean VAR_BOOLEAN_FALSE;
    protected static final SymbolStringArray CONST_EMPTY_STRING_ARRAY;
    protected static final SymbolNumberArray CONST_EMPTY_NUMBER_ARRAY;
    protected static final SymbolBooleanArray CONST_EMPTY_BOOLEAN_ARRAY;
    protected static final SymbolStringArray CONST_NULL_STRING_ARRAY;
    protected static final SymbolNumberArray CONST_NULL_NUMBER_ARRAY;
    protected static final SymbolBooleanArray CONST_NULL_BOOLEAN_ARRAY;
    protected static final SymbolString CONST_NULL_STRING;
    protected static final SymbolString CONST_EMPTY_STRING;
    protected static final SymbolBoolean CONST_BOOLEAN_TRUE;
    protected static final SymbolBoolean CONST_BOOLEAN_FALSE;
    protected Scope parent;
    protected Map<String, Symbol> symbolTable;
    
    public StdScope(final Scope parent) {
        this.parent = parent;
        this.symbolTable = new HashMap<String, Symbol>();
    }
    
    @Nonnull
    public static StdScope copyOf(@Nonnull final StdScope other) {
        final StdScope scope = new StdScope(other.parent);
        scope.mergeSymbols(other);
        return scope;
    }
    
    @Nonnull
    public StdScope merge(@Nonnull final StdScope other) {
        this.mergeSymbols(other);
        return this;
    }
    
    @Nonnull
    public static StdScope mergeScopes(@Nonnull final StdScope first, @Nonnull final StdScope second) {
        return copyOf(first).merge(second);
    }
    
    protected void mergeSymbols(@Nonnull final StdScope other) {
        other.symbolTable.forEach(this::add);
    }
    
    protected void add(final String name, final Symbol symbol) {
        if (this.symbolTable.containsKey(name)) {
            throw new IllegalStateException("Trying to add symbol twice to scope " + name);
        }
        this.symbolTable.put(name, symbol);
    }
    
    public void addConst(final String name, @Nullable final String value) {
        if (value == null) {
            this.add(name, StdScope.CONST_NULL_STRING);
        }
        else if (value.isEmpty()) {
            this.add(name, StdScope.CONST_EMPTY_STRING);
        }
        else {
            this.add(name, new SymbolString(true, () -> value));
        }
    }
    
    public void addConst(final String name, final double value) {
        this.add(name, new SymbolNumber(true, () -> value));
    }
    
    public void addConst(final String name, final boolean value) {
        this.add(name, value ? StdScope.CONST_BOOLEAN_TRUE : StdScope.CONST_BOOLEAN_FALSE);
    }
    
    public void addConst(final String name, @Nullable final String[] value) {
        if (value == null) {
            this.add(name, StdScope.CONST_NULL_STRING_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.CONST_EMPTY_STRING_ARRAY);
        }
        else {
            this.add(name, new SymbolStringArray(true, () -> value));
        }
    }
    
    public void addConst(final String name, @Nullable final double[] value) {
        if (value == null) {
            this.add(name, StdScope.CONST_NULL_NUMBER_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.CONST_EMPTY_NUMBER_ARRAY);
        }
        else {
            this.add(name, new SymbolNumberArray(true, () -> value));
        }
    }
    
    public void addConst(final String name, @Nullable final boolean[] value) {
        if (value == null) {
            this.add(name, StdScope.CONST_NULL_BOOLEAN_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.CONST_EMPTY_BOOLEAN_ARRAY);
        }
        else {
            this.add(name, new SymbolBooleanArray(true, () -> value));
        }
    }
    
    public void addConstEmptyArray(final String name) {
        this.add(name, new Symbol(true, ValueType.EMPTY_ARRAY));
    }
    
    public void addVar(final String name, @Nullable final String value) {
        if (value == null) {
            this.add(name, StdScope.VAR_NULL_STRING);
        }
        else if (value.isEmpty()) {
            this.add(name, StdScope.VAR_EMPTY_STRING);
        }
        else {
            this.add(name, new SymbolString(false, () -> value));
        }
    }
    
    public void addVar(final String name, final double value) {
        this.add(name, new SymbolNumber(false, () -> value));
    }
    
    public void addVar(final String name, final boolean value) {
        this.add(name, value ? StdScope.VAR_BOOLEAN_TRUE : StdScope.VAR_BOOLEAN_FALSE);
    }
    
    public void addVar(final String name, @Nullable final String[] value) {
        if (value == null) {
            this.add(name, StdScope.VAR_NULL_STRING_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.VAR_EMPTY_STRING_ARRAY);
        }
        else {
            this.add(name, new SymbolStringArray(false, () -> value));
        }
    }
    
    public void addVar(final String name, @Nullable final double[] value) {
        if (value == null) {
            this.add(name, StdScope.VAR_NULL_NUMBER_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.VAR_EMPTY_NUMBER_ARRAY);
        }
        else {
            this.add(name, new SymbolNumberArray(false, () -> value));
        }
    }
    
    public void addVar(final String name, @Nullable final boolean[] value) {
        if (value == null) {
            this.add(name, StdScope.VAR_NULL_BOOLEAN_ARRAY);
        }
        else if (value.length == 0) {
            this.add(name, StdScope.VAR_EMPTY_BOOLEAN_ARRAY);
        }
        else {
            this.add(name, new SymbolBooleanArray(false, () -> value));
        }
    }
    
    public void addInvariant(@Nonnull final String name, final Function function, final ValueType returnType, @Nonnull final ValueType... argumentTypes) {
        this.add(Scope.encodeFunctionName(name, argumentTypes), new SymbolFunction(true, returnType, function));
        this.add(name, new SymbolFunction(false, returnType, null));
    }
    
    public void addVariant(@Nonnull final String name, final Function function, final ValueType returnType, @Nonnull final ValueType... argumentTypes) {
        this.add(Scope.encodeFunctionName(name, argumentTypes), new SymbolFunction(false, returnType, function));
        this.add(name, new SymbolFunction(false, returnType, null));
    }
    
    public void addSupplier(final String name, final Supplier<String> value) {
        this.add(name, new SymbolString(false, value));
    }
    
    public void addSupplier(final String name, final DoubleSupplier value) {
        this.add(name, new SymbolNumber(false, value));
    }
    
    public void addSupplier(final String name, final BooleanSupplier value) {
        this.add(name, new SymbolBoolean(false, value));
    }
    
    public void addStringArraySupplier(final String name, final Supplier<String[]> value) {
        this.add(name, new SymbolStringArray(false, value));
    }
    
    public void addDoubleArraySupplier(final String name, final Supplier<double[]> value) {
        this.add(name, new SymbolNumberArray(false, value));
    }
    
    public void addBooleanArraySupplier(final String name, final Supplier<boolean[]> value) {
        this.add(name, new SymbolBooleanArray(false, value));
    }
    
    protected Symbol get(final String name) {
        return this.symbolTable.get(name);
    }
    
    @Nonnull
    protected Symbol get(final String name, final ValueType valueType) {
        final Symbol symbol = this.symbolTable.get(name);
        if (symbol == null) {
            throw new IllegalStateException("Can't find symbol " + name + " in symbol table");
        }
        if (!ValueType.isAssignableType(valueType, symbol.valueType)) {
            throw new IllegalStateException("Type mismatch with " + name + ". Got " + String.valueOf(valueType) + " but expected " + String.valueOf(symbol.valueType));
        }
        return symbol;
    }
    
    protected void replace(final String name, @Nonnull final Symbol symbol) {
        final Symbol oldSymbol = this.get(name, symbol.valueType);
        if (oldSymbol.isConstant) {
            throw new IllegalStateException("Can't replace a constant in symbol table: " + name);
        }
        if (symbol.isConstant) {
            throw new IllegalStateException("Can't replace a variable with a constant: " + name);
        }
        this.symbolTable.put(name, symbol);
    }
    
    public void changeValue(final String name, @Nullable final String value) {
        if (value == null) {
            this.replace(name, StdScope.VAR_NULL_STRING);
        }
        else if (value.isEmpty()) {
            this.replace(name, StdScope.VAR_EMPTY_STRING);
        }
        else {
            this.replace(name, new SymbolString(false, () -> value));
        }
    }
    
    public void changeValue(final String name, final double value) {
        this.replace(name, new SymbolNumber(false, () -> value));
    }
    
    public void changeValue(final String name, final boolean value) {
        this.replace(name, value ? StdScope.VAR_BOOLEAN_TRUE : StdScope.VAR_BOOLEAN_FALSE);
    }
    
    public void changeValue(final String name, @Nullable final String[] value) {
        if (value == null) {
            this.replace(name, StdScope.VAR_NULL_STRING_ARRAY);
        }
        else if (value.length == 0) {
            this.replace(name, StdScope.VAR_EMPTY_STRING_ARRAY);
        }
        else {
            this.replace(name, new SymbolStringArray(false, () -> value));
        }
    }
    
    public void changeValue(final String name, @Nullable final double[] value) {
        if (value == null) {
            this.replace(name, StdScope.VAR_NULL_NUMBER_ARRAY);
        }
        else if (value.length == 0) {
            this.replace(name, StdScope.VAR_EMPTY_NUMBER_ARRAY);
        }
        else {
            this.replace(name, new SymbolNumberArray(false, () -> value));
        }
    }
    
    public void changeValue(final String name, @Nullable final boolean[] value) {
        if (value == null) {
            this.replace(name, StdScope.VAR_NULL_BOOLEAN_ARRAY);
        }
        else if (value.length == 0) {
            this.replace(name, StdScope.VAR_EMPTY_BOOLEAN_ARRAY);
        }
        else {
            this.replace(name, new SymbolBooleanArray(false, () -> value));
        }
    }
    
    public void changeValueToEmptyArray(final String name) {
        final Symbol symbol = this.get(name);
        Objects.requireNonNull(symbol, "Can't find symbol in symbol table in changeValue()");
        if (symbol.isConstant) {
            throw new IllegalStateException("Can't replace a constant in symbol table: " + name);
        }
        switch (symbol.valueType) {
            default: {
                throw new IllegalStateException("Can't assign an empty array to symbol " + name + "  of type " + String.valueOf(symbol.valueType));
            }
            case EMPTY_ARRAY: {
                return;
            }
            case NUMBER_ARRAY: {
                this.symbolTable.put(name, StdScope.VAR_EMPTY_NUMBER_ARRAY);
                break;
            }
            case STRING_ARRAY: {
                this.symbolTable.put(name, StdScope.VAR_EMPTY_STRING_ARRAY);
                break;
            }
            case BOOLEAN_ARRAY: {
                this.symbolTable.put(name, StdScope.VAR_EMPTY_BOOLEAN_ARRAY);
                break;
            }
        }
    }
    
    @Override
    public Supplier<String> getStringSupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getStringSupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol instanceof final SymbolString symbolString) {
                return symbolString.value;
            }
            throw new IllegalStateException("Symbol is not a string: " + name);
        }
    }
    
    @Override
    public DoubleSupplier getNumberSupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getNumberSupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol instanceof final SymbolNumber symbolNumber) {
                return symbolNumber.value;
            }
            throw new IllegalStateException("Symbol is not a number: " + name);
        }
    }
    
    @Override
    public BooleanSupplier getBooleanSupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getBooleanSupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol instanceof final SymbolBoolean symbolBoolean) {
                return symbolBoolean.value;
            }
            throw new IllegalStateException("Symbol is not a boolean: " + name);
        }
    }
    
    @Override
    public Supplier<String[]> getStringArraySupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getStringArraySupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol.valueType == ValueType.EMPTY_ARRAY) {
                return () -> ArrayUtil.EMPTY_STRING_ARRAY;
            }
            if (symbol instanceof final SymbolStringArray symbolStringArray) {
                return symbolStringArray.value;
            }
            throw new IllegalStateException("Symbol is not a string array: " + name);
        }
    }
    
    @Override
    public Supplier<double[]> getNumberArraySupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getNumberArraySupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol.valueType == ValueType.EMPTY_ARRAY) {
                return () -> ArrayUtil.EMPTY_DOUBLE_ARRAY;
            }
            if (symbol instanceof final SymbolNumberArray symbolNumberArray) {
                return symbolNumberArray.value;
            }
            throw new IllegalStateException("Symbol is not a number array: " + name);
        }
    }
    
    @Override
    public Supplier<boolean[]> getBooleanArraySupplier(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getBooleanArraySupplier(name);
            }
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        else {
            if (symbol.valueType == ValueType.EMPTY_ARRAY) {
                return () -> ArrayUtil.EMPTY_BOOLEAN_ARRAY;
            }
            if (symbol instanceof final SymbolBooleanArray symbolBooleanArray) {
                return symbolBooleanArray.value;
            }
            throw new IllegalStateException("Symbol is not a boolean array: " + name);
        }
    }
    
    @Override
    public Function getFunction(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol == null) {
            if (this.parent != null) {
                return this.parent.getFunction(name);
            }
            throw new IllegalStateException("Unable to find function: " + name);
        }
        else {
            if (symbol instanceof final SymbolFunction symbolFunction) {
                return symbolFunction.value;
            }
            throw new IllegalStateException("Symbol is not a function: " + name);
        }
    }
    
    @Override
    public boolean isConstant(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol != null) {
            return symbol.isConstant;
        }
        if (this.parent == null) {
            throw new IllegalStateException("Unable to find symbol: " + name);
        }
        return this.parent.isConstant(name);
    }
    
    @Nullable
    @Override
    public ValueType getType(final String name) {
        final Symbol symbol = this.get(name);
        if (symbol != null) {
            return symbol.valueType;
        }
        if (this.parent != null) {
            return this.parent.getType(name);
        }
        return null;
    }
    
    static {
        VAR_EMPTY_STRING_ARRAY = new SymbolStringArray(false, () -> ArrayUtil.EMPTY_STRING_ARRAY);
        VAR_EMPTY_NUMBER_ARRAY = new SymbolNumberArray(false, () -> ArrayUtil.EMPTY_DOUBLE_ARRAY);
        VAR_EMPTY_BOOLEAN_ARRAY = new SymbolBooleanArray(false, () -> ArrayUtil.EMPTY_BOOLEAN_ARRAY);
        VAR_NULL_STRING_ARRAY = new SymbolStringArray(false, () -> null);
        VAR_NULL_NUMBER_ARRAY = new SymbolNumberArray(false, () -> null);
        VAR_NULL_BOOLEAN_ARRAY = new SymbolBooleanArray(false, () -> null);
        VAR_NULL_STRING = new SymbolString(false, () -> null);
        VAR_EMPTY_STRING = new SymbolString(false, () -> "");
        VAR_BOOLEAN_TRUE = new SymbolBoolean(false, () -> true);
        VAR_BOOLEAN_FALSE = new SymbolBoolean(false, () -> false);
        CONST_EMPTY_STRING_ARRAY = new SymbolStringArray(true, () -> ArrayUtil.EMPTY_STRING_ARRAY);
        CONST_EMPTY_NUMBER_ARRAY = new SymbolNumberArray(true, () -> ArrayUtil.EMPTY_DOUBLE_ARRAY);
        CONST_EMPTY_BOOLEAN_ARRAY = new SymbolBooleanArray(true, () -> ArrayUtil.EMPTY_BOOLEAN_ARRAY);
        CONST_NULL_STRING_ARRAY = new SymbolStringArray(true, () -> null);
        CONST_NULL_NUMBER_ARRAY = new SymbolNumberArray(true, () -> null);
        CONST_NULL_BOOLEAN_ARRAY = new SymbolBooleanArray(true, () -> null);
        CONST_NULL_STRING = new SymbolString(true, () -> null);
        CONST_EMPTY_STRING = new SymbolString(true, () -> "");
        CONST_BOOLEAN_TRUE = new SymbolBoolean(true, () -> true);
        CONST_BOOLEAN_FALSE = new SymbolBoolean(true, () -> false);
    }
    
    protected static class Symbol
    {
        public final boolean isConstant;
        public final ValueType valueType;
        
        public Symbol(final boolean isConstant, final ValueType valueType) {
            this.isConstant = isConstant;
            this.valueType = valueType;
        }
    }
    
    protected static class SymbolString extends Symbol
    {
        public final Supplier<String> value;
        
        public SymbolString(final boolean isConstant, final Supplier<String> value) {
            super(isConstant, ValueType.STRING);
            this.value = value;
        }
    }
    
    protected static class SymbolNumber extends Symbol
    {
        public final DoubleSupplier value;
        
        public SymbolNumber(final boolean isConstant, final DoubleSupplier value) {
            super(isConstant, ValueType.NUMBER);
            this.value = value;
        }
    }
    
    protected static class SymbolBoolean extends Symbol
    {
        public final BooleanSupplier value;
        
        public SymbolBoolean(final boolean isConstant, final BooleanSupplier value) {
            super(isConstant, ValueType.BOOLEAN);
            this.value = value;
        }
    }
    
    protected static class SymbolStringArray extends Symbol
    {
        public final Supplier<String[]> value;
        
        public SymbolStringArray(final boolean isConstant, final Supplier<String[]> value) {
            super(isConstant, ValueType.STRING_ARRAY);
            this.value = value;
        }
    }
    
    protected static class SymbolNumberArray extends Symbol
    {
        public final Supplier<double[]> value;
        
        public SymbolNumberArray(final boolean isConstant, final Supplier<double[]> value) {
            super(isConstant, ValueType.NUMBER_ARRAY);
            this.value = value;
        }
    }
    
    protected static class SymbolBooleanArray extends Symbol
    {
        public final Supplier<boolean[]> value;
        
        public SymbolBooleanArray(final boolean isConstant, final Supplier<boolean[]> value) {
            super(isConstant, ValueType.BOOLEAN_ARRAY);
            this.value = value;
        }
    }
    
    protected static class SymbolFunction extends Symbol
    {
        public final Function value;
        
        public SymbolFunction(final boolean isConstant, final ValueType returnType, final Function value) {
            super(isConstant, returnType);
            this.value = value;
        }
    }
}
