package com.xhpc.order.service.impl;

import com.udojava.evalex.Expression;

import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class Calc {

    private static String regex = "^[a-zA-Z]+$";
    private static Pattern pattern = Pattern.compile(regex);

    public static void main(String[] argv) {

        Map<String, Double> map = new HashMap<>();
        map.put("T", Double.valueOf(0.1234));
        String ruleExpression = "T * 30%";
        double rs = eval(ruleExpression, map);
        System.out.println(rs);
        System.out.println(0.7 + 0.1);
    }

    //计算中采用5位小数(四舍五入),结果保留4位小数(截断)
    public static double eval(String ruleExpression, Map<String, Double> params) {

        return eval(ruleExpression, params, RoundingMode.FLOOR);
    }

    public static double eval(String ruleExpression, Map<String, Double> params, RoundingMode rm) {

        String[] split = ruleExpression.split("[()*/\\-+?]");
        for (String splitValue : split) {
            if (splitValue.endsWith("%")) {
                BigDecimal rate = new BigDecimal(splitValue.trim().replace("%", ""));
                ruleExpression = ruleExpression.replace(splitValue, rate.divide(BigDecimal.valueOf(100)).setScale(5,
                        RoundingMode.HALF_UP).toString());
            } else {
                if (pattern.matcher(splitValue.trim()).matches()) {
                    ruleExpression = ruleExpression.replace(splitValue, params.get(splitValue.trim()).toString());
                }
            }
        }
        Expression expression = new Expression(ruleExpression);
        expression.setPrecision(5);
        expression.setRoundingMode(RoundingMode.HALF_UP);
        return expression.eval().setScale(4, rm).doubleValue();
    }

    private static void test(String s, double x) {

        double v = eval(s);
        System.out.println((v == x) + ": " + s + " = " + v);
    }

    private static double eval(String s) {

        Parser p = Parser.parse(s);
        double v = p.value();

        if (p.isValid()) {
            return v;
        } else {
            throw new ArithmeticException("error check: " + p.error() + ": " + s);
        }
    }

}

/**
 * Parse via recursive descent using the following LL(1) grammar:
 * <code>
 * expr = [addop] term {(addop) term} end
 * term = factor {(mulop) factor} end
 * factor = word | number | "(" expr ")" end
 * word = name ["(" expr] ["," expr] ")"] end
 * addop = "+" | "-"
 * mulop = "*" | "/"
 * </code>
 *
 * @see <a href="http://en.wikipedia.org/wiki/Recursive_descent_parser">
 * Recursive descent parser</a>.
 */
class Parser extends Object {

    private final StreamTokenizer tokens;
    private int token;
    private final double value;
    private String error;

    private Parser(String s) {

        Reader reader = new StringReader(s);
        tokens = new StreamTokenizer(reader);
        tokens.ordinaryChar(Symbol.SLASH.toChar());

        getToken();
        value = expr();
        if (!tokenIs(Symbol.END))
            putError("syntax error");
    }

    public static Parser parse(String s) {

        return new Parser(s);
    }

    public boolean isValid() {

        return error == null;
    }

    public double value() {

        return value;
    }

    public String error() {

        return error;
    }

    // expr = [addop] term {(addop) term} end
    private double expr() {

        BigDecimal sign = BigDecimal.ONE;
        accept(Symbol.PLUS);
        if (accept(Symbol.MINUS)) {
            sign = BigDecimal.valueOf(-1);
        }
        BigDecimal value = sign.multiply(term());
        while (Symbol.isAddOp(token)) {
            if (accept(Symbol.PLUS)) {
                value = value.add(term());
            }
            if (accept(Symbol.MINUS)) {
                value = value.subtract(term());
            }
        }
        return value.doubleValue();
    }

    // term = factor {(mulop) factor} end
    private BigDecimal term() {

        BigDecimal value = factor();
        while (Symbol.isMulOp(token)) {
            if (accept(Symbol.STAR)) {
                value = value.multiply(factor());
            }
            if (accept(Symbol.SLASH)) {
                value = value.divide(factor());
            }
        }
        return value;
    }

    // factor = word | number | "(" expr ")" end
    private BigDecimal factor() {

        BigDecimal value = BigDecimal.ZERO;
        if (tokenIs(Symbol.WORD)) {
            value = word();
        } else if (tokenIs(Symbol.NUMBER)) {
            value = new BigDecimal(tokens.nval);
            getToken();
        } else if (accept(Symbol.OPEN)) {
            value = BigDecimal.valueOf(expr());
            expect(Symbol.CLOSE);
        } else {
            putError("factor error");
            getToken();
        }
        return value;
    }

    // word = name ["(" expr] ["," expr] ")"] end
    private BigDecimal word() {

        BigDecimal value = BigDecimal.ZERO;
        String name = tokens.sval;
        FunctionAdapter fa = Function.lookup(name);
        getToken();
        if (fa != null) {
            int count = fa.getCount();
            if (count == 0) {
                value = BigDecimal.valueOf(fa.eval());
            } else if (accept(Symbol.OPEN)) {
                double[] args = new double[count];
                for (int i = 0; i < count; i++) {
                    args[i] = expr();
                    if (i < count - 1)
                        expect(Symbol.COMMA);
                }
                value = BigDecimal.valueOf(fa.eval(args));
                expect(Symbol.CLOSE);
            } else putError("missing " + Symbol.OPEN.toChar());
        } else putError("undefined " + name);
        return value;
    }

    /**
     * Fetch the next token in the stream.
     */
    private void getToken() {

        try {
            token = tokens.nextToken();
        } catch (IOException e) {
            putError("i/o error " + e.getMessage());
        }
    }

    /**
     * Return true if the current token matches the given symbol.
     */
    private boolean tokenIs(Symbol symbol) {

        return token == symbol.token();
    }

    /**
     * Require a matching symbol; gerate an error if it's unexpected.
     */
    private void expect(Symbol symbol) {

        if (accept(symbol)) return;
        putError("missing " + symbol.toChar());
    }

    /**
     * Advance if the current token matches the given symbol.
     */
    private boolean accept(Symbol symbol) {

        if (tokenIs(symbol)) {
            getToken();
            return true;
        }
        return false;
    }

    /**
     * Generate an error; ignore line numbers.
     */
    private void putError(String s) {

        if (error == null)
            error = s + " at " + tokens.toString().replaceAll(",.*$", "");
    }

}

/**
 * Enumerate the terminal symbols recognized by the Parser.
 * Each symbol's token field is initialized with a value that
 * matches one returned by StreamTokenizer's nextToken() method.
 */
enum Symbol {
    PLUS('+'), MINUS('-'), STAR('*'), SLASH('/'),
    OPEN('('), CLOSE(')'), COMMA(','), PERCENT('%'),
    END(StreamTokenizer.TT_EOF),
    WORD(StreamTokenizer.TT_WORD),
    NUMBER(StreamTokenizer.TT_NUMBER);

    private final int token;

    Symbol(int token) {

        this.token = token;
    }

    /**
     * Return this symbol's token.
     */
    public int token() {

        return this.token;
    }

    /**
     * If printable, return character for this symbol's token.
     */
    public char toChar() {

        if (this.token < 32) return '\ufffd';
        else return (char) this.token;
    }

    /**
     * Return if the given token matches PLUS or MINUS.
     */
    public static boolean isAddOp(int token) {

        return token == PLUS.token || token == MINUS.token;
    }

    /**
     * Return if the given token matches STAR or SLASH.
     */
    public static boolean isMulOp(int token) {

        return token == STAR.token || token == SLASH.token;
    }
}

/**
 * Enumerate the available functions. Although an enum may define
 * <i>constant-specific</i> methods, an included adpter or interface
 * is perhaps easier to read, initialize and maintain.
 */
enum Function {
    MAX(new Max()),
    MIN(new Min());

    private final FunctionAdapter fa;

    /**
     * Construct a Function with the specified adapter.
     **/
    Function(FunctionAdapter fa) {

        this.fa = fa;
    }

    /**
     * Return a Function's adapter by name; null if unknown.
     */
    public static FunctionAdapter lookup(String name) {

        Function f;
        try {
            f = Enum.valueOf(Function.class, name.toUpperCase());
        } catch (RuntimeException e) {
            return null;
        }
        return f.fa;
    }
}

/**
 * Adapt to functions with a variable number of arguments.
 * Concrete implementations should override getCount() to indicate
 * the expected number of arguments. The default is one.
 */
abstract class FunctionAdapter {

    public int getCount() {

        return 1;
    }

    abstract double eval(double... args);

}

class Max extends FunctionAdapter {

    public int getCount() {

        return 2;
    }

    public double eval(double... args) {

        return Math.max(args[0], args[1]);
    }

}

class Min extends FunctionAdapter {

    public int getCount() {

        return 2;
    }

    public double eval(double... args) {

        return Math.min(args[0], args[1]);
    }

}
