package com.younike.common.utils.redenvelope;

import lombok.extern.log4j.Log4j2;

import java.util.LinkedList;
import java.util.List;

/**
 * @author yangby
 * @description
 * @date 2021/9/22
 */
@Log4j2
public class RedEnvelopeUtil {

    /**
     * 每个红包最小金额，单位为与scale有关,例如scale为2,单位为分
     *//*
    private static final int MIN_MONEY = 1;

    *//**
     * 红包金额的离散程度，值越大红包金额越分散
     *//*
    private static final double DISPERSE = 1;

    *//**
     * 根据剩余的总量和总个数，获取一个随机的量
     *
     * @param amount 总量
     * @param count  总个数
     * @return 随机量
     *//*
    public static BigDecimal getOneRedBag(BigDecimal amount, int count, int scale) {
        //pow函数是个计算 10的scale次方的函数
        int money = amount.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        if (money < MIN_MONEY * count) {
            log.error("amount={}, count={},最小值设置过大", amount.toPlainString(), count);
            throw new RuntimeException("最小值设置过大");
        }

        //最大值 = 均值*离散程度
        int max = (int) (money * DISPERSE / count);

        //最大值不能大于总金额
        max = Math.min(max, money);
        return new BigDecimal(randomBetweenMinAndMax(money, count, MIN_MONEY, max)).divide(BigDecimalUtils.pow(10, scale), scale,
                RoundingMode.HALF_UP);

    }

    *//**
     * 在最小值和最大值之间随机产生一个
     *
     * @param money
     * @param count
     * @param min   : 最小量
     * @param max   ： 最大量
     * @return
     *//*
    public static int randomBetweenMinAndMax(int money, int count, int min, int max) {
        //最后一个直接返回
        if (count == 1) {
            return money;
        }
        //最小和最大金额一样，返最小和最大值都行
        if (min == max) {
            return min;
        }
        //最小值 == 均值， 直接返回最小值
        if (min == money / count) {
            return min;
        }
        //min<=随机数bag<=max
        int bag = ((int) Math.rint(Math.random() * (max - min) + min));

        //剩余的均值
        int avg = (money - bag) / (count - 1);
        //比较验证剩余的还够不够分(均值>=最小值 是必须条件),不够分的话就是最大值过大
        if (avg < MIN_MONEY) {
            *//*
             * 重新随机一个，最大值改成本次生成的量
             * 由于 min<=本次金额bag<=max, 所以递归时bag是不断减小的。
             * bag在减小到min之间一定有一个值是合适的，递归结束。
             * bag减小到和min相等时，递归也会结束，所以这里不会死递归。
             *//*
            return randomBetweenMinAndMax(money, count, min, bag);
        } else {
            return bag;
        }
    }

    *//**
     * 将一个BigDecimal 切分成指定个数指定精度指定范围的若干个数,如果范围设置不当,前面随机的数较小,可能会导致最后一个数过大超出范围
     * @param amount 总数量
     * @param count 总个数
     * @param scale 精度
     * @param min 最大值
     * @param max 最小值
     * @return
     *//*
    public static List<BigDecimal> splitBigDecimalFromRange(BigDecimal amount, int count, int scale, BigDecimal min, BigDecimal max) {
        BigDecimal total = new BigDecimal(0);
        List<BigDecimal> list = Lists.newArrayList();
        for (int i = 0; i < count; i++) {
            BigDecimal tem = getOneRedBag(amount.subtract(total), count - i, scale, min, max);
            total = total.add(tem);
            list.add(tem);
        }
        return list;
    }


    private static BigDecimal getOneRedBag(BigDecimal amount, int count, int scale, BigDecimal min, BigDecimal max) {
        // 转成int做运算
        int amountInt = amount.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        int minInt = min.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        int maxInt = max.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        if (amountInt < minInt * count) {
            throw new RuntimeException("最小值设置过大");
        }
        if (minInt > maxInt){
            throw new RuntimeException("最大值小于最小值");
        }
        if (maxInt * count < amountInt){
            throw new RuntimeException("最大值设置过小");
        }
        //最大值不能大于总金额
        maxInt = Math.min(maxInt, amountInt);
        //randomBetweenMinAndMax()上面有这个方法
        return new BigDecimal(randomBetweenMinAndMax(amountInt, count, minInt, maxInt)).divide(BigDecimalUtils.pow(10, scale), scale,
                RoundingMode.HALF_UP);
    }*/


    /*public static void main(String[] args) {
        //总量
        BigDecimal amount = new BigDecimal(1);
        //总个数
        int count = 10;

        //最后这个数要和amount一致才对
        BigDecimal total = new BigDecimal(0);
        List<BigDecimal> list = Lists.newArrayList();
        for (int i = 0; i < count; i++) {
            BigDecimal tem = getOneRedBag(amount.subtract(total), count - i, 4);
            total = total.add(tem);
            list.add(tem);
            System.out.println("第" + (count - i) + "个红包的金额是：" + tem + "元");
        }
        //总金额是否相等
        System.out.println("总计金额是否相等：" + (total.compareTo(amount) == 0));
        System.out.println("红包个数:" + list.size());
        System.out.println("红包金额明细:" + list);
        Collections.sort(list);
        System.out.println("排序后的红包明细:" + list);
    }*/

    /**
     * 生成红包最小值 1分
     */
    private static final int MIN_MONEY = 1;

    /**
     * 生成红包最大值 200人民币
     */
    private static final int MAX_MONEY = 200 * 100;

    /**
     * 小于最小值
     */
    private static final int LESS = -1;
    /**
     * 大于最大值
     */
    private static final int MORE = -2;

    /**
     * 正常值
     */
    private static final int OK = 1;

    /**
     * 最大的红包是平均值的 TIMES 倍，防止某一次分配红包较大
     */
    private static final double TIMES = 2.1F;

    private int recursiveCount = 0;

    public List<Integer> splitRedPacket(int money, int count) {
        List<Integer> moneys = new LinkedList<>();

        //金额检查，如果最大红包 * 个数 < 总金额；则需要调大最小红包 MAX_MONEY
        if (MAX_MONEY * count <= money) {
            System.err.println("请调大最小红包金额 MAX_MONEY=[" + MAX_MONEY + "]");
            return moneys ;
        }


        //计算出最大红包
        int max = (int) ((money / count) * TIMES);
        max = Math.min(max, MAX_MONEY);

        for (int i = 0; i < count; i++) {
            //随机获取红包
            int redPacket = randomRedPacket(money, MIN_MONEY, max, count - i);
            moneys.add(redPacket);
            //总金额每次减少
            money -= redPacket;
        }

        return moneys;
    }

    private int randomRedPacket(int totalMoney, int minMoney, int maxMoney, int count) {
        //只有一个红包直接返回
        if (count == 1) {
            return totalMoney;
        }

        if (minMoney == maxMoney) {
            return minMoney;
        }

        //如果最大金额大于了剩余金额 则用剩余金额 因为这个 money 每分配一次都会减小
        maxMoney = Math.min(maxMoney, totalMoney);

        //在 minMoney到maxMoney 生成一个随机红包
        int redPacket = (int) (Math.random() * (maxMoney - minMoney) + minMoney);

        int lastMoney = totalMoney - redPacket;

        int status = checkMoney(lastMoney, count - 1);

        //正常金额
        if (OK == status) {
            return redPacket;
        }

        //如果生成的金额不合法 则递归重新生成
        if (LESS == status) {
            recursiveCount++;
            System.out.println("recursiveCount==" + recursiveCount);
            return randomRedPacket(totalMoney, minMoney, redPacket, count);
        }

        if (MORE == status) {
            recursiveCount++;
            System.out.println("recursiveCount===" + recursiveCount);
            return randomRedPacket(totalMoney, redPacket, maxMoney, count);
        }

        return redPacket;
    }

    /**
     * 校验剩余的金额的平均值是否在 最小值和最大值这个范围内
     *
     * @param lastMoney
     * @param count
     * @return
     */
    private int checkMoney(int lastMoney, int count) {
        double avg = lastMoney / count;
        if (avg < MIN_MONEY) {
            return LESS;
        }

        if (avg > MAX_MONEY) {
            return MORE;
        }

        return OK;
    }


    public static void main(String[] args) {
        RedEnvelopeUtil redPacket = new RedEnvelopeUtil();
        List<Integer> redPackets = redPacket.splitRedPacket(20000, 100);
        System.out.println(redPackets);

        int sum = 0;
        for (Integer red : redPackets) {
            sum += red;
        }
        System.out.println(sum);
    }

}
