package com.ruoyi.common.utils.jep;

import com.ruoyi.project.business.dto.JEPCustomFunction.*;
import com.ruoyi.project.business.enums.CalculateResultEnum;
import com.ruoyi.project.business.enums.InputTypeEnum;
import com.ruoyi.project.business.enums.JEPFuncEnum;
import com.ruoyi.project.business.enums.JEPSplitEnum;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.nfunk.jep.JEP;
import org.nfunk.jep.function.Ceil;
import org.nfunk.jep.function.Floor;
import org.nfunk.jep.function.If;
import org.nfunk.jep.function.Sum;

@Slf4j
public class JEPUtils {

    public static final String CALCULATE_RESULT = "result";

    public static final String AFTER_ANALYSIS_FORMULA = "analysisFormula";

    public static final String AFTER_ENCIPHER_PARAM = "afterEncipherParams";

    public static final String AFTER_ENCIPHER_FUNC = "afterEncipherFunc";

    public static final String AFTER_ANALYSIS_FUNC = "analysisFunc";

    public static final String AFTER_ANALYSIS_PARAM = "analysisParam";

    public static final String CALCULATE_VALUE = "calculateValue";

    public static final String OBJECT_CALCULATE_VALUE = "objectCalculateValue";


    Map<String, Object> map = new HashMap<String, Object>();

    /**
     * @param formula        解析后的 公式
     * @param map            参数对 key :code value : 参数值
     * @param paramInputType 参数输入类型 key :code value : 参数输入类型
     * @param isDebug        是否开启调试模式
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @description:
     * @author: jubs 😀
     * @date: 2024/6/13 14:20
     **/
    public static Map<String, Object> calculate(String formula, Map<String, Object> map,
                                                Map<String, String> paramInputType, Boolean isDebug) {
        Map<String, Object> result = new HashMap<>();
        List<String> encipherdList = new ArrayList<>();

        // 设置 numberFactory 实现将结果转换为 BigDecimal 类型
        JEP jep = new JEP();
        //加入目前支持的函数
        jep.addFunction("SUM", new Sum());
        jep.addFunction("IF", new If());
        jep.addFunction("MAX", new MaxFunction());
        jep.addFunction("MIN", new MinFunction());
        jep.addFunction("FIND", new FindFunction());
        jep.addFunction("IFS", new IFSFunction());
        jep.addFunction("ROUNDUP", new Ceil());
        jep.addFunction("ROUNDDOWN", new Floor());
        jep.addFunction("ROUND", new CustomRoundFunction());
        jep.addFunction("ISEMPTY", new IsEmptyFunction());
        jep.addFunction("INCLUDE",new IncludeFunction());

        //通过 `$@$` 获取方法
        List<String> funcList = getFuncShort(formula);
        for (String func : funcList) {
            formula = formula.replace(JEPSplitEnum.FUNC_SPLIT_FLAG.getFuncKey(), "");
            func = func.replace(JEPSplitEnum.FUNC_SPLIT_FLAG.getFuncKey(), "");
            JEPFuncEnum jEPFuncEnum = JEPFuncEnum.fromByKey(func);
            formula = formula.replace(func, jEPFuncEnum.getFuncValue());
        }
        // 匹配 逻辑符号进行替换
        // @Date 2024/6/5 10:59 @Author jubs 😀 @description:前端已做转换
        //formula = formula.replace( JEPLogicEnum.OR.getLogicKey(), JEPLogicEnum.OR.getLogicValue());
        //formula = formula.replace(JEPLogicEnum.AND.getLogicKey(), JEPLogicEnum.AND.getLogicValue());


        String regex = "\\$_\\$.*?\\$_\\$";
        Pattern pattern = Pattern.compile(regex);
        // 对不存在的参数设置默认值
        Matcher matcher = pattern.matcher(formula);
        while (matcher.find()) {
            String match = matcher.group();
            if (map.get(match) == null){
                map.put(match, "");
                paramInputType.put(match,InputTypeEnum.INPUT.getCode());
            }
        }

        for (String param : map.keySet()) {
            if (!isDebug) {
                // log.info("加密后的入参：{}",
                //         JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey() + param + JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey());
                encipherdList.add(
                    JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey() + param + JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey());
                jep.addVariable(
                    JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey() + param + JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey(),
                    map.get(param));
            } else {
                // log.info("加密后的入参：{}", param);
                encipherdList.add(param);
                // log.info("入参：{}", param);
                if (paramInputType.containsKey(param)) {
                    InputTypeEnum inputTypeEnum = InputTypeEnum.from(paramInputType.get(param));
                    switch (inputTypeEnum) {
                        case INPUT:
                        case SELECT:
                        case INPUT_SELECT:
                        case CHECK_BOX:
                            String paramStr = (String) map.get(param);
                            if (StringUtils.isNotBlank(paramStr)) {
                                boolean isNumberFlag = NumberUtils.isCreatable(paramStr);
                                jep.addVariable(param, isNumberFlag ? new BigDecimal(paramStr) : paramStr);
                                // log.info("入参值：{}", paramStr);
                            } else {
                                jep.addVariable(param, "NO-VALUE");
                                // log.info("入参值：{}", "");
                            }
                            break;
                        case INPUT_NUMBER:
                        case INPUT_DECIMAL:
                            String paramStr1 = (String) map.get(param);
                            if (Objects.nonNull(paramStr1)) {
                                jep.addVariable(param, new BigDecimal(paramStr1));
                                // log.info("入参值：{}", new BigDecimal(paramStr1));
                            } else {
                                jep.addVariable(param, new BigDecimal(0));
                                // log.info("入参值：{}", new BigDecimal(0));
                            }

                            break;
                    }
                }


            }
        }

        jep.setImplicitMul(true);

        jep.parseExpression(formula);
        // log.info("解析后的公式：{}", formula);
        // log.info("解析后的公式的计算double结果：{}", jep.getValue());
        // log.info("解析后的公式的计算Object结果：{}", jep.getValueAsObject());
        if (Double.isNaN(jep.getValue()) || jep.hasError()) {
            result.put(CALCULATE_RESULT, CalculateResultEnum.NG.getValue());
        }
        result.put(AFTER_ANALYSIS_FORMULA, formula);
        result.put(AFTER_ENCIPHER_PARAM, encipherdList);
        result.put(CALCULATE_VALUE, jep.getValue() + "");
        try {
            // @Date 2024/7/8 8:43 @Author jubs 😀 @description: JEP计算暂不取整
            BigDecimal objValue = new BigDecimal(jep.getValueAsObject() + "");
            //            objValue = objValue.stripTrailingZeros();
//            objValue = objValue.setScale(2, RoundingMode.HALF_UP);
            result.put(OBJECT_CALCULATE_VALUE, objValue.stripTrailingZeros().toPlainString());
        } catch (Exception e) {
            result.put(OBJECT_CALCULATE_VALUE, jep.getValueAsObject() + "");
            // log.info("JEPUTils 计算结果 数值格式化失败，使用原始值：{}", jep.getValueAsObject());
            // log.error("JEPUtils 计算结果格式化失败", e);
        }
        return result;
    }

    public static double stringToDouble(String str) {
        //计算小数点后的位数
        int decimalPlaces = str.indexOf('.') > 0 ? str.length() - str.indexOf('.') - 1 : 0;
        // 使用String.format格式化double值，保留原始的小数位数
        String format = "%." + decimalPlaces + "f";
        double value = Double.parseDouble(str);
        String formattedValue = String.format(format, value);
        return Double.parseDouble(formattedValue);
    }


    public static List<String> getParam(String str) {
        List<String> matchList = new ArrayList<>();
        String regx = "\\$_\\$\\s*(.*?)\\s*\\$_\\$";
        Pattern pattern = Pattern.compile(regx);
        Matcher matcher = pattern.matcher(str);
        while (matcher.find()) {
            matchList.add(matcher.group(1));
        }
        return matchList;
    }

    public static List<String> getFuncShort(String str) {
        List<String> matchList = new ArrayList<>();
        String regx = "\\$@\\$([^\\$]+)\\$@\\$";
        Pattern pattern = Pattern.compile(regx);
        Matcher matcher = pattern.matcher(str);

        // 查找匹配的内容并输出
        while (matcher.find()) {
            String content = matcher.group();
            matchList.add(content);
        }
        return matchList;
    }

    public static Map<String, Object> analysisFormula(String formula) {
        Map<String, Object> result = new HashMap<>();
        //根据 $_$ 获取参数
        List<String> paramList = getParam(formula);
        //根据 $@$ 获取方法
        List<String> funcList = getFuncShort(formula);

        //替换掉其中的 $_$、$@$ 按照长度 倒序排序
        if (paramList.size() > 0) {
            paramList = paramList.stream().map(item -> item.replace(JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey(), ""))
                    .collect(Collectors.toList());
        }
        if (funcList.size() > 0) {
            funcList = funcList.stream().map(item -> item.replace(JEPSplitEnum.FUNC_SPLIT_FLAG.getFuncKey(), ""))
                    .collect(Collectors.toList());
        }

        paramList.sort((o1, o2) -> o2.length() - o1.length());
        result.put(AFTER_ANALYSIS_PARAM, paramList);
        funcList.sort((o1, o2) -> o2.length() - o1.length());
        result.put(AFTER_ANALYSIS_FUNC, funcList);
        for (String s : funcList) {
            JEPFuncEnum funcEnum = JEPFuncEnum.fromByKey(s);
            formula = formula.replace(JEPSplitEnum.FUNC_SPLIT_FLAG.getFuncKey(), "")
                .replace(funcEnum.getFuncKey(), funcEnum.getFuncValue());

        }

        formula = formula.replace(JEPSplitEnum.PARAM_SPLIT_FLAG.getFuncKey(), "");

        result.put(AFTER_ANALYSIS_FORMULA, formula);
        return result;
    }

}
