package com.yanzuoguang.util.helper;

import com.yanzuoguang.util.YzgError;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 公式计算
 *
 * @author 颜佐光
 */
public class FormulaHelper {

    private static final String REGEX_DOUBLE = "^[-+]?[0-9]*\\.?[0-9]+$";
    private static final String REGEX_QUOT = "(^.*?)\\((.+?)\\)(.*?$)";
    private static final String REGEX_CALC_ADD_PLUS = "(^.*)([+\\-])(.*?$)";
    private static final String REGEX_CALC_MULTIPLY_MOD = "(^.*)([*/])(.*?$)";
    private static final String REGEX_CALC_TAG = "[+\\-*/()]+";
    private static final String EMPTY_CHAR = " ";
    private static final String TEMP_VAR_NAME = "@temp";

    private static FormulaHelper calcInstance = new FormulaHelper();

    /**
     * 公式参数获取
     */
    public interface CalcParameter {
        /**
         * 获取参数值
         *
         * @param parameterName 获取参数值的名称
         * @return
         */
        double getValue(String parameterName);
    }


    /**
     * 获取excel列序号
     *
     * @param columnName
     * @return
     */
    public static final int getExcelIndex(String columnName) {
        columnName = columnName.toLowerCase();
        if (!columnName.matches("^[a-z]+$")) {
            throw YzgError.getRuntimeException("013", columnName);
        }
        // 从名称转换列序号
        int formulaColumnIndex = 0;
        char[] chs = new char[columnName.length()];
        columnName.getChars(0, columnName.length(), chs, 0);
        for (int i = 0; i < chs.length; i++) {
            formulaColumnIndex = formulaColumnIndex * 26 + (chs[i] - 'a' + 1);
        }
        formulaColumnIndex--;
        return formulaColumnIndex;
    }

    /**
     * 计算公式
     *
     * @param formula 公式内容,支持括号、空格、数字、+、-、*、/、变量名,如: A * ( B + C )
     * @return 运算后的结果
     */
    public static double calc(String formula) {
        return calcInstance.calRun(formula, StringHelper.EMPTY, null);
    }


    /**
     * 计算公式
     *
     * @param formula       公式内容,支持括号、空格、数字、+、-、*、/、变量名,如: A * ( B + C )
     * @param calcParameter 获取变量值
     * @return 运算后的结果
     */
    public static double calc(String formula, CalcParameter calcParameter) {
        return calcInstance.calRun(formula, StringHelper.EMPTY, calcParameter);
    }


    /**
     * 计算公式
     *
     * @param formula       公式内容,支持括号、空格、数字、+、-、*、/、变量名,如: A * ( B + C )
     * @param tempName      临时变量名称
     * @param calcParameter 获取变量值
     * @return 运算后的结果
     */
    public static double calc(String formula, String tempName, CalcParameter calcParameter) {
        return calcInstance.calRun(formula, tempName, calcParameter);
    }

    /**
     * 计算公式
     *
     * @param formula       公式内容,支持括号、空格、数字、+、-、*、/、变量名,如: A * ( B + C )
     * @param tempName      临时变量名称
     * @param calcParameter 获取变量值
     * @return 运算后的结果
     */
    private double calRun(String formula, String tempName, CalcParameter calcParameter) {
        if (StringHelper.isEmpty(formula)) {
            return 0;
        }
        tempName = StringHelper.getFirst(tempName, TEMP_VAR_NAME);
        // 去掉公式空格
        formula = formula.replaceAll(EMPTY_CHAR, "");
        // 获取所有的变量名称
        List<String> varNames = getVarNames(formula);
        // 获取所有变量值
        Map<String, Double> varValues = getVarValues(varNames, calcParameter);
        // 返回计算结果
        return calcProc(formula, tempName, varValues);
    }

    /**
     * 获取变量表
     *
     * @param formula 输入等式的右边
     **/
    private List<String> getVarNames(String formula) {
        List<String> list = new ArrayList<>();
        //清理所有运算符,并且包含多个运算符号时,将多个运算符号当成一个运算符号处理
        String formulaTo = formula.replaceAll(REGEX_CALC_TAG, EMPTY_CHAR);
        String[] items = formulaTo.split(EMPTY_CHAR);
        for (String item : items) {
            // 判断是否是空字符串、纯数字,是则不属于变量
            if (StringHelper.isEmpty(item) || item.matches(REGEX_DOUBLE)) {
                continue;
            }
            list.add(item);
        }
        return list;
    }

    /**
     * 获取所有变量值
     *
     * @param varNames      变量名称列表
     * @param calcParameter 获取变量值
     * @return 变量值
     */
    private Map<String, Double> getVarValues(List<String> varNames, CalcParameter calcParameter) {
        // 获取所有的变量值
        Map<String, Double> varValues = new HashMap<>(varNames.size());
        for (String name : varNames) {
            if (varValues.containsKey(name) || calcParameter == null) {
                continue;
            }
            double value = calcParameter.getValue(name);
            varValues.put(name, value);
        }
        return varValues;
    }

    /**
     * 最终计算结果
     *
     * @param formula   公式
     * @param tempName  临时变量名称
     * @param varValues 变量值
     * @return
     */
    private double calcProc(String formula, String tempName, Map<String, Double> varValues) {
        double ret = 0;
        if (formula.matches(REGEX_QUOT)) {
            // 获取第一个括号和最后一个括号
            Matcher matcher = getMatcher(REGEX_QUOT, formula);
            double quotResult = calcProc(matcher.group(2), tempName, varValues);
            String mutualKey = TEMP_VAR_NAME + varValues.size();
            varValues.put(mutualKey, quotResult);
            String formulaTo = matcher.group(1) + mutualKey + matcher.group(3);
            ret = calcProc(formulaTo, tempName, varValues);
        } else if (formula.matches(REGEX_CALC_ADD_PLUS)) {
            // 判断是否包含+-运算符号
            Matcher matcher = getMatcher(REGEX_CALC_ADD_PLUS, formula);
            double leftResult = calcProc(matcher.group(1), tempName, varValues);
            double rightResult = calcProc(matcher.group(3), tempName, varValues);
            ret = calcItem(matcher.group(2), leftResult, rightResult);
        } else if (formula.matches(REGEX_CALC_MULTIPLY_MOD)) {
            // 判断是否包含*/运算符号
            Matcher matcher = getMatcher(REGEX_CALC_MULTIPLY_MOD, formula);
            double leftResult = calcProc(matcher.group(1), tempName, varValues);
            double rightResult = calcProc(matcher.group(3), tempName, varValues);
            ret = calcItem(matcher.group(2), leftResult, rightResult);
        } else if (formula.matches(REGEX_DOUBLE)) {
            ret = StringHelper.toDouble(formula);
        } else {
            ret = StringHelper.toDouble(varValues.get(formula));
        }
        // System.out.println("公式: " + formula + " 值" + ret);
        return ret;
    }

    /**
     * 匹配正则表达式和公式
     *
     * @param regex   正则表达式
     * @param formula 公式
     * @return 匹配后的结果
     */
    private Matcher getMatcher(String regex, String formula) {
        Pattern p = Pattern.compile(regex);
        Matcher matcher = p.matcher(formula);
        if (!matcher.find()) {
            throw YzgError.getRuntimeException("014");
        }
        return matcher;
    }

    private double calcItem(String flag, double a, double b) {
        switch (flag) {
            case "+":
                return CalcHelper.add(a, b);
            case "-":
                return CalcHelper.sub(a, b);
            case "*":
                return CalcHelper.mul(a, b);
            case "/":
                return CalcHelper.div(a, b);
            default:
                return 0;
        }
    }


}