package com.alibaba.qlexpress4.runtime.operator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.QLPrecedences;
import com.alibaba.qlexpress4.aparser.OperatorFactory;
import com.alibaba.qlexpress4.aparser.ParserOperatorManager;
import com.alibaba.qlexpress4.aparser.QLexer;
import com.alibaba.qlexpress4.exception.ErrorReporter;
import com.alibaba.qlexpress4.exception.UserDefineException;
import com.alibaba.qlexpress4.runtime.QRuntime;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.DivideAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.DivideOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.MinusAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.MinusOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.MultiplyAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.MultiplyOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.PlusAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.PlusOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.RemainderAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.arithmetic.RemainderOperator;
import com.alibaba.qlexpress4.runtime.operator.assign.AssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseAndAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseAndOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseInvertOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseLeftShiftAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseLeftShiftOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseOrAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseOrOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftUnsignedAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftUnsignedOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseXorAssignOperator;
import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseXorOperator;
import com.alibaba.qlexpress4.runtime.operator.collection.InOperator;
import com.alibaba.qlexpress4.runtime.operator.collection.NotInOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.EqualOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.GreaterEqualOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.GreaterOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.LessEqualOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.LessOperator;
import com.alibaba.qlexpress4.runtime.operator.compare.UnequalOperator;
import com.alibaba.qlexpress4.runtime.operator.logic.LogicAndOperator;
import com.alibaba.qlexpress4.runtime.operator.logic.LogicNotOperator;
import com.alibaba.qlexpress4.runtime.operator.logic.LogicOrOperator;
import com.alibaba.qlexpress4.runtime.operator.string.LikeOperator;
import com.alibaba.qlexpress4.runtime.operator.string.NotLikeOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.MinusMinusPrefixUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.MinusMinusSuffixUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.MinusUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.PlusPlusPrefixUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.PlusPlusSuffixUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.PlusUnaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator;
import com.alibaba.qlexpress4.runtime.util.ThrowUtils;

/**
 * @author bingo
 */
public class OperatorManager implements OperatorFactory, ParserOperatorManager {
    private static final Map<String, BinaryOperator> DEFAULT_BINARY_OPERATOR_MAP = new ConcurrentHashMap<>(64);
    
    private static final Map<String, UnaryOperator> DEFAULT_PREFIX_UNARY_OPERATOR_MAP = new ConcurrentHashMap<>(8);
    
    private static final Map<String, UnaryOperator> DEFAULT_SUFFIX_UNARY_OPERATOR_MAP = new ConcurrentHashMap<>(8);
    
    static {
        List<BinaryOperator> binaryOperatorList = new ArrayList<>(64);
        binaryOperatorList.add(AssignOperator.getInstance());
        binaryOperatorList.add(PlusOperator.getInstance());
        binaryOperatorList.add(PlusAssignOperator.getInstance());
        binaryOperatorList.add(MinusOperator.getInstance());
        binaryOperatorList.add(MinusAssignOperator.getInstance());
        binaryOperatorList.add(MultiplyOperator.getInstance());
        binaryOperatorList.add(MultiplyAssignOperator.getInstance());
        binaryOperatorList.add(DivideOperator.getInstance());
        binaryOperatorList.add(DivideAssignOperator.getInstance());
        binaryOperatorList.add(RemainderOperator.getInstance("%"));
        binaryOperatorList.add(RemainderAssignOperator.getInstance());
        //binaryOperatorList.add(RemainderOperator.getInstance("mod"));
        binaryOperatorList.add(BitwiseAndOperator.getInstance());
        binaryOperatorList.add(BitwiseAndAssignOperator.getInstance());
        binaryOperatorList.add(BitwiseOrOperator.getInstance());
        binaryOperatorList.add(BitwiseOrAssignOperator.getInstance());
        binaryOperatorList.add(BitwiseXorOperator.getInstance());
        binaryOperatorList.add(BitwiseXorAssignOperator.getInstance());
        binaryOperatorList.add(BitwiseLeftShiftOperator.getInstance());
        binaryOperatorList.add(BitwiseLeftShiftAssignOperator.getInstance());
        binaryOperatorList.add(BitwiseRightShiftOperator.getInstance());
        binaryOperatorList.add(BitwiseRightShiftAssignOperator.getInstance());
        binaryOperatorList.add(BitwiseRightShiftUnsignedOperator.getInstance());
        binaryOperatorList.add(BitwiseRightShiftUnsignedAssignOperator.getInstance());
        binaryOperatorList.add(LogicAndOperator.getInstance("&&"));
        binaryOperatorList.add(LogicAndOperator.getInstance("and"));
        binaryOperatorList.add(LogicOrOperator.getInstance("||"));
        binaryOperatorList.add(LogicOrOperator.getInstance("or"));
        binaryOperatorList.add(EqualOperator.getInstance());
        binaryOperatorList.add(UnequalOperator.getInstance("!="));
        binaryOperatorList.add(UnequalOperator.getInstance("<>"));
        //binaryOperatorList.add(PrismaticUnequalOperator.getInstance());
        binaryOperatorList.add(GreaterOperator.getInstance());
        binaryOperatorList.add(GreaterEqualOperator.getInstance());
        binaryOperatorList.add(LessOperator.getInstance());
        binaryOperatorList.add(LessEqualOperator.getInstance());
        binaryOperatorList.add(InOperator.getInstance());
        binaryOperatorList.add(NotInOperator.getInstance());
        binaryOperatorList.add(LikeOperator.getInstance());
        binaryOperatorList.add(NotLikeOperator.getInstance());
        binaryOperatorList.add(InstanceOfOperator.getInstance());
        for (BinaryOperator binaryOperator : binaryOperatorList) {
            DEFAULT_BINARY_OPERATOR_MAP.put(binaryOperator.getOperator(), binaryOperator);
        }
        
        List<UnaryOperator> prefixUnaryOperatorList = new ArrayList<>(8);
        prefixUnaryOperatorList.add(PlusUnaryOperator.getInstance());
        prefixUnaryOperatorList.add(MinusUnaryOperator.getInstance());
        prefixUnaryOperatorList.add(PlusPlusPrefixUnaryOperator.getInstance());
        prefixUnaryOperatorList.add(MinusMinusPrefixUnaryOperator.getInstance());
        prefixUnaryOperatorList.add(BitwiseInvertOperator.getInstance());
        prefixUnaryOperatorList.add(LogicNotOperator.getInstance());
        for (UnaryOperator unaryOperator : prefixUnaryOperatorList) {
            DEFAULT_PREFIX_UNARY_OPERATOR_MAP.put(unaryOperator.getOperator(), unaryOperator);
        }
        
        List<UnaryOperator> suffixUnaryOperatorList = new ArrayList<>(8);
        suffixUnaryOperatorList.add(PlusPlusSuffixUnaryOperator.getInstance());
        suffixUnaryOperatorList.add(MinusMinusSuffixUnaryOperator.getInstance());
        for (UnaryOperator unaryOperator : suffixUnaryOperatorList) {
            DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.put(unaryOperator.getOperator(), unaryOperator);
        }
    }
    
    private static final Map<String, Integer> ALIASABLE_KEYWORDS = new HashMap<>();
    
    static {
        ALIASABLE_KEYWORDS.put("if", QLexer.IF);
        ALIASABLE_KEYWORDS.put("then", QLexer.THEN);
        ALIASABLE_KEYWORDS.put("else", QLexer.ELSE);
        ALIASABLE_KEYWORDS.put("for", QLexer.FOR);
        ALIASABLE_KEYWORDS.put("while", QLexer.WHILE);
        ALIASABLE_KEYWORDS.put("break", QLexer.BREAK);
        ALIASABLE_KEYWORDS.put("continue", QLexer.CONTINUE);
        ALIASABLE_KEYWORDS.put("return", QLexer.RETURN);
        ALIASABLE_KEYWORDS.put("function", QLexer.FUNCTION);
        ALIASABLE_KEYWORDS.put("macro", QLexer.MACRO);
        ALIASABLE_KEYWORDS.put("new", QLexer.NEW);
        ALIASABLE_KEYWORDS.put("null", QLexer.NULL);
        ALIASABLE_KEYWORDS.put("true", QLexer.TRUE);
        ALIASABLE_KEYWORDS.put("false", QLexer.FALSE);
    }
    
    private final Map<String, BinaryOperator> customBinaryOperatorMap = new ConcurrentHashMap<>();
    
    private final Map<String, Integer> keyWordAliases = new ConcurrentHashMap<>();
    
    /**
     * Register a custom binary operator if it does not clash with a built-in operator.
     *
     * @param operatorName          operator lexeme, for example {@code "**"}
     * @param customBinaryOperator  implementation of the custom operator
     * @param priority              operator precedence, see {@link QLPrecedences}
     * @return {@code true} if registered successfully; {@code false} if the name is already used
     */
    public boolean addBinaryOperator(String operatorName, CustomBinaryOperator customBinaryOperator, int priority) {
        if (DEFAULT_BINARY_OPERATOR_MAP.containsKey(operatorName)) {
            return false;
        }
        BinaryOperator preBinaryOperator = customBinaryOperatorMap.putIfAbsent(operatorName,
            adapt2BinOp(operatorName, customBinaryOperator, priority));
        return preBinaryOperator == null;
    }
    
    public boolean replaceDefaultOperator(String operatorName, CustomBinaryOperator customBinaryOperator) {
        BinaryOperator defaultOperator = DEFAULT_BINARY_OPERATOR_MAP.get(operatorName);
        if (defaultOperator == null) {
            return false;
        }
        BinaryOperator preBinaryOperator = customBinaryOperatorMap.putIfAbsent(operatorName,
            adapt2BinOp(operatorName, customBinaryOperator, defaultOperator.getPriority()));
        return preBinaryOperator == null;
    }
    
    private BinaryOperator adapt2BinOp(String operatorName, CustomBinaryOperator customBinaryOperator, int priority) {
        return new BinaryOperator() {
            @Override
            public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions,
                ErrorReporter errorReporter) {
                try {
                    return customBinaryOperator.execute(left, right);
                }
                catch (UserDefineException e) {
                    throw ThrowUtils.reportUserDefinedException(errorReporter, e);
                }
                catch (Throwable t) {
                    throw ThrowUtils.wrapThrowable(t,
                        errorReporter,
                        "OPERATOR_INNER_EXCEPTION",
                        "custom operator '" + operatorName + "' inner exception");
                }
            }
            
            @Override
            public String getOperator() {
                return operatorName;
            }
            
            @Override
            public int getPriority() {
                return priority;
            }
        };
    }
    
    /**
     * @param operatorLexeme like +, =, *, /
     * @return binary operator
     */
    public BinaryOperator getBinaryOperator(String operatorLexeme) {
        BinaryOperator customBinaryOperator = customBinaryOperatorMap.get(operatorLexeme);
        if (customBinaryOperator != null) {
            return customBinaryOperator;
        }
        
        return DEFAULT_BINARY_OPERATOR_MAP.get(operatorLexeme);
    }
    
    /**
     * like --1 ++1 !true ~1 ^1
     *
     * @param operatorLexeme ++, --
     * @return prefix unary operator
     */
    public UnaryOperator getPrefixUnaryOperator(String operatorLexeme) {
        return DEFAULT_PREFIX_UNARY_OPERATOR_MAP.get(operatorLexeme);
    }
    
    /**
     * like 1-- 1++
     *
     * @param operatorLexeme ++, --
     * @return suffix unary operator
     */
    public UnaryOperator getSuffixUnaryOperator(String operatorLexeme) {
        return DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.get(operatorLexeme);
    }
    
    @Override
    public boolean isOpType(String lexeme, OpType opType) {
        switch (opType) {
            case MIDDLE:
                return getBinaryOperator(lexeme) != null;
            case PREFIX:
                return DEFAULT_PREFIX_UNARY_OPERATOR_MAP.containsKey(lexeme);
            case SUFFIX:
                return DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.containsKey(lexeme);
        }
        return false;
    }
    
    @Override
    public Integer precedence(String lexeme) {
        return getBinaryOperator(lexeme).getPriority();
    }
    
    @Override
    public Integer getAlias(String lexeme) {
        return keyWordAliases.get(lexeme);
    }
    
    public boolean addKeyWordAlias(String lexeme, String keyWord) {
        Integer keyWordId = ALIASABLE_KEYWORDS.get(keyWord);
        if (keyWordId == null) {
            return false;
        }
        keyWordAliases.put(lexeme, keyWordId);
        return true;
    }
    
    public boolean addOperatorAlias(String lexeme, String operator) {
        BinaryOperator originDefaultOp = DEFAULT_BINARY_OPERATOR_MAP.get(operator);
        if (originDefaultOp != null) {
            BinaryOperator newOperator = adaptOriginOperator(originDefaultOp, lexeme);
            BinaryOperator prev = customBinaryOperatorMap.putIfAbsent(lexeme, newOperator);
            return prev == null;
        }
        BinaryOperator originCusOp = customBinaryOperatorMap.get(operator);
        if (originCusOp != null) {
            BinaryOperator newOperator = adaptOriginOperator(originCusOp, lexeme);
            BinaryOperator prev = customBinaryOperatorMap.putIfAbsent(lexeme, newOperator);
            return prev == null;
        }
        return false;
    }
    
    private BinaryOperator adaptOriginOperator(BinaryOperator originOperator, String lexeme) {
        return new BinaryOperator() {
            @Override
            public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions,
                ErrorReporter errorReporter) {
                return originOperator.execute(left, right, qRuntime, qlOptions, errorReporter);
            }
            
            @Override
            public String getOperator() {
                return lexeme;
            }
            
            @Override
            public int getPriority() {
                return originOperator.getPriority();
            }
        };
    }
}
