package com.sdtool.common.tool;

import com.dySweetFishPlugin.tool.lang.ShortUrlGenerate;
import com.sdtool.common.entity.base.BaseEntity;
import com.sdtool.common.entity.site.ERPTokenUser;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.net.http.HttpRequest;
import com.sweetfish.service.RetResult;
import com.sweetfish.util.Utility;
import groovy.lang.GroovyClassLoader;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.FileResourceLoader;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.codehaus.groovy.syntax.Types;
import org.rex.RMap;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by jlutt on 2018-11-17.
 * ERP公共函数
 *
 * @author jlutt
 */
@SuppressWarnings("unused")
public class ERPUtils {

    /**
     * 汉语中数字大写
     */
    private static final String[] CN_UPPER_NUMBER = {"零", "壹", "贰", "叁", "肆",
            "伍", "陆", "柒", "捌", "玖"};
    /**
     * 汉语中货币单位大写，这样的设计类似于占位符
     */
    private static final String[] CN_UPPER_MONETRAY_UNIT = {"分", "角", "元",
            "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆", "拾",
            "佰", "仟"};
    /**
     * 特殊字符：整
     */
    private static final String CN_FULL = "整";
    /**
     * 特殊字符：负
     */
    private static final String CN_NEGATIVE = "负";
    /**
     * 金额的精度，默认值为2
     */
    private static final int MONEY_PRECISION = 2;
    /**
     * 特殊字符：零元整
     */
    private static final String CN_ZEOR_FULL = "零元" + CN_FULL;
    private static AESEncrypter aesTokenEncrypter;

    public static final String SPECIAL_SYMBOL_REGEX = "[`~!@#$^&*()=|{}':;',\\[\\]\\.<>/?~！@#￥……&*（）——|{}【】‘；：”“’。，、？-]";

    private static final ERPTokenUser SYSTOKENUSER = new ERPTokenUser(0, "系统");

    private static final ERPTokenUser SYSIMAGEUSER = new ERPTokenUser(99999999, "系统修图");

    private static final ERPTokenUser SYS_NONE_IMAGE_USER = new ERPTokenUser(99999998, "不修图");

    private static final ERPTokenUser SYSFORCEUSER = new ERPTokenUser(99999998, "强制完成用户");

    private static final ERPTokenUser SYS_ERP_CALL_USER = new ERPTokenUser(99999997, "都能运发货反馈用户");

    private static final ERPTokenUser SYS_REMOTE_ERP_USER = new ERPTokenUser(99999996, "远程ERP用户");

    public static final String PARAM_SUCCESS_IDS = "_sys_success_ids";
    public static final String PARAM_FAIL_IDS = "_sys_fail_ids";

    public static final String PARAM_SEARCH_IDS = "_sys_search_ids";

    public static final String PARAM_SEARCH_ID = "_sys_search_id";

    public static final String PARAM_SEARCH_CODE = "_sys_search_code";

    public static final String PARAM_SEARCH_Exist = "_sys_search_exist";

    /**
     * 产品参数保留的id，小于此id的为系统默认置入的参数，用于和产品中心同步，即产品中心模式，没有此记录，开发人员可增加维护产品参数，其它商城、erp有此记录，用于自行维护产品中心没有的参数
     * 这样做的目的，是为了从产品中心下载产品档案时，能方便的对应上产品参数
     */
    public static final long RESERVE_PRODUCT_PARAMETER_ID = 200000L;

    public static final RMap<String, Integer> PARAM_VOID_FLAG_0 = new RMap<>(Utility.ofMap("voidFlag", 0));
    public static final RMap<String, Integer> PARAM_VOID_FLAG_1 = new RMap<>(Utility.ofMap("voidFlag", 1));
    public static final RMap<String, Integer> PARAM_VOID_FLAG_ALL = new RMap<>(Utility.ofMap("voidFlag", -1));

    private static final double DEFAULT_RATIO = 25.4;
    private static final double DEFAULT_DPI = 96;

    static {
        try {
            aesTokenEncrypter = new AESEncrypter("dnyERP@Token_LogiN");
        } catch (Exception e) {
            aesTokenEncrypter = null;
        }

    }

    /**
     * 金额元到分
     *
     * @param money 原金额
     * @return 分
     */
    public static long money100(BigDecimal money) {
        return (money == null) ? 0 : money.multiply(BigDecimal.valueOf(100)).longValue();
    }

    /**
     * 金额分转元
     *
     * @param fee 原金额
     * @return 元
     */
    public static BigDecimal money(long fee) {
        return BigDecimal.valueOf(fee).multiply(BigDecimal.valueOf(0.01)).setScale(2, RoundingMode.HALF_UP);
    }

    public static BigDecimal zeroMoney(BigDecimal money) {
        return Objects.requireNonNullElse(money, BigDecimal.ZERO);
    }

    /**
     * 判断map中是否有key
     *
     * @param map 原map
     * @param key 主键
     * @return 是否包含有值的key
     */
    @SuppressWarnings("rawtypes")
    public static boolean mapContainKey(RMap map, String key) {
        if (map == null) return false;

        return (map.containsKey(key) && StringUtils.isNotBlank(map.getString(key)));
    }


    /**
     * 将多个key:value对应值组合成一个Map，items长度必须是偶数, 参数个数若是奇数的话，最后一个会被忽略
     * 类似 JDK9中的 Map.of 方法
     *
     * @param items 键值对
     * @return RMap
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static RMap ofRMap(Object... items) {
        RMap map = new RMap();
        int len = items.length / 2;
        for (int i = 0; i < len; i++) {
            map.put(items[i * 2], items[i * 2 + 1]);
        }
        return map;
    }

    /**
     * 从http请求中解析erp登录token
     *
     * @param request http请求
     * @return token字符串
     */
    public static String parseHttpToken(HttpRequest request) {
        String token = request.getCookie("Access-Token", "");
        if (StringUtils.isEmpty(token)) {
            token = request.getHeader("Access-Token", "");
            if (StringUtils.isEmpty(token)) {
                token = request.getHeader("access-token", "");
                if (StringUtils.isEmpty(token)) {
                    token = request.getParameter("Delphi_Client_Token", "");
                    if ((StringUtils.isNotBlank(token)) && (token.endsWith("?"))) {
                        token = token.substring(0, token.length() - 1);
                    }
                }
            }
        }

        return token;
    }

    /**
     * 从http请求中解析erp登录token
     *
     * @param request http请求
     * @return token字符串
     */
    public static String parseHttpFrom(HttpRequest request) {
        String userFrom = request.getHeader("X-ERP-User-From", "");
        if (StringUtils.isEmpty(userFrom)) {
            userFrom = request.getHeader("x-erp-user-from", "");
            if (StringUtils.isEmpty(userFrom)) {
                userFrom = request.getHeader("X-Erp-User-From", "");

            }
        }

        return userFrom;
    }

    /**
     * 从token字符串中解析出登录用户
     *
     * @param token 业务token
     * @param key   加密
     * @return 登录用户
     */
    public static ERPTokenUser parseERPUserFromToken(String token, String key, JsonConvert jsonConvert) {
        //从token中解析数据
        String[] parts = token.split("\\.");
        //如果不是两部分
        if (parts.length != 2) {
            return null;
        }
        String userJson = org.apache.commons.codec.binary.StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
        String userJwtJson = userJson;
        if (aesTokenEncrypter != null) {
            try {
                userJwtJson = aesTokenEncrypter.decrypt(userJson);
            } catch (Exception e) {
                userJwtJson = token;
            }
        }
        String orginSign = org.apache.commons.codec.binary.StringUtils.newStringUtf8(Base64.decodeBase64(parts[1]));
        //验证签名
        String newSign = DigestUtils.md5Hex(userJwtJson + "&key=" + key).toUpperCase();
        if (!newSign.equals(orginSign)) {
            //签名不对
            return null;
        }
        //签名正确，取出json
        ERPTokenUser jsonUser;
        try {
            jsonUser = jsonConvert.convertFromO(ERPTokenUser.class, userJwtJson);
        } catch (Exception e) {
            return null;
        }

        //判断是否过期
        if (jsonUser.getExpireTime() < System.currentTimeMillis()) {
            return null;
        }

        return jsonUser;
    }

    /**
     * 从登录用户生成token字符串
     *
     * @param user        用户信息
     * @param key         签名key
     * @param jsonConvert json转换器
     * @return token字符串
     */
    public static String parseTokenFromERPUser(ERPTokenUser user, String key, JsonConvert jsonConvert) {
        //设置过期时间
        user.setExpireTime(Instant.now().plus(7, ChronoUnit.DAYS).toEpochMilli());
        String userJson = jsonConvert.convertTo(user);
        String sign = DigestUtils.md5Hex(userJson + "&key=" + key).toUpperCase();

        String userJwtJson = userJson;
        if (aesTokenEncrypter != null) {
            try {
                userJwtJson = aesTokenEncrypter.encrypt(userJson);
            } catch (Exception e) {
                userJwtJson = userJson;
            }
        }

        return Base64.encodeBase64URLSafeString(userJwtJson.getBytes(StandardCharsets.UTF_8)) + "." +
                Base64.encodeBase64URLSafeString(sign.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 通过模板使用beetl生成脚本文件
     *
     * @param mapFunction  模板参数构建方法
     * @param templateName 模板名称
     * @return 脚本内容
     */
    public static String parseScriptFromTemplate(Function<String, Map<String, Object>> mapFunction, String pathName, String templateName) {
        Map<String, Object> beetlMap = mapFunction.apply("");
        try {
            FileResourceLoader resourceLoader = new FileResourceLoader(pathName, "utf-8");
            Configuration cfg = Configuration.defaultConfiguration();
            cfg.setPlaceholderStart("#{");
            cfg.setPlaceholderEnd("}");
            GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
            gt.setSharedVars(beetlMap);
            Template t = gt.getTemplate(templateName);
            return t.render();
        } catch (IOException e) {
            return "";
        }
    }

    /**
     * 生成ERP中脚本编译规则
     *
     * @return 脚本编译规则
     */
    public static CompilerConfiguration buildERPGroovyConfig() {
        //region 设置脚本安全性

        //https://blog.csdn.net/qq_33513250/article/details/103197719
        //https://github.com/codehaus/groovy-git/blob/master/src/examples/groovyShell/BlacklistingShell.groovy

        //配置groovy脚本安全性
        final SecureASTCustomizer secure = new SecureASTCustomizer();
        //设置关键字黑名单
        List<Integer> tokensBlacklist = new ArrayList<>();
//        tokensBlacklist.add(Types.KEYWORD_WHILE);
        tokensBlacklist.add(Types.KEYWORD_GOTO);
        secure.setDisallowedTokens(tokensBlacklist);
        // 设置直接导入检查
//                secure.setIndirectImportCheckEnabled(true);
        // 添加导入黑名单
//                List<String> importsBlackList = new ArrayList<>();
//                importsBlackList.add("com.alibaba.fastjson.JSONObject");
//                secure.setImportsBlacklist(importsBlackList);
        //禁止使用对象
        secure.setDisallowedReceiversClasses(Arrays.asList(
                Runtime.class,
                System.class
//                File.class,
//                Path.class
        ));
        // 自定义CompilerConfiguration，设置AST
        CompilerConfiguration groovyConfig = new CompilerConfiguration();
        groovyConfig.addCompilationCustomizers(secure);
        groovyConfig.setSourceEncoding("utf-8");
        //endregion
        return groovyConfig;
    }

    /**
     * 检查groovy脚本
     *
     * @param script 脚本内容
     * @return 出错信息
     */
    public static String checkGroovyScript(ClassLoader loader, String libPath, String script) {
        try (GroovyClassLoader groovyCl = new GroovyClassLoader(loader, buildERPGroovyConfig())) {
            groovyCl.addClasspath(libPath);
            groovyCl.parseClass(script);
            groovyCl.clearCache();
            return "";
        } catch (IOException | CompilationFailedException e) {
            return e.getMessage();
        }
    }

    /**
     * 从日期获取年份
     *
     * @param dateTime 日期
     * @return 年份
     */
    public static int getDateYear(Date dateTime) {
        return dateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().getYear();
    }

    /**
     * 从日期获取月份
     *
     * @param dateTime 日期
     * @return 月份
     */
    public static int getDateMonth(Date dateTime) {
        return dateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().getMonthValue();
    }

    /**
     * 从日期获取月中的第几天
     *
     * @param dateTime 日期
     * @return 第几天
     */
    public static int getDateDay(Date dateTime) {
        return dateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().getDayOfMonth();
    }

    /**
     * 获取对象创建的年份
     *
     * @param entity
     * @return
     */
    public static int getDateYear(BaseEntity entity) {
        return getDateYear(entity.getCreateTime());
    }

    /**
     * 字符串分割为int数组
     * groovy的collect只能转成list，不利于相关查询
     * groovy src.split(",").collect {it as int}
     *
     * @param src       字符串
     * @param delimiter 分隔符
     * @return int[]
     */
    public static int[] splitStrToIntArray(String src, String delimiter) {
        return Arrays.stream(src.split(delimiter)).mapToInt(Integer::valueOf).toArray();
    }

    /**
     * 字符串分割为int数组
     * groovy的collect只能转成list，不利于相关查询
     * groovy src.split(",").collect {it as int}
     *
     * @param src       字符串
     * @param delimiter 分隔符
     * @return long[]
     */
    public static long[] splitStrToLongArray(String src, String delimiter) {
        return Arrays.stream(src.split(delimiter)).mapToLong(Long::valueOf).toArray();
    }

    public static List<Long> splitStrToLongList(String src, String delimiter) {
        return Arrays.stream(src.split(delimiter)).map(Long::valueOf).collect(Collectors.toList());
    }

    public static long[] longListToArray(List<Long> src) {
        return src.stream().mapToLong(Long::valueOf).toArray();
    }

    public static String[] longArrayToStrArray(long[] src) {
        return Arrays.stream(src).mapToObj(String::valueOf).toArray(String[]::new);
    }

    public static int[] intListToArray(List<Integer> src) {
        return src.stream().mapToInt(Integer::valueOf).toArray();
    }

    public static String[] intArrayToStrArray(int[] src) {
        return Arrays.stream(src).mapToObj(String::valueOf).toArray(String[]::new);
    }

    /**
     * 按精度转换整数为小数
     * 对于纸张，一般precise=3，对于辅料，按档案获取
     *
     * @param num     长整数
     * @param precise 精度
     * @return 数字
     */
    public static BigDecimal preciseStockQty(long num, int precise) {
        return BigDecimal.valueOf(num).multiply(BigDecimal.valueOf(Math.pow(10, -1 * precise))).setScale(precise, RoundingMode.HALF_UP);
    }

    /**
     * 按固定数量分割list
     *
     * @param list 原list
     * @param size 每个list的长度
     * @param <T>  泛型
     * @return 切割后的数组
     */
    public static <T> ArrayList<List<T>> partitionList(List<T> list, int size) {
        final AtomicInteger counter = new AtomicInteger(0);

        Map<Integer, List<T>> groups = list.stream()
                .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / size));

        return new ArrayList<>(groups.values());
    }

    /**
     * 合并多个list
     *
     * @param lists list数组
     * @param <T>   泛型
     * @return 合并后的list
     */
    @SafeVarargs
    public static <T> List<T> mergeList(List<T>... lists) {
        return Stream.of(lists)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    /**
     * 去重
     * 自带的list去重无法灵活的根据属性进行判断，需要给bean增加equals和hashcode方法
     * <p>
     * someList.stream().filter(distinctByKey(Person::getName))
     *
     * @param keyExtractor 分组表达式
     * @param <T>          泛型
     * @return 过滤条件
     */
    public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    /**
     * 检验是否包含中文
     *
     * @param str 字符串
     * @return 是否包含中文
     */
    public static boolean isContainChinese(String str) {
        Pattern p = Pattern.compile("[\\u4e00-\\u9fa5]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    /**
     * 检验是否包含英文
     *
     * @param str 字符串
     * @return 是否包含英文
     */
    public static boolean isContainEnglish(String str) {
        Pattern p = Pattern.compile("[a-zA-Z]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    /**
     * 检验是否包含数字
     *
     * @param str 字符串
     * @return 是否包含数字
     */
    public static boolean isContainNumber(String str) {
        Pattern p = Pattern.compile("[0-9]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    /**
     * 检验字符串是否包含中文/英文/数字
     *
     * @param str 字符串
     * @return 三位000 代表包含 中文/英文/数字
     */
    public static String distinguishStr(String str) {
        StringBuilder sb = new StringBuilder();
        if (isContainChinese(str)) sb.append("1");
        else sb.append("0");
        if (isContainEnglish(str)) sb.append("1");
        else sb.append("0");
        if (isContainNumber(str)) sb.append("1");
        else sb.append("0");

        return sb.toString();
    }

    /**
     * 将supplierCode转换为字符串
     *
     * @param supplierCode 分表
     * @return supplierCode转换为字符串
     */
    public static String encodeSupplierCode(long supplierCode) {
        return ShortUrlGenerate.encode(supplierCode, 4);
    }

    /**
     * 将字符串转换为supplierCode
     *
     * @param str 字符串
     * @return supplierCode
     */
    public static long decodeSupplierCode(String str) {
        return ShortUrlGenerate.decode(str);
    }

    public static String convertNullToBlank(String str) {
        if (StringUtils.isBlank(str)) return "";
        else return str;
    }

    /**
     * 把输入的金额转换为汉语中人民币的大写
     *
     * @param numberOfMoney 输入的金额
     * @return 对应的汉语大写
     */
    public static String number2CNMontrayUnit(BigDecimal numberOfMoney) {
        StringBuilder sb = new StringBuilder();
        // -1, 0, or 1 as the value of this BigDecimal is negative, zero, or
        // positive.
        int signum = numberOfMoney.signum();
        // 零元整的情况
        if (signum == 0) {
            return CN_ZEOR_FULL;
        }
        // 这里会进行金额的四舍五入
        long number = numberOfMoney.movePointRight(MONEY_PRECISION).setScale(0, RoundingMode.HALF_UP).abs().longValue();
        // 得到小数点后两位值
        long scale = number % 100;
        int numUnit;
        int numIndex = 0;
        boolean getZero = false;
        // 判断最后两位数，一共有四中情况：00 = 0, 01 = 1, 10, 11
        if (!(scale > 0)) {
            numIndex = 2;
            number = number / 100;
            getZero = true;
        }
        if ((scale > 0) && (!(scale % 10 > 0))) {
            numIndex = 1;
            number = number / 10;
            getZero = true;
        }
        int zeroSize = 0;
        while (number > 0) {
            // 每次获取到最后一个数
            numUnit = (int) (number % 10);
            if (numUnit > 0) {
                if ((numIndex == 9) && (zeroSize >= 3)) {
                    sb.insert(0, CN_UPPER_MONETRAY_UNIT[6]);
                }
                if ((numIndex == 13) && (zeroSize >= 3)) {
                    sb.insert(0, CN_UPPER_MONETRAY_UNIT[10]);
                }
                sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
                sb.insert(0, CN_UPPER_NUMBER[numUnit]);
                getZero = false;
                zeroSize = 0;
            } else {
                ++zeroSize;
                if (!(getZero)) {
                    sb.insert(0, CN_UPPER_NUMBER[numUnit]);
                }
                if (numIndex == 2) {
                    sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
                } else if (((numIndex - 2) % 4 == 0) && (number % 1000 > 0)) {
                    sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
                }
                getZero = true;
            }
            // 让number每次都去掉最后一个数
            number = number / 10;
            ++numIndex;
        }
        // 如果signum == -1，则说明输入的数字为负数，就在最前面追加特殊字符：负
        if (signum == -1) {
            sb.insert(0, CN_NEGATIVE);
        }
        // 输入的数字小数点后两位为"00"的情况，则要在最后追加特殊字符：整
        if (!(scale > 0)) {
            sb.append(CN_FULL);
        }
        return sb.toString();
    }

    public static double getStrNumToDouble(String str, int index) {
        if (StringUtils.isBlank(str)) {
            return 0;
        }
        Pattern pattern = Pattern.compile("[^0-9.]");
        Matcher a1 = pattern.matcher(str);
        String[] dis = a1.replaceAll(" ").split("\\s+");
        if (dis.length > 0) {
            if (StringUtils.isEmpty(dis[0])) index++;
            if (index >= dis.length) return 0;
            String result = dis[index];
            return Double.parseDouble(result);
        } else return 0;
    }

    public static int getStrNumToInt(String str, int index) {
        if (StringUtils.isBlank(str)) {
            return 0;
        }
        Pattern pattern = Pattern.compile("[^0-9.]");
        Matcher a1 = pattern.matcher(str);
        String[] dis = a1.replaceAll(" ").split("\\s+");
        if (dis.length > 0) {
            if (StringUtils.isEmpty(dis[0])) index++;
            if (index >= dis.length) return 0;
            String result = dis[index];
            return Integer.parseInt(result);
        } else return 0;
    }

    public static BigDecimal stringToBigDecimal(String str) {
        BigDecimal a;
        try {
            a = new BigDecimal(str);
        } catch (Exception ignored) {
            a = BigDecimal.ZERO;
        }
        return a;
    }

    public static String lineSeparator() {
        return System.lineSeparator();
    }

    public static ERPTokenUser getSysTokenUser() {
        return SYSTOKENUSER;
    }

    public static ERPTokenUser getSysImageUser() {
        return SYSIMAGEUSER;
    }

    public static ERPTokenUser getSysNoneImageUser() {
        return SYS_NONE_IMAGE_USER;
    }

    public static ERPTokenUser getSysForceUser() {
        return SYSFORCEUSER;
    }

    public static ERPTokenUser getSysDnyFeedBackUser() {
        return SYS_ERP_CALL_USER;
    }

    public static ERPTokenUser getSysRemoteErpUser() {
        return SYS_REMOTE_ERP_USER;
    }

    //获取当月开始毫秒值
    public static long getMonthStartTimeMilliseconds() {
        LocalDate thisMonth = LocalDate.now();
        LocalDate firstDayThisMonth = thisMonth.withDayOfMonth(1);
        LocalDateTime localDateTime = firstDayThisMonth.atStartOfDay();
        //设置正确的时区
        return localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    //获取当天开始毫秒值
    public static long getDayStartTimeMilliseconds() {
        LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
        return todayStart.toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    //获取当天结束毫秒值
    public static long getDayEndTimeMilliseconds() {
        LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        return todayStart.toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    /**
     * 从订单付款方式中获取相应类型的金额
     *
     * @param payKind 付款方式
     * @param type    付款类型
     * @return 金额
     */
    public static BigDecimal getMoneyFromPayKind(String payKind, int type) {
        //系统允许的最复杂情况： 0:现金收款:100:aaa+1:物流代收:200:bbb+2:担保人:300:张翠山+3:预存:500:ccc
        BigDecimal returnMoney = BigDecimal.ZERO;
        if (StringUtils.isBlank(payKind)) {
            return returnMoney;
        }
        String[] payKinds = payKind.split("\\+");
        String strMoney = "";
        for (String a : payKinds) {
            if (StringUtils.isBlank(a)) {
                return returnMoney;
            }
            String[] aList = a.split(":");
            if (aList.length == 0) {
                return returnMoney;
            }
            if (aList[0].equalsIgnoreCase(String.valueOf(type))) {
                strMoney = aList[2];
                break;
            }
        }
        if (StringUtils.isNotBlank(strMoney)) {
            double aMoney = getStrNumToDouble(strMoney.trim(), 0);
            return new BigDecimal(aMoney).setScale(2, RoundingMode.HALF_UP);
        } else {
            return returnMoney;
        }
    }

    public static String setPayKindMoney(String payKind, int type, BigDecimal aMoney) {
        //0:现金收款:100:aaa+1:物流代收:200:bbb+2:担保人:300:张翠山+3:预存:500:ccc
        if (StringUtils.isBlank(payKind)) {
            return payKind;
        }
        if (aMoney == null) {
            aMoney = BigDecimal.ZERO;
        }
        String[] payKinds = payKind.split("\\+");
        String resultStr = "";
        for (String a : payKinds) {
            if (StringUtils.isBlank(a)) {
                return payKind;
            }
            String[] aList = a.split(":");
            if (aList.length <= 2) {
                return payKind;
            }
            if (aList[0].equalsIgnoreCase(String.valueOf(type))) {
                if (StringUtils.isBlank(resultStr)) {
                    resultStr = aList[0] + ":" + aList[1] + ":" + aMoney + ":" + (aList.length >= 4 ? aList[3] : "");
                } else {
                    resultStr = resultStr + "+" + aList[0] + ":" + aList[1] + ":" + aMoney + ":" + (aList.length >= 4 ? aList[3] : "");
                }
            } else {
                resultStr = StringUtils.isBlank(resultStr) ? a : resultStr + "+" + a;
            }
        }
        return resultStr;
    }


    public static String parseTimeFormat(Date date, String format) {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(date);
    }

    public static String quotedStr(String inputStr) {
        if (StringUtils.isBlank(inputStr)) {
            return "";
        }
        return "'" + inputStr + "'";
    }

    /**
     * Groovy此代码会有错误，改为java实现
     * 主要是地址解析使用
     *
     * @param address 地址
     * @return 格式化字符串
     */
    public static String formatParseAddress(String address) {
        //        dest = dest.replaceAll(SPECIAL_SYMBOL_REGEX, " ");

        return address.replaceAll("\\r\\n", " ")
                .replaceAll("\\n", " ")
                .replace("\\t", " ")
                .replace(" {2,}", " ")
                .replaceAll("(\\d{3})-(\\d{4})-(\\d{4})", "$1$2$3")
                .replace("(\\d{3}) (\\d{4}) (\\d{4})", "$1$2$3");
    }

    public static RetResult<String> initBoxDesc(String boxDesc) {
        String str = "\\s* | \\t | \\r | \\n";
        boxDesc = boxDesc.replaceAll(str, "");
        String[] boxDescArr = boxDesc.split("\\+");
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < boxDescArr.length; i++) {
            if (!boxDescArr[i].contains("*"))
                boxDescArr[i] = "1*" + boxDescArr[i];
            else if (boxDescArr[i].split("\\*").length > 0 && "".equals(boxDescArr[i].split("\\*")[0]))
                //还有3*300+*100,5*350+*250+1*2这样的奇葩数据,要正确初始化,用1填补成规范数据
                boxDescArr[i] = "1" + boxDescArr[i];

            if (i == boxDescArr.length - 1) {
                stringBuilder.append(boxDescArr[i]);
            } else {
                stringBuilder.append(boxDescArr[i]).append("+");
            }

            String[] split = boxDescArr[i].split("\\*");
            //如果是1*100+*这样的就要判断长度,避免报错...
//            if (split.length <= 1)
            if (split.length != 2)
                return RetResult.<String>errorT().retcode(-1).retinfo("非法打包数据:[ " + boxDesc + "],请重新输入!");

            if (Long.parseLong(split[1]) < 0)
                return RetResult.<String>errorT().retcode(-1).retinfo("输入的打包数量存在负数,请重新输入!");
        }
        return RetResult.<String>successT().retcode(0).result(stringBuilder.toString());
    }

    public static void createFileFromInStream(int bufferSize, String destFile, InputStream in) throws IOException {
        byte[] buffer = new byte[bufferSize];
        int len;
        boolean isWrited = false;
        Path destFilePath = Paths.get(destFile);

        while ((len = in.read(buffer)) != -1) {
            if (!isWrited) {
                try (OutputStream out = Files.newOutputStream(destFilePath, StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
                    out.write(buffer, 0, len);
                }
            } else {
                try (OutputStream out = Files.newOutputStream(destFilePath, StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
                    out.write(buffer, 0, len);
                }
            }
            isWrited = true;
        }
    }

    public static void createFileFromInStream(String destFile, InputStream in) throws IOException {
        createFileFromInStream(4096, destFile, in);
    }

    public static void appendFileFromInStream(int bufferSize, String destFile, InputStream in) throws IOException {
        writeFileFromInStream(bufferSize, destFile, in, StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.SYNC);
    }

    public static void appendFileFromInStream(String destFile, InputStream in) throws IOException {
        writeFileFromInStream(4096, destFile, in, StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.SYNC);
    }

    public static void writeFileFromInStream(int bufferSize, String destFile, InputStream in, OpenOption... options) throws IOException {
        byte[] buffer = new byte[bufferSize];
        int len;
        while ((len = in.read(buffer)) != -1) {
            try (OutputStream out = Files.newOutputStream(Paths.get(destFile), options)) {
                out.write(buffer, 0, len);
            }
        }
    }

    public static String replaceEscape(String str) {
        String dest = "";
        if (str != null) {
            Pattern p = Pattern.compile("[\r\n]");
            Matcher m = p.matcher(str);
            dest = m.replaceAll("");
        }
        return dest;
    }

    public static long calcDaysBetweenDates(Date beginDate, Date endDate) {
        return (endDate.getTime() - beginDate.getTime()) / (24 * 60 * 60 * 1000);
    }

    public static void main(String[] args) throws Exception {
        BigDecimal testb = new BigDecimal(13900);

        System.out.println(testb.setScale(-3, RoundingMode.CEILING).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.FLOOR).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.HALF_UP).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.HALF_DOWN).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.HALF_EVEN).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.UP).toPlainString());
        System.out.println(testb.setScale(-3, RoundingMode.DOWN).toPlainString());


    }

    /**
     * 纸张吨价和令价的转换
     *
     * @param mtRule   纸张规格
     * @param mtSuttle 纸张克重
     */
    public static BigDecimal convertUnitByToMt(String mtRule, BigDecimal mtSuttle) {
        String[] split = mtRule.split("\\*");

        BigDecimal paperLong = new BigDecimal(split[0]).multiply(BigDecimal.valueOf(0.001)).setScale(8, RoundingMode.HALF_UP);
        BigDecimal paperWidth = new BigDecimal(split[1]).multiply(BigDecimal.valueOf(0.001)).setScale(8, RoundingMode.HALF_UP);

        return new BigDecimal(String.valueOf(1000000)).divide(paperLong.multiply(paperWidth).multiply(mtSuttle).multiply(BigDecimal.valueOf(500)), 4, RoundingMode.HALF_UP).setScale(4, RoundingMode.HALF_UP);
    }

    /**
     * 使用数量转换为库存数量
     */
    public static BigDecimal useQtyToStockQty(BigDecimal seQty, BigDecimal vUnit) {
        if (vUnit == null) {
            return BigDecimal.ZERO;
        }

        if (vUnit.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        return seQty.divide(vUnit, 8, RoundingMode.HALF_UP).setScale(8, RoundingMode.HALF_UP);
    }

    /**
     * 使用数量转换为采购数量
     */
    public static BigDecimal useQtyToBuyQty(BigDecimal seQty, BigDecimal vUnit1, BigDecimal vUnit2) {
        if (vUnit1 == null) {
            return BigDecimal.ZERO;
        }

        if (vUnit1.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        if (vUnit2 == null) {
            return BigDecimal.ZERO;
        }

        if (vUnit2.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        return seQty.divide(vUnit1.multiply(vUnit2), 8, RoundingMode.HALF_UP).setScale(8, RoundingMode.HALF_UP);
    }

    /**
     * 库存数量换为采购数量
     *
     * @param mtQty 库存数量
     */
    public static BigDecimal stockQtyToBuyQty(BigDecimal mtQty, BigDecimal vUnit) {
        if (vUnit == null) {
            return BigDecimal.ZERO;
        }

        if (vUnit.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        return mtQty.divide(vUnit, 8, RoundingMode.HALF_UP).setScale(8, RoundingMode.HALF_UP);
    }

    /**
     * 库存数量换为使用数量
     *
     * @param mtQty 库存数量
     */
    public static BigDecimal stockQtyToUseQty(BigDecimal mtQty, BigDecimal vUnit) {
        if (vUnit == null) {
            return BigDecimal.ZERO;
        }

        if (vUnit.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        return mtQty.multiply(vUnit).setScale(8, RoundingMode.HALF_UP);
    }

    /**
     * 按精度转换BigDecimal为整数
     * 对于纸张，一般precise=3，对于辅料，按档案获取
     *
     * @param qty
     * @param precise
     * @return
     */
    public static long preciseStockNumber(BigDecimal qty, int precise) {
        return qty.multiply(BigDecimal.valueOf(Math.pow(10, precise))).longValue();
    }

    public static double px2mm(double px) {
        return px / DEFAULT_DPI * DEFAULT_RATIO;
    }

    public static double mm2px(double mm) {
        return mm * DEFAULT_DPI / DEFAULT_RATIO;
    }

    public static String generateUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}
