package com.sw.mes.production.dto;

import com.sw.mes.core.exception.BusinessException;
import com.sw.mes.production.entity.FormulaScheme;
import com.sw.mes.production.entity.FormulaWeightScheme;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.Assert;

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

/**
 * @author zhaowenjie
 */
public class FormulaSchemeDataParam {
    private static final Set<String> ALL_BARREL_SHAPED_NAME = new HashSet<>(Arrays.asList("首桶", "尾桶", "标准桶"));
    /**
     * 初装桶型
     */
    private static final Set<String> INITIAL_BARREL_SHAPED_NAME = new HashSet<>(Arrays.asList("首桶"));
    @Getter
    private Long formulaSchemeId;

    public void setFormulaScheme(FormulaScheme formulaScheme) {
        // 默认桶型
        Set<String> defaultBucketShape = this.repeatTimes == 0 ? INITIAL_BARREL_SHAPED_NAME : ALL_BARREL_SHAPED_NAME;
        this.formulaSchemeId = formulaScheme.getId();
        Map<String, Set<String>> materialBarrelShaped = new HashMap<>();
        formulaScheme.getBarrelShaped().forEach(barrelShaped -> {
            barrelShaped.getMaterialClassNames()
                    .stream()
                    .peek(materialClassName -> materialBarrelShaped.computeIfAbsent(materialClassName, name -> new HashSet<>()))
                    .forEach(materialClassName -> materialBarrelShaped.get(materialClassName).add(barrelShaped.getName()));
        });

        // 定量物料重量
        this.rationMaterialTypeWeights = new ArrayList<>();
        BigDecimal[] rationMaterialTypeWeight = {BigDecimal.ZERO};
        formulaScheme.getTypeFormula().stream().filter(typeFormula -> "定量".equals(typeFormula.getInputType())).forEach(typeFormula -> {
            typeFormula.getMaterialTypeWeights().forEach(materialTypeWeight -> {
                MaterialTypeWeight materialTypeWeightItem = new MaterialTypeWeight();
                materialTypeWeightItem.setLevel(materialTypeWeight.getLevel());
                String materialTypeClassName = typeFormula.getMaterialType() + ">>" + materialTypeWeight.getMaterialClassName();
                materialTypeWeightItem.setMaterialClassName(materialTypeClassName);
                materialTypeWeightItem.setRationWeight((materialTypeWeight.getProportion().multiply(typeFormula.getProportion()).divide(BigDecimal.valueOf(100), 0, RoundingMode.DOWN)));
                materialTypeWeightItem.setWeight(materialTypeWeightItem.getRationWeight().multiply(BigDecimal.valueOf(this.barrelCountDtos.size())));
                materialTypeWeightItem.setBarrelShapedName(defaultBucketShape);
                rationMaterialTypeWeight[0] = rationMaterialTypeWeight[0].add(materialTypeWeightItem.getWeight());
                this.rationMaterialTypeWeights.add(materialTypeWeightItem);
            });
        });
        BigDecimal totalWeight = this.weight.subtract(rationMaterialTypeWeight[0]);
        // 计算后总重量
        BigDecimal[] calculateTotalWeight = {BigDecimal.ZERO};
        List<MaterialTypeWeight> finalMaterialTypeWeightsA = new ArrayList<>();
        List<MaterialTypeWeight> finalMaterialTypeWeightsB = new ArrayList<>();
        formulaScheme.getTypeFormula().stream().filter(typeFormula -> "占比".equals(typeFormula.getInputType())).forEach(typeFormula -> {
            typeFormula.getMaterialTypeWeights().forEach(materialTypeWeight -> {
                MaterialTypeWeight materialTypeWeightItem = new MaterialTypeWeight();
                materialTypeWeightItem.setLevel(materialTypeWeight.getLevel());
                String materialTypeClassName = typeFormula.getMaterialType() + ">>" + materialTypeWeight.getMaterialClassName();
                materialTypeWeightItem.setMaterialClassName(materialTypeClassName);
                materialTypeWeightItem.setWeight(materialTypeWeight.getProportion().multiply(typeFormula.getProportion()).divide(BigDecimal.valueOf(10000), 4, RoundingMode.DOWN).multiply(totalWeight));
                materialTypeWeightItem.setBarrelShapedName(materialBarrelShaped.getOrDefault(materialTypeClassName, defaultBucketShape));
                if (materialTypeWeight.getRationWeight() != null && materialTypeWeight.getRationWeight().compareTo(BigDecimal.ZERO) > 0) {
                    materialTypeWeightItem.setRationWeight(materialTypeWeight.getRationWeight());
                }
                BigDecimal oldWeight = materialTypeWeightItem.getWeight();
                switch (materialTypeWeight.getLevel()) {
                    case "A":
                        materialTypeWeightItem.setWeight(materialTypeWeightItem.getWeight().divide(BigDecimal.TEN, 0, RoundingMode.CEILING).multiply(BigDecimal.TEN));
                        materialTypeWeightItem.setDifference(materialTypeWeightItem.getWeight().subtract(oldWeight));
                        finalMaterialTypeWeightsA.add(materialTypeWeightItem);
                        break;
                    case "B":
                        materialTypeWeightItem.setWeight(materialTypeWeightItem.getWeight().divide(BigDecimal.TEN, 0, RoundingMode.HALF_UP).multiply(BigDecimal.TEN));
                        materialTypeWeightItem.setDifference(materialTypeWeightItem.getWeight().subtract(oldWeight));
                        finalMaterialTypeWeightsB.add(materialTypeWeightItem);
                        break;
                    default:
                        throw new BusinessException("【" + materialTypeWeight.getLevel() + "】物料等级不支持");
                }
                calculateTotalWeight[0] = calculateTotalWeight[0].add(materialTypeWeightItem.getWeight());
            });
        });

        List<MaterialTypeWeight> materialTypeWeightsA = finalMaterialTypeWeightsA;
        List<MaterialTypeWeight> materialTypeWeightsB = finalMaterialTypeWeightsB;
        //计算重量总差值
        BigDecimal totalDifference = calculateTotalWeight[0].subtract(totalWeight);
        if (!materialTypeWeightsB.isEmpty() && totalDifference.compareTo(BigDecimal.ZERO) != 0) {
            if (totalDifference.compareTo(BigDecimal.ZERO) != 0) {
                materialTypeWeightsB = materialTypeWeightsB.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference).reversed()).collect(Collectors.toList());
                while (totalDifference.compareTo(BigDecimal.ZERO) > 0) {
                    materialTypeWeightsB.get(0).setWeight(materialTypeWeightsB.get(0).getWeight().subtract(BigDecimal.TEN));
                    materialTypeWeightsB.get(0).setDifference(materialTypeWeightsB.get(0).getDifference().subtract(BigDecimal.TEN));
                    totalDifference = totalDifference.subtract(BigDecimal.TEN);
                    materialTypeWeightsB = materialTypeWeightsB.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference).reversed()).collect(Collectors.toList());
                }
            } else {
                materialTypeWeightsB = materialTypeWeightsB.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference)).collect(Collectors.toList());
                while (totalDifference.compareTo(BigDecimal.ZERO) < 0) {
                    materialTypeWeightsB.get(0).setWeight(materialTypeWeightsB.get(0).getWeight().add(BigDecimal.TEN));
                    materialTypeWeightsB.get(0).setDifference(materialTypeWeightsB.get(0).getDifference().add(BigDecimal.TEN));
                    totalDifference = totalDifference.add(BigDecimal.TEN);
                    materialTypeWeightsB = materialTypeWeightsB.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference)).collect(Collectors.toList());
                }
            }
        }

        if (!materialTypeWeightsA.isEmpty() && totalDifference.compareTo(BigDecimal.ZERO) != 0) {
            if (totalDifference.compareTo(BigDecimal.ZERO) != 0) {
                materialTypeWeightsA = materialTypeWeightsA.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference).reversed()).collect(Collectors.toList());
                while (totalDifference.compareTo(BigDecimal.ZERO) > 0) {
                    materialTypeWeightsA.get(0).setWeight(materialTypeWeightsA.get(0).getWeight().subtract(BigDecimal.TEN));
                    materialTypeWeightsA.get(0).setDifference(materialTypeWeightsA.get(0).getDifference().subtract(BigDecimal.TEN));
                    totalDifference = totalDifference.subtract(BigDecimal.TEN);
                    materialTypeWeightsA = materialTypeWeightsA.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference).reversed()).collect(Collectors.toList());
                }
            } else {
                materialTypeWeightsA = materialTypeWeightsA.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference)).collect(Collectors.toList());
                while (totalDifference.compareTo(BigDecimal.ZERO) < 0) {
                    materialTypeWeightsA.get(0).setWeight(materialTypeWeightsA.get(0).getWeight().add(BigDecimal.TEN));
                    materialTypeWeightsA.get(0).setDifference(materialTypeWeightsA.get(0).getDifference().add(BigDecimal.TEN));
                    totalDifference = totalDifference.add(BigDecimal.TEN);
                    materialTypeWeightsA = materialTypeWeightsA.stream().sorted(Comparator.comparing(MaterialTypeWeight::getDifference)).collect(Collectors.toList());
                }
            }
        }
        this.materialTypeWeights.addAll(materialTypeWeightsA);
        this.materialTypeWeights.addAll(materialTypeWeightsB);
        if (type != null && type == 2) {
            Assert.isTrue(materialWeights != null && !materialWeights.isEmpty(), "分解 3～x桶时，已投入物料不能为空");
            this.materialWeights.forEach(materialWeight -> {
                AtomicBoolean isTrue = new AtomicBoolean(true);
                this.materialTypeWeights.stream().filter(materialTypeWeight -> materialTypeWeight.getMaterialClassName().equals(materialWeight.getMaterialType() + ">>" + materialWeight.getMaterialClassName())).findFirst().ifPresent(materialTypeWeight -> {
                    materialTypeWeight.setWeight(materialTypeWeight.getWeight().subtract(materialWeight.getWeight()));
                    Assert.isTrue(materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) >= 0, "【" + materialTypeWeight.getMaterialClassName() + "】该物料减投入重量后为负数无法分解");
                    isTrue.set(false);
                });
                if (isTrue.get()) {
                    this.rationMaterialTypeWeights.stream().filter(materialTypeWeight -> materialTypeWeight.getMaterialClassName().equals(materialWeight.getMaterialType() + ">>" + materialWeight.getMaterialClassName())).findFirst().ifPresent(materialTypeWeight -> {
                        materialTypeWeight.setWeight(materialTypeWeight.getWeight().subtract(materialWeight.getWeight()));
                        Assert.isTrue(materialTypeWeight.getWeight().compareTo(BigDecimal.ZERO) >= 0, "【" + materialTypeWeight.getMaterialClassName() + "】该物料减投入重量后为负数无法分解");
                    });
                }
            });
        }
    }

    /**
     * 定量物料重量
     */
    @Getter
    private List<MaterialTypeWeight> rationMaterialTypeWeights;

    /**
     * 占比计算的
     */
    @Getter
    private List<MaterialTypeWeight> materialTypeWeights = new ArrayList<>();

    public void setFormulaWeightScheme(FormulaWeightScheme formulaWeightScheme) {
        this.scopeLower = formulaWeightScheme.getScopeLower();
        this.scopeUpper = formulaWeightScheme.getScopeUpper();
        formulaWeightScheme.getWeightScheme().forEach(weightScheme -> {
            switch (weightScheme.getBarrelShaped()) {
                case "首桶":
                    this.firstBarrelWeightUpper = weightScheme.getUpper();
                    this.firstBarrelWeightLower = weightScheme.getLower();
                    break;
                case "标准桶":
                    this.standardBarrelWeightUpper = weightScheme.getUpper();
                    this.standardBarrelWeightLower = weightScheme.getLower();
                    break;
                case "尾桶":
                    this.lastBarrelWeightUpper = weightScheme.getUpper();
                    this.lastBarrelWeightLower = weightScheme.getLower();
                    break;
                default:
                    throw new BusinessException("桶型错误");
            }
        });
    }

    /**
     * 投料总重量
     */
    @Getter
    @Setter
    private BigDecimal weight;

    /**
     * 本次投料的重量
     */
    @Setter
    @Getter
    private BigDecimal thisWeight;

    /**
     * 1: 全部桶； 2: 3~X桶
     */
    @Setter
    @Getter
    private Integer type;

    /**
     * 首桶的上限
     */
    @Getter
    private BigDecimal firstBarrelWeightUpper;
    /**
     * 首桶的下限
     */
    @Getter
    private BigDecimal firstBarrelWeightLower;
    /**
     * 标准桶上限
     */
    @Getter
    private BigDecimal standardBarrelWeightUpper;
    /**
     * 标准桶下限
     */
    @Getter
    private BigDecimal standardBarrelWeightLower;
    /**
     * 尾桶的上限
     */
    @Getter
    private BigDecimal lastBarrelWeightUpper;
    /**
     * 尾桶的下限
     */
    @Getter
    private BigDecimal lastBarrelWeightLower;

    /**
     * 报料范围上限
     */
    @Getter
    private BigDecimal scopeUpper;

    /**
     * 报料范围下限
     */
    @Getter
    private BigDecimal scopeLower;

    @ApiModelProperty(value = "复投次数(0：初始装料，1：初始复投，>1 取段复投)")
    @Getter
    @Setter
    private Integer repeatTimes;

    @Getter
    @Setter
    private LinkedList<BarrelCountDto> barrelCountDtos;

    @Data
    public static class MaterialTypeWeight {
        @ApiModelProperty(value = "等级")
        private String level;

        @ApiModelProperty(value = "物料分类名称")
        private String materialClassName;

        @ApiModelProperty(value = "物料重量")
        private BigDecimal weight;

        @ApiModelProperty(value = "物料重量（定量)")
        private BigDecimal rationWeight;

        @ApiModelProperty(value = "可以放入的桶型")
        private Set<String> barrelShapedName;

        @ApiModelProperty(value = "差值")
        private BigDecimal  difference;
    }

    @Setter
    private List<DecompositionSchemeParam.MaterialWeight> materialWeights;
}

