package net.northking.cctp.executePlan.utils;

import cn.hutool.core.collection.CollUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>Title: PercentageCalculatorUtil</p>
 * <p>Description: 百分比计算</p>
 *
 * @Author chenjianfeng
 * @Version V1.0
 * @Date 2023/9/26 17:09
 * <p>Copyright (c) 京北方信息技术股份有限公司 Corporation 2022 . All rights reserved.</p>
 */
public class PercentageCalculatorUtil {

    public static final int ONE_HUNDRED = 100;

    public static List<Option> computePercentage(List<Option> src){
        Integer total = src.stream().map(Option::getVoteCount).reduce(0, Integer::sum);
        if (total == null || total <= 0){
            throw new RuntimeException("计算百分比总数不能小于等于0");
        }
        BigDecimal totalBigDecimal = BigDecimal.valueOf(total);
        BigDecimal hundredBigDecimal = BigDecimal.valueOf(ONE_HUNDRED);
        List<IdDecimalTuple> decimalPartList = new LinkedList<>();
        src.forEach(votable -> {
            BigDecimal percentage = BigDecimal.valueOf(votable.getVoteCount())
                    .divide(totalBigDecimal, 6, RoundingMode.FLOOR).multiply(hundredBigDecimal);
            votable.setPercentage(percentage.setScale(0, RoundingMode.FLOOR).intValue());
            decimalPartList.add(new IdDecimalTuple(votable, percentage.remainder(BigDecimal.ONE)));
        });
        Integer totalPercentage = src.stream().map(Option::getPercentage).reduce(0, Integer::sum);
        if (Objects.equals(totalPercentage, ONE_HUNDRED)){
            return src;
        }
        List<IdDecimalTuple> sortedEntry = decimalPartList.stream()
                .sorted(Comparator.comparing(IdDecimalTuple::getDecimal).reversed())
                .collect(Collectors.toList());
        IntStream.range(0, ONE_HUNDRED - totalPercentage).mapToObj(i -> sortedEntry.get(i).getId())
                .forEach(option -> option.setPercentage(option.getPercentage() + 1));
        return src;
    }

    public static Map<String, Integer> computePercentage(Map<String, Integer> params){
        if (CollUtil.isEmpty(params)){
            throw new RuntimeException("百分比计算参数不能为空");
        }
        List<Option> options = new ArrayList<>();
        params.forEach((key, value) -> {
            Option option = new Option(value, key);
            options.add(option);
        });

        List<Option> optionResult = computePercentage(options);
        return optionResult.stream()
                .collect(Collectors.toMap(Option::getCodeStr, Option::getPercentage));
    }

    private static class IdDecimalTuple{
        /**
         * 传送的对象
         */
        private Option id;

        /**
         * 精度
         */
        private BigDecimal decimal;

        public IdDecimalTuple(Option id, BigDecimal decimal) {
            this.id = id;
            this.decimal = decimal;
        }

        public Option getId() {
            return id;
        }

        public void setId(Option id) {
            this.id = id;
        }

        public BigDecimal getDecimal() {
            return decimal;
        }

        public void setDecimal(BigDecimal decimal) {
            this.decimal = decimal;
        }
    }

    public static class Option{
        /**
         * 原数字
         */
        private Integer voteCount;

        /**
         * 占的百分比
         */
        private Integer percentage;

        /**
         * 唯一标识
         */
        private String codeStr;

        public Option(Integer voteCount, String codeStr) {
            this.voteCount = voteCount;
            this.codeStr = codeStr;
        }

        public Integer getVoteCount() {
            return voteCount;
        }

        public void setVoteCount(Integer voteCount) {
            this.voteCount = voteCount;
        }

        public Integer getPercentage() {
            return percentage;
        }

        public void setPercentage(Integer percentage) {
            this.percentage = percentage;
        }

        public String getCodeStr() {
            return codeStr;
        }

        public void setCodeStr(String codeStr) {
            this.codeStr = codeStr;
        }
    }
}
