package com.sw.mes.production.service.impl;


import com.alibaba.fastjson.JSON;
import com.sw.mes.core.base.BaseServiceImpl;
import com.sw.mes.core.context.BaseContextHandler;
import com.sw.mes.production.dao.FormulaSchemeDao;
import com.sw.mes.production.dto.*;
import com.sw.mes.production.entity.*;
import com.sw.mes.production.service.*;
import com.sw.mes.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;


/**
 * @Author
 * @Description：
 * @Date: Create in
 * @Modified By:
 */
@Slf4j
@Service
@Transactional
public class FormulaSchemeServiceImpl extends BaseServiceImpl<FormulaScheme> implements FormulaSchemeService {
    FormulaSchemeDao dao;

    @Autowired
    public void setDao(FormulaSchemeDao dao) {
        super.setBaseDao(dao);
        this.dao = dao;
    }

    @Autowired
    private FormulaSchemeDetailService formulaSchemeDetailService;

    @Autowired
    private FormulaWeightSchemeService formulaWeightSchemeService;

    @Autowired
    private FormulaSchemeService formulaSchemeService;

    @Autowired
    private FormulaProductSchemeService formulaProductSchemeService;

    @Autowired
    private FormulaProductSchemeDetailService productSchemeDetailService;

    @Autowired
    private FormulaProductSchemeWeightDetailService productSchemeWeightDetailService;

    @Override
    public ValidateResult validate(ValidateScheme validateScheme) {
        FormulaScheme formulaScheme = new FormulaScheme();
        BeanUtils.copyProperties(validateScheme.getFormulaSchemeDto(), formulaScheme);
        FormulaWeightScheme formulaWeightScheme = formulaWeightSchemeService.get(validateScheme.getWeightSchemeId());

        FormulaSchemeData formulaSchemeData = new FormulaSchemeData();
        formulaSchemeData.setSchemeId(formulaScheme.getId());
        formulaSchemeData.setSchemeCode(formulaScheme.getCode());
        formulaSchemeData.setSchemeVersion(formulaScheme.getVersion());
        formulaSchemeData.setWeightSchemeId(validateScheme.getWeightSchemeId());
        formulaSchemeData.setWeightSchemeName(formulaWeightScheme.getName());
        FormulaSchemeDataParam formulaSchemeDataParam = new FormulaSchemeDataParam();
        formulaSchemeDataParam.setWeight(validateScheme.getWeight());
        formulaSchemeDataParam.setType(1);
        formulaSchemeDataParam.setRepeatTimes(validateScheme.getRepeatTimes());
        formulaSchemeDataParam.setFormulaWeightScheme(formulaWeightScheme);
        LinkedList<BarrelCountDto> barrelCount = getBarrelCounts(formulaSchemeDataParam);
        formulaSchemeDataParam.setBarrelCountDtos(barrelCount);
        formulaSchemeDataParam.setMaterialWeights(null);
        formulaSchemeDataParam.setFormulaScheme(formulaScheme);

        boolean validateStatus = this.ingredienDecompositionScheme(barrelCount, formulaSchemeDataParam, formulaSchemeData);
        ValidateResult validateResult = new ValidateResult();
        validateResult.setStatus(validateStatus);
        validateResult.setMaterialFeedingDetailArrs(formulaSchemeData.getMaterialFeedingDetailArrs());
        return validateResult;
    }

    /**
     * 分解方案（分解全部桶）
     *
     * @return FormulaSchemeData 方案数据
     */
    @Override
    public FormulaSchemeData decompositionScheme(DecompositionSchemeParam decompositionSchemeParam) {
        FormulaProductScheme formulaProductScheme = formulaProductSchemeService.findByEquipmentCodeAndProductModeCode(decompositionSchemeParam.getEquipmentCode(), decompositionSchemeParam.getProductModeCode());
        Assert.notNull(formulaProductScheme, "未找到该设备号和产品料号的产品配方方案");
        // 获取重量方案
        FormulaWeightScheme formulaWeightScheme = null;
        FormulaProductSchemeWeightDetail productSchemeWeightDetail = productSchemeWeightDetailService.findByFpsIdAndScope(formulaProductScheme.getId(),
                decompositionSchemeParam.getRepeatTimes()==0? new BigDecimal("500"): decompositionSchemeParam.getWeight());
        if (!ObjectUtils.isEmpty(productSchemeWeightDetail)){
            formulaWeightScheme = formulaWeightSchemeService.get(productSchemeWeightDetail.getWeightSchemeId());
        }else if (!ObjectUtils.isEmpty(formulaProductScheme.getDecompositionSchemeId())){
            formulaWeightScheme = formulaWeightSchemeService.get(formulaProductScheme.getDecompositionSchemeId());
        }
        Assert.notNull(formulaWeightScheme, "未找到该产品配方对应的拆桶方案");

        // 获取配方方案
        Long formulaSchemeId;
        FormulaProductSchemeDetail schemeDetail = null;
        if (decompositionSchemeParam.getRepeatTimes() == 0){
            schemeDetail = productSchemeDetailService.findByFpsIdAndAgainNum(formulaProductScheme.getId(),  0);
        } else if (!ObjectUtils.isEmpty(decompositionSchemeParam.getIsFillEnd()) && 1 == decompositionSchemeParam.getIsFillEnd()){
            schemeDetail = productSchemeDetailService.findByFpsIdAndWsIdAndAgainNum(formulaProductScheme.getId(), formulaWeightScheme.getId(), 99);
        }else {
            schemeDetail = productSchemeDetailService.findByFpsIdAndWsIdAndAgainNum(formulaProductScheme.getId(), formulaWeightScheme.getId(), decompositionSchemeParam.getRepeatTimes());
        }
        if (!ObjectUtils.isEmpty(schemeDetail)){
            formulaSchemeId = schemeDetail.getFormulaSchemeId();
        }else {
            // 兼容过渡期
            if (decompositionSchemeParam.getRepeatTimes() == 0) {
                formulaSchemeId = formulaProductScheme.getInitialFormulaId();
            } else if (decompositionSchemeParam.getRepeatTimes() == 1) {
                formulaSchemeId = formulaProductScheme.getInitialReinvestmentId();
            } else {
                formulaSchemeId = formulaProductScheme.getReinvestmentId();
            }
        }
        Assert.notNull(formulaSchemeId, "未查询到配方数据");
        FormulaScheme formulaScheme = formulaSchemeService.get(formulaSchemeId);
        Assert.notNull(formulaScheme, "未查询到配方数据");

        FormulaSchemeDataParam formulaSchemeDataParam = new FormulaSchemeDataParam();
        formulaSchemeDataParam.setWeight(decompositionSchemeParam.getWeight());
        if (formulaSchemeDataParam.getWeight() == null || formulaSchemeDataParam.getWeight().compareTo(BigDecimal.ZERO) <= 0) {
            Assert.isTrue(decompositionSchemeParam.getRepeatTimes() == 0, "初始装料分解方案，必须传入复投重量");
            Assert.isTrue(formulaWeightScheme.getInitialWeight() != null && formulaWeightScheme.getInitialWeight().compareTo(BigDecimal.ZERO) > 0, "【" + formulaWeightScheme.getName() + "】该重量方案重的初装重量为空或等于 0");
            formulaSchemeDataParam.setWeight(formulaWeightScheme.getInitialWeight());
        }
        formulaSchemeDataParam.setType(decompositionSchemeParam.getType());
        formulaSchemeDataParam.setRepeatTimes(decompositionSchemeParam.getRepeatTimes());
        formulaSchemeDataParam.setFormulaWeightScheme(formulaWeightScheme);
        LinkedList<BarrelCountDto> barrelCount = getBarrelCounts(formulaSchemeDataParam);
        formulaSchemeDataParam.setBarrelCountDtos(barrelCount);
        formulaSchemeDataParam.setMaterialWeights(decompositionSchemeParam.getMaterialWeights());
        formulaSchemeDataParam.setFormulaScheme(formulaScheme);

        FormulaSchemeData formulaSchemeData = new FormulaSchemeData();
        formulaSchemeData.setSchemeId(formulaScheme.getId());
        formulaSchemeData.setSchemeCode(formulaScheme.getCode());
        formulaSchemeData.setSchemeVersion(formulaScheme.getVersion());
        formulaSchemeData.setWeightSchemeId(formulaWeightScheme.getId());
        formulaSchemeData.setWeightSchemeName(formulaWeightScheme.getName());
        formulaSchemeData.setBarrelSpecs(formulaWeightScheme.getBarrelSpecs());
        boolean validateStatus = this.ingredienDecompositionScheme(barrelCount, formulaSchemeDataParam, formulaSchemeData);
        Assert.isTrue(validateStatus, "分解方案失败，总重量不一致");
        return formulaSchemeData;
    }

    @Override
    public void saveOrUpdate(FormulaScheme formulaScheme, List<FormulaSchemeDetail> formulaSchemeDetails) {
        formulaScheme.setGmtUpdate(new Date());
        formulaScheme.setUserUpdate(BaseContextHandler.getUsername());
        saveOrUpdate(formulaScheme);
        formulaSchemeDetails.forEach(formulaSchemeDetail -> {
            formulaSchemeDetail.setFormulaSchemeId(formulaScheme.getId());
            formulaSchemeDetail.setGmtUpdate(new Date());
            formulaSchemeDetail.setUserUpdate(BaseContextHandler.getUsername());
        });
        formulaSchemeDetailService.saveAll(formulaSchemeDetails);
    }

    @Override
    public Integer getMaxVersion(String code) {
        return Optional.ofNullable(dao.findTopByCodeOrderByVersionDesc(code)).map(FormulaScheme::getVersion).orElse(1);
    }

    /**
     * 分解桶数
     *
     * @param formulaSchemeDataParam 参数
     * @return 桶数及重量
     */
    public LinkedList<BarrelCountDto> getBarrelCounts(FormulaSchemeDataParam formulaSchemeDataParam) {
        formulaSchemeDataParam.setThisWeight(formulaSchemeDataParam.getWeight());
        LinkedList<BarrelCountDto> barrelCountDtos = new LinkedList<>();
        if (formulaSchemeDataParam.getRepeatTimes() == 0) {
            barrelCountDtos.add(new BarrelCountDto(1, formulaSchemeDataParam.getWeight()));
            return barrelCountDtos;
        }
        Assert.isTrue(formulaSchemeDataParam.getWeight().compareTo(formulaSchemeDataParam.getScopeLower()) >= 0, "投料重量小于报料范围下限");
        Assert.isTrue(formulaSchemeDataParam.getWeight().compareTo(formulaSchemeDataParam.getScopeUpper()) <= 0, "投料重量大于报料范围上限");
        Assert.isTrue(formulaSchemeDataParam.getThisWeight().remainder(BigDecimal.valueOf(10)).compareTo(BigDecimal.ZERO) == 0, "重量必须是10的倍数");

//        // 之前的方案（至少包含一个首桶）
//        execBarrelCountsV1(formulaSchemeDataParam, barrelCountDtos);
        // 无需首桶
        execBarrelCountsV2(formulaSchemeDataParam, barrelCountDtos);
        return barrelCountDtos;
    }

    private void execBarrelCountsV2(FormulaSchemeDataParam formulaSchemeDataParam, LinkedList<BarrelCountDto> barrelCountDtos) {
        BigDecimal weight = formulaSchemeDataParam.getThisWeight();
        // 所有桶重量明细
        List<BigDecimal> barrelCounts = new LinkedList<>();
        // 先把首桶减出去
        final Integer[] index = {1};
        if (formulaSchemeDataParam.getType() == 1) {
            weight = weight.subtract(formulaSchemeDataParam.getFirstBarrelWeightUpper());
            barrelCounts.add(0, formulaSchemeDataParam.getFirstBarrelWeightUpper());
        } else {
            index[0] = 3;
        }
        // 标准桶数
        int standardBarrelCount = weight.divide(formulaSchemeDataParam.getStandardBarrelWeightUpper(), 0, RoundingMode.DOWN).intValue();
        for (int i = 0; i < standardBarrelCount; i++) {
            barrelCounts.add(formulaSchemeDataParam.getStandardBarrelWeightUpper());
        }
        // 尾桶的重量
        BigDecimal lastBarrelWeight = weight.remainder(formulaSchemeDataParam.getStandardBarrelWeightUpper());
        // 如果尾桶的重量 = 0, 就把标准桶最后一桶就为尾桶
        if (lastBarrelWeight.compareTo(BigDecimal.ZERO) == 0) {
            if (standardBarrelCount > 0) {
                // 标准桶数移除，成为尾桶
                barrelCounts.remove(barrelCounts.size() - 1);
//                // 标准桶数减一
//                standardBarrelCount--;
                // 尾桶的重量 = 0, 就把标准桶最后一桶就为尾桶
                lastBarrelWeight = formulaSchemeDataParam.getStandardBarrelWeightUpper();
            } else {
                // 首桶移除，成为尾桶
                barrelCounts.remove(0);
                // 尾桶的重量 = 0, 就把标准桶最后一桶就为尾桶
                lastBarrelWeight = formulaSchemeDataParam.getFirstBarrelWeightUpper();
            }
        }
        BigDecimal lastBarrelWeight1, lastBarrelWeight2;
        // 剩余的重量（余数）
        if (lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) < 0 || lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) > 0) {
            // 尾巴桶低于下限，则向标准桶借一桶
            if (lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) < 0) {
                // 没得借了，就抛出错误
                Assert.isTrue(!barrelCounts.isEmpty(), "尾桶重量小于下限，且无填补桶数，无法分解");
                // （标准桶的上限+ 剩余的重量（余数））
                lastBarrelWeight = barrelCounts.get(barrelCounts.size() - 1).add(lastBarrelWeight);
                barrelCounts.remove(barrelCounts.size() - 1);
                Assert.isTrue(lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) > 0, "【尾桶+上一桶】重量依旧小于尾桶下限，无法分解");
            }
            if (lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) > 0) {
                // 将尾桶重量一分为二
                BigDecimal divide = lastBarrelWeight.divide(BigDecimal.valueOf(20), 0, RoundingMode.DOWN);
                lastBarrelWeight1 = (divide.multiply(BigDecimal.valueOf(10)).add(lastBarrelWeight.remainder(BigDecimal.valueOf(20))));
                lastBarrelWeight2 = divide.multiply(BigDecimal.valueOf(10));
                Assert.isTrue(lastBarrelWeight1.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) >= 0, "分解完尾桶后：【尾1桶】重量小于下限，无法分解");
                Assert.isTrue(lastBarrelWeight2.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) >= 0, "分解完尾桶后：【尾2桶】重量小于下限，无法分解");
                Assert.isTrue(lastBarrelWeight1.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) <= 0, "分解完尾桶后：【尾1桶】重量大于上限，无法分解");
                Assert.isTrue(lastBarrelWeight2.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) <= 0, "分解完尾桶后：【尾2桶】重量大于上限，无法分解");
                barrelCounts.add(lastBarrelWeight1);
                barrelCounts.add(lastBarrelWeight2);
            }else{
                barrelCounts.add(lastBarrelWeight);
            }
        } else {
            barrelCounts.add(lastBarrelWeight);
        }

        barrelCounts.forEach(barrelCount -> {
            barrelCountDtos.add(new BarrelCountDto(index[0], barrelCount));
            index[0]++;
        });
    }

    /**
     * 第一版拆桶方案
     *
     * @param formulaSchemeDataParam 参数
     * @param barrelCountDtos        桶数及重量
     */
    private static void execBarrelCountsV1(FormulaSchemeDataParam formulaSchemeDataParam, LinkedList<BarrelCountDto> barrelCountDtos) {
        BigDecimal weight = formulaSchemeDataParam.getThisWeight();
        // 先把首桶减出去
        final Integer[] index = {1};
        if (formulaSchemeDataParam.getType() == 1) {
            weight = weight.subtract(formulaSchemeDataParam.getFirstBarrelWeightUpper());
            barrelCountDtos.add(new BarrelCountDto(index[0], formulaSchemeDataParam.getFirstBarrelWeightUpper()));
            index[0]++;
        } else {
            index[0] = 3;
        }
        // 标准桶重量明细
        List<BigDecimal> barrelCounts = new LinkedList<>();
        // 标准桶数
        int standardBarrelCount = weight.divide(formulaSchemeDataParam.getStandardBarrelWeightUpper(), 0, RoundingMode.DOWN).intValue();
        for (int i = 0; i < standardBarrelCount; i++) {
            barrelCounts.add(formulaSchemeDataParam.getStandardBarrelWeightUpper());
        }
        // 尾桶的重量
        BigDecimal lastBarrelWeight = weight.remainder(formulaSchemeDataParam.getStandardBarrelWeightUpper());
        // 如果尾桶的重量 = 0, 就把标准桶最后一桶就为尾桶
        if (lastBarrelWeight.compareTo(BigDecimal.ZERO) == 0) {
            Assert.isTrue(standardBarrelCount > 0, "无尾桶且无标准桶数，无法分解");
            // 标准桶数移除，成为尾桶
            barrelCounts.remove(barrelCounts.size() - 1);
            // 标准桶数减一
            standardBarrelCount--;
            // 尾桶的重量 = 0, 就把标准桶最后一桶就为尾桶
            lastBarrelWeight = formulaSchemeDataParam.getStandardBarrelWeightUpper();
        }
        BigDecimal lastBarrelWeight1, lastBarrelWeight2;
        // 剩余的重量（余数）
        if (lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) < 0 || lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) > 0) {
            // 尾巴桶低于下限，则向标准桶借一桶
            if (lastBarrelWeight.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) < 0) {
                // 没得借了，就抛出错误
                Assert.isTrue(standardBarrelCount > 0, "尾桶重量小于下限，且无标准桶数，无法分解");
                barrelCounts.remove(barrelCounts.size() - 1);
                // （标准桶的上限+ 剩余的重量（余数））
                lastBarrelWeight = formulaSchemeDataParam.getStandardBarrelWeightUpper().add(lastBarrelWeight);
            }
            // 将尾桶重量一分为二
            BigDecimal divide = lastBarrelWeight.divide(BigDecimal.valueOf(20), 0, RoundingMode.DOWN);
            lastBarrelWeight1 = (divide.multiply(BigDecimal.valueOf(10)).add(lastBarrelWeight.remainder(BigDecimal.valueOf(20))));
            lastBarrelWeight2 = divide.multiply(BigDecimal.valueOf(10));
            Assert.isTrue(lastBarrelWeight1.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) >= 0, "分解完尾桶后：【尾1桶】重量小于下限，无法分解");
            Assert.isTrue(lastBarrelWeight2.compareTo(formulaSchemeDataParam.getLastBarrelWeightLower()) >= 0, "分解完尾桶后：【尾2桶】重量小于下限，无法分解");
            Assert.isTrue(lastBarrelWeight1.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) <= 0, "分解完尾桶后：【尾1桶】重量大于上限，无法分解");
            Assert.isTrue(lastBarrelWeight2.compareTo(formulaSchemeDataParam.getLastBarrelWeightUpper()) <= 0, "分解完尾桶后：【尾2桶】重量大于上限，无法分解");
            barrelCounts.add(lastBarrelWeight1);
            barrelCounts.add(lastBarrelWeight2);
        } else {
            barrelCounts.add(lastBarrelWeight);
        }

        barrelCounts.forEach(barrelCount -> {
            barrelCountDtos.add(new BarrelCountDto(index[0], barrelCount));
            index[0]++;
        });
    }

    /**
     * 配料分解方案
     *
     * @return FormulaSchemeData 方案数据
     */
    public boolean ingredienDecompositionScheme(LinkedList<BarrelCountDto> barrelCount, FormulaSchemeDataParam formulaSchemeDataParam, FormulaSchemeData formulaSchemeData) {
        Map<String, FormulaSchemeDataParam.MaterialTypeWeight> materialClassNameMap = formulaSchemeDataParam.getRationMaterialTypeWeights().stream()
                .collect(Collectors.toMap(FormulaSchemeDataParam.MaterialTypeWeight::getMaterialClassName, v -> v));
        materialClassNameMap.putAll(formulaSchemeDataParam.getMaterialTypeWeights().stream()
                .collect(Collectors.toMap(FormulaSchemeDataParam.MaterialTypeWeight::getMaterialClassName, v -> v)));
        List<FormulaSchemeDataParam.MaterialTypeWeight> materialTypeWeightAlls = new ArrayList<>(materialClassNameMap.values());

        Map<String, Integer> materialClassNameIndex = new HashMap<>();
        int x = 3 + materialClassNameMap.size();
        int y = 3 + barrelCount.size() + 1;
        // 合计行序号
        int totalY = y - 1;
        // 合计列序号
        int totalX = x - 2;
        // 单桶容量列（重量)）
        int singleBarrelWeightX = x - 1;
        // 物料等级行
        int materialGradeY = 0;
        // 取整数量--- 需要投入的物料总重行
        int materialTypeWeightY = 1;
        // 桶数--- 物料名称行
        int materialTypeNameY = 2;
        // 物料数据填充起始列
        int startMaterialTypeWeightX = 1;
        // 物料数据填充起始行
        int startMaterialTypeWeightY = 3;

        //  初始化 物料分配明细数组
        Object[][] materialFeedingDetailArrs = getObjects(formulaSchemeDataParam.getThisWeight(), y, x, materialGradeY, materialTypeWeightY, materialTypeNameY, totalY, totalX, singleBarrelWeightX, materialTypeWeightAlls, startMaterialTypeWeightX, materialClassNameIndex);

        // 首桶行
        Integer firstBucketY = null;
        if (barrelCount.get(0).getIndex() == 1) {
            firstBucketY = startMaterialTypeWeightY;
        }
        // 尾桶行
        int tailBucketY = barrelCount.size() + startMaterialTypeWeightY - 1;
        // 标准桶行
        List<Integer> standardBucketY = new ArrayList<>();

        // 所有桶的序号和重量
        Map<Integer, BarrelCountDto> allBucketIndexMap = new HashMap<>();
        for (int j = 0; j < barrelCount.size(); j++) {
            if (barrelCount.get(j).getIndex() > 1 && j < barrelCount.size() - 1) {
                standardBucketY.add(j + startMaterialTypeWeightY);
            }
            allBucketIndexMap.put(j + startMaterialTypeWeightY, barrelCount.get(j));
            materialFeedingDetailArrs[j + startMaterialTypeWeightY][0] = barrelCount.get(j).getIndex();
            materialFeedingDetailArrs[j + startMaterialTypeWeightY][singleBarrelWeightX] = barrelCount.get(j).getWeight();
            for (int i = 0; i < materialTypeWeightAlls.size() + 1; i++) {
                materialFeedingDetailArrs[j + startMaterialTypeWeightY][i + startMaterialTypeWeightX] = BigDecimal.ZERO;
            }
        }

        // 放入定量的物料
        formulaSchemeDataParam.getRationMaterialTypeWeights().forEach(rationMaterialTypeWeight -> {
            allBucketIndexMap.forEach((currentY, value) -> {
                // 增加该物料已装入重量
                Assert.isTrue(addMaterialTypeWeights(materialFeedingDetailArrs, currentY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex, rationMaterialTypeWeight, rationMaterialTypeWeight.getRationWeight()), "定量的物料，无法放入");
            });
        });
        log.info("放入定量的物料1：{}", JSON.toJSONString(materialFeedingDetailArrs));
        formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getRationWeight() != null).forEach(materialTypeWeight -> {
            allBucketIndexMap.forEach((currentY, value) -> {
                // 增加该物料已装入重量
                Assert.isTrue(addMaterialTypeWeights(materialFeedingDetailArrs, currentY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex, materialTypeWeight, materialTypeWeight.getRationWeight()), "定量的物料，无法放入");
            });
        });
        log.info("放入定量的物料2：{}", JSON.toJSONString(materialFeedingDetailArrs));
        // 放入首桶物料
        Integer finalFirstBucketY = firstBucketY;
        formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("首桶") && materialTypeWeight.getBarrelShapedName().size() == 1).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).forEach(materialTypeWeight -> {
                    Assert.isTrue(finalFirstBucketY != null, "没有首桶，只能放入首桶的物料放不进去");
                    Assert.isTrue(addMaterialTypeWeights(materialFeedingDetailArrs, finalFirstBucketY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex, materialTypeWeight, materialTypeWeight.getWeight()), "只能放入首桶的物料，首桶装不下");
                    log.info("放入首桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));
                }
        );
        // 放入尾桶物料
        formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("尾桶") && materialTypeWeight.getBarrelShapedName().size() == 1).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).forEach(materialTypeWeight -> {
                    Assert.isTrue(addMaterialTypeWeights(materialFeedingDetailArrs, tailBucketY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex, materialTypeWeight, materialTypeWeight.getWeight()), "只能放入尾桶的物料，尾桶装不下");
                    log.info("放入尾桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));
                }
        );
        // 放入标准桶的物料
        recursiveInsertionOfMaterials(formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("标准桶") && materialTypeWeight.getBarrelShapedName().size() == 1).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).collect(Collectors.toList()),
                materialFeedingDetailArrs, standardBucketY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);
        log.info("放入标准桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));

        // 放入包含尾桶，并且包含首桶的物料
        List<Integer> firstTailBucket = new ArrayList<>();
        if (firstBucketY != null) {
            firstTailBucket.add(firstBucketY);
        }
        firstTailBucket.add(tailBucketY);
        recursiveInsertionOfMaterials(formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("首桶") && materialTypeWeight.getBarrelShapedName().contains("尾桶")).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).collect(Collectors.toList()), materialFeedingDetailArrs, firstTailBucket, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);

        if (firstBucketY != null) {
            // 放入包含首桶物料
            recursiveInsertionOfMaterials(formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("首桶")).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).collect(Collectors.toList()), materialFeedingDetailArrs, Collections.singletonList(startMaterialTypeWeightY), totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);
            log.info("放入包含首桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));
        }

        // 放入包含尾桶物料
        recursiveInsertionOfMaterials(formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().contains("尾桶")).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).collect(Collectors.toList()), materialFeedingDetailArrs, Collections.singletonList(tailBucketY), totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);
        log.info("放入包含尾桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));

        // 放入包含标准桶物料
        recursiveInsertionOfMaterials(formulaSchemeDataParam.getMaterialTypeWeights().stream().filter(materialTypeWeight -> materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) > 0 && materialTypeWeight.getBarrelShapedName().contains("标准桶")).sorted(Comparator.comparing(materialTypeWeight -> materialTypeWeight.getBarrelShapedName().size())).collect(Collectors.toList()), materialFeedingDetailArrs, standardBucketY, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);
        log.info("放入包含标准桶物料：{}", JSON.toJSONString(materialFeedingDetailArrs));
        //验证是否装完
        for (int i = 1; i < totalX; i++) {
            Assert.isTrue(new BigDecimal(materialFeedingDetailArrs[materialTypeWeightY][i].toString()).compareTo(new BigDecimal(materialFeedingDetailArrs[totalY][i].toString())) == 0 ,
                    "【" + materialFeedingDetailArrs[2][i] + "】该物料，只能装在"
                            + StringUtils.join(materialClassNameMap.get((String) materialFeedingDetailArrs[2][i]).getBarrelShapedName(), ",") + "，装不下");
        }
        /*
             materialFeedingDetailArrs：输出实例
               [
                 ["物料等级","A","A","A","B",null,""],
                 ["取整数量",240,180,140,240,null,800],
                 ["桶数","循环料-123456","多晶硅-硅料","打底料-13245678","多晶硅-13245678","投入合计","单桶容量"],
                 [1,0,0,20,0,20,110],
                 [2,0,0,20,0,20,130],
                 [3,0,0,20,0,20,130],
                 [4,0,0,20,0,20,130],
                 [5,0,0,20,0,20,130],
                 [6,0,0,20,0,20,90],
                 [7,0,0,20,0,20,80],
                 ["投入合计",0,0,140,0,0,0]
              ]
         */
        log.info("放入其他物料：{}", JSON.toJSONString(materialFeedingDetailArrs));
        formulaSchemeData.setMaterialFeedingDetailArrs(materialFeedingDetailArrs);

        Map<String, List<FormulaSchemeDetail>> formulaSchemeDetailMap = new HashMap<>();
        List<FormulaSchemeData.FormulaSchemeDataDetail> formulaSchemeDataDetails = new LinkedList<>();
        allBucketIndexMap.forEach((key, value) -> {
            FormulaSchemeData.FormulaSchemeDataDetail formulaSchemeDataDetail = new FormulaSchemeData.FormulaSchemeDataDetail();
            formulaSchemeDataDetail.setIndex(value.getIndex());
            formulaSchemeDataDetail.setWeight(value.getWeight());
            formulaSchemeDataDetail.setLoadingDetails(new LinkedList<>());
            materialClassNameIndex.forEach((materialClassName, index) -> {
                BigDecimal weight = (BigDecimal) materialFeedingDetailArrs[key][index];
                if (weight.compareTo(BigDecimal.ZERO) > 0) {
                    FormulaSchemeData.LoadingDetail formulaSchemeDataLoadingDetail = new FormulaSchemeData.LoadingDetail();
                    formulaSchemeDataLoadingDetail.setMaterialType(materialClassName.split(">>")[0]);
                    formulaSchemeDataLoadingDetail.setMaterialClassName(materialClassName.split(">>")[1]);
                    formulaSchemeDataLoadingDetail.setWeight(weight);
                    if (formulaSchemeDetailMap.containsKey(materialClassName)) {
                        formulaSchemeDataLoadingDetail.setFormulaSchemeDetails(formulaSchemeDetailMap.get(materialClassName));
                    } else {
                        List<FormulaSchemeDetail> formulaSchemeDetails = formulaSchemeDetailService.findByFormulaSchemeIdAndMaterialTypeAndMaterialClassName(formulaSchemeDataParam.getFormulaSchemeId(), formulaSchemeDataLoadingDetail.getMaterialType(), formulaSchemeDataLoadingDetail.getMaterialClassName());
                        formulaSchemeDataLoadingDetail.setFormulaSchemeDetails(formulaSchemeDetails);
                        formulaSchemeDetailMap.put(materialClassName, formulaSchemeDetails);
                    }
                    formulaSchemeDataDetail.getLoadingDetails().add(formulaSchemeDataLoadingDetail);
                }
            });
            formulaSchemeDataDetails.add(formulaSchemeDataDetail);
        });
        formulaSchemeData.setDetail(formulaSchemeDataDetails);
        return ((BigDecimal) materialFeedingDetailArrs[totalY][totalX]).compareTo((BigDecimal) materialFeedingDetailArrs[materialTypeWeightY][singleBarrelWeightX]) == 0;
    }

    /**
     * 递归放入物料
     *
     * @param materialTypeWeights
     * @param materialFeedingDetailArrs
     * @param barrelIndexs
     * @param totalY
     * @param totalX
     * @param singleBarrelWeightX
     * @param materialTypeWeightY
     * @param materialClassNameIndex
     */
    private void recursiveInsertionOfMaterials(List<FormulaSchemeDataParam.MaterialTypeWeight> materialTypeWeights, Object[][] materialFeedingDetailArrs, List<Integer> barrelIndexs, int totalY, int totalX, int singleBarrelWeightX, int materialTypeWeightY, Map<String, Integer> materialClassNameIndex) {
        AtomicBoolean isTrue = new AtomicBoolean(false);
        materialTypeWeights.forEach(materialTypeWeight -> {
            barrelIndexs.forEach(barrelIndex -> {
                if (addMaterialTypeWeights(materialFeedingDetailArrs, barrelIndex, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex, materialTypeWeight, BigDecimal.TEN)) {
                    if (!isTrue.get()) {
                        isTrue.set(true);
                    }
                }
            });
        });
        if (isTrue.get()) {
            recursiveInsertionOfMaterials(materialTypeWeights, materialFeedingDetailArrs, barrelIndexs, totalY, totalX, singleBarrelWeightX, materialTypeWeightY, materialClassNameIndex);
        }
    }

    /**
     * 初始化 物料分配明细数组
     *
     * @param thisWeight               投入的重量
     * @param y                        总行数
     * @param x                        总列数
     * @param materialGradeY           物料等级行
     * @param materialTypeWeightY      取整数量--- 需要投入的物料总重行
     * @param materialTypeNameY        桶数--- 物料名称行
     * @param totalX                   合计列序号
     * @param singleBarrelWeightX      单桶容量列（重量)）
     * @param materialTypeWeightAlls   物料分配明细
     * @param startMaterialTypeWeightX 物料数据填充起始列
     * @param materialClassNameIndex   物料名称列序号MAP
     * @return 物料分配明细数组
     */
    @NotNull
    private static Object[][] getObjects(BigDecimal thisWeight, int y, int x, int materialGradeY, int materialTypeWeightY, int materialTypeNameY, int totalY, int totalX, int singleBarrelWeightX,
                                         List<FormulaSchemeDataParam.MaterialTypeWeight> materialTypeWeightAlls, int startMaterialTypeWeightX, Map<String, Integer> materialClassNameIndex) {
        Object[][] materialFeedingDetailArrs = new Object[y][x];
        materialFeedingDetailArrs[materialGradeY][0] = "物料等级";
        materialFeedingDetailArrs[materialTypeWeightY][0] = "取整数量";
        materialFeedingDetailArrs[materialTypeNameY][0] = "桶数";

        materialFeedingDetailArrs[materialGradeY][totalX] = "";
        materialFeedingDetailArrs[materialTypeWeightY][totalX] = "";
        materialFeedingDetailArrs[materialTypeNameY][totalX] = "投入合计";

        materialFeedingDetailArrs[materialGradeY][singleBarrelWeightX] = "";
        materialFeedingDetailArrs[materialTypeWeightY][singleBarrelWeightX] = thisWeight;
        materialFeedingDetailArrs[materialTypeNameY][singleBarrelWeightX] = "单桶容量";

        for (int j = 0; j < materialTypeWeightAlls.size(); j++) {
            materialFeedingDetailArrs[materialGradeY][startMaterialTypeWeightX + j] = materialTypeWeightAlls.get(j).getLevel();
            materialFeedingDetailArrs[materialTypeWeightY][startMaterialTypeWeightX + j] = materialTypeWeightAlls.get(j).getWeight();
            materialFeedingDetailArrs[materialTypeNameY][startMaterialTypeWeightX + j] = materialTypeWeightAlls.get(j).getMaterialClassName();
            materialClassNameIndex.put(materialTypeWeightAlls.get(j).getMaterialClassName(), startMaterialTypeWeightX + j);
        }
        materialFeedingDetailArrs[totalY][0] = "投入合计";
        for (int i = 1; i < x; i++) {
            materialFeedingDetailArrs[totalY][i] = BigDecimal.ZERO;
        }
        materialFeedingDetailArrs[totalY][x - 1] = thisWeight;
        return materialFeedingDetailArrs;
    }

    /**
     * @param materialFeedingDetailArrs 物料分配明细数组
     * @param currentY                  桶序号
     * @param totalY                    合计行序号
     * @param totalX                    合计列序号
     * @param singleBarrelWeightX       单桶容量列（重量)）
     * @param materialTypeWeightY       取整数量--- 需要投入的物料总重行
     * @param materialClassNameIndex    物料名称列序号MAP
     * @param materialTypeWeight        物料分配明细
     * @return
     */
    private boolean addMaterialTypeWeights(Object[][] materialFeedingDetailArrs, Integer currentY, Integer totalY, Integer totalX, Integer singleBarrelWeightX, Integer materialTypeWeightY, Map<String, Integer> materialClassNameIndex, FormulaSchemeDataParam.MaterialTypeWeight materialTypeWeight, BigDecimal weight) {
        int indexX = materialClassNameIndex.get(materialTypeWeight.getMaterialClassName());
        // 如果不是定量，则按照 每次 10去分配
        BigDecimal currentY_TotalX = weight.add(materialFeedingDetailArrs[currentY][totalX] == null ? BigDecimal.ZERO : (BigDecimal) materialFeedingDetailArrs[currentY][totalX]);
        BigDecimal totalY_IndexX = weight.add(materialFeedingDetailArrs[totalY][indexX] == null ? BigDecimal.ZERO : (BigDecimal) materialFeedingDetailArrs[totalY][indexX]);
        if ((currentY_TotalX.compareTo((BigDecimal) materialFeedingDetailArrs[currentY][singleBarrelWeightX]) <= 0) && (totalY_IndexX.compareTo((BigDecimal) materialFeedingDetailArrs[materialTypeWeightY][indexX]) <= 0)) {
            materialFeedingDetailArrs[currentY][indexX] = weight.add((BigDecimal) materialFeedingDetailArrs[currentY][indexX]);
            materialFeedingDetailArrs[totalY][indexX] = totalY_IndexX;
            materialFeedingDetailArrs[currentY][totalX] = currentY_TotalX;
            materialFeedingDetailArrs[totalY][totalX] = weight.add(materialFeedingDetailArrs[totalY][totalX] == null ? BigDecimal.ZERO : (BigDecimal) materialFeedingDetailArrs[totalY][totalX]);
            materialTypeWeight.setWeight(materialTypeWeight.getWeight().subtract(weight));
            return true;
        } else {
            return false;
        }
    }
}
