package com.ximai.mes.pro.schedule.strategy;

import com.ximai.common.utils.data.StringUtils;
import com.ximai.mes.pro.schedule.*;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 资源设备选择策略
 */
public class EquipmentSelectionStrategyImpl implements EquipmentSelectionStrategy{

    boolean isMultiple = true;
    private final List<EvaluateEquipment> evaluateEquipmentList = new ArrayList<EvaluateEquipment>(){{
            add(new EvaluateEquipmentCapacity());
            add(new EvaluateEquipmentDeliveryTime());
            add(new EvaluateEquipmentShift());
    }};

    @Override
    public EquipmentSelectionResult evaluateOptionalEquipment(Task task) {
        EquipmentSelectionResult rst = new EquipmentSelectionResult();
        List<IOperationTimeCalculator.OperationTimePlan> operationTimePlans = new ArrayList<IOperationTimeCalculator.OperationTimePlan>();
        //必选设备逻辑添加
        List<Equipment> matchEquipments = this.matchEquipment(task, task.getOptionalEquipments());
        List<TaskSchedulingContext.TaskSchedulingResultContext> taskSchResults = new ArrayList<>();
        if(task.getOptionalEquipments().isEmpty()){
            rst.setCause(EquipmentSelectionResult.EquipmentSelectionCause.UN_EQUIPMENT);
            return rst;
        }else if(matchEquipments.isEmpty()){
            rst.setCause(EquipmentSelectionResult.EquipmentSelectionCause.UN_MATCH);
            return rst;
        }
        matchEquipments.forEach(eqt -> {
            TaskSchedulingContext context = new TaskSchedulingContext()
            {{
                setJob(task.getJob());
                setTask(task);
                setEquipment(eqt);
            }};
            IOperationTimeCalculator.OperationTimePlan opertionTimePlan = task.getOperationTimeCalculator().calculate(context);
            if (opertionTimePlan.getPlan().isEmpty()) {
                return;
            }
            taskSchResults.add(new TaskSchedulingContext.TaskSchedulingResultContext(eqt, opertionTimePlan, context));
        });
        taskSchResults.forEach(s->{
            //依次评估设备
            evaluateEquipmentList.forEach(e->{
                double evaluate = e.evaluate(s.getTaskSchedulingContext(), s.getOpertionTimePlan(), taskSchResults);
                rst.getEvaluateDetail().append(e.getClass().getSimpleName()).append(":").append(evaluate).append("*").append(e.weight()).append(",");
                s.getOpertionTimePlan().setScore((evaluate* e.weight())+s.getOpertionTimePlan().getScore());
            });
            rst.getEvaluateDetail().append(s.getEquipment().getWorkUnitName()).append("，合计：").append(s.getOpertionTimePlan().getScore()).append(System.getProperty("line.separator"));
            operationTimePlans.add(s.getOpertionTimePlan());
        });

        if(operationTimePlans.isEmpty()){
            rst.setCause(EquipmentSelectionResult.EquipmentSelectionCause.UN_CALENDER);
            return rst;
        }
        rst.setSelectList(this.evaluateOptionalEquipment(task, operationTimePlans));
        return rst;
    }

    /*
    根据设备评估得分，返回合适的设备产能
    评分第一如果数量满足任务，直接返回该数据
    评分第一数量不满足，累加下台设备数量，满足的话一并返回
    如果累加所有设备数量都不满足任务数量，返回空
     */
    private List<EvaluateOptionalEquipmentResult> evaluateOptionalEquipment(Task task, List<IOperationTimeCalculator.OperationTimePlan> operationTimePlanList) {
        List<EvaluateOptionalEquipmentResult> rst = new ArrayList<EvaluateOptionalEquipmentResult>();
        List<IOperationTimeCalculator.OperationTimePlan> evaludateOpertionTimePlanList = new ArrayList<IOperationTimeCalculator.OperationTimePlan>();
        //按评分倒排
        operationTimePlanList.sort(Comparator.comparing(IOperationTimeCalculator.OperationTimePlan::getScore, Collections.reverseOrder()));
        //评分第一如果数量满足任务，直接返回该数据
        IOperationTimeCalculator.OperationTimePlan firstOperationTimePlan = operationTimePlanList.get(0);
        BigDecimal totalQuantity = firstOperationTimePlan.getPlan().stream().map(s->s.getQuantity()).reduce(BigDecimal.ZERO, BigDecimal::add);
        if(task.getOrderQuantity().compareTo(totalQuantity)==0){
            evaludateOpertionTimePlanList.add(firstOperationTimePlan);
        }else{
            BigDecimal accumulation = BigDecimal.ZERO;
            //评分第一数量不满足，累加下台设备数量，直到满足
            for(IOperationTimeCalculator.OperationTimePlan operationTimePlan : operationTimePlanList){
                int i = 1;
                for(IOperationTimeCalculator.OperationTimePlanItem planItem : operationTimePlan.getPlan()){
                    accumulation = accumulation.add(planItem.getQuantity());
                    if(accumulation.compareTo(task.getOrderQuantity())>=0){
                        //排产时间段拆分多出的数量
                        BigDecimal overageQuantity = accumulation.subtract(task.getOrderQuantity());
                        if(overageQuantity.compareTo(BigDecimal.ZERO)>0){
                            List<IOperationTimeCalculator.OperationTimePlanItem> tempList = operationTimePlan.getPlan().subList(0, i);
                            BigDecimal needQuantity = planItem.getQuantity().subtract(overageQuantity);
                            BigDecimal needRate = needQuantity.divide(planItem.getQuantity(),4, BigDecimal.ROUND_DOWN);
                            //结束时间减去开始时间，保留分钟
                            Long newEndTime = new BigDecimal(planItem.getEndedTime().minus(planItem.getStartedTime()).toNanos()).multiply(needRate).setScale(0, BigDecimal.ROUND_UP).longValue();
                            newEndTime = newEndTime + Duration.ofMinutes(1).toNanos() - 1;
                            planItem.setEndedTime(Duration.ofMinutes(Duration.ofNanos(newEndTime).toMinutes()).plus(planItem.getStartedTime()));
                            planItem.setQuantity(needQuantity);
                            tempList.set(tempList.size()-1, planItem);
                            operationTimePlan.setPlan(tempList);
                            evaludateOpertionTimePlanList.add(operationTimePlan);
                        }
                        break;
                    }
                    i++;
                }
                if(accumulation.compareTo(task.getOrderQuantity())>=0){
                    break;
                }
                evaludateOpertionTimePlanList.add(operationTimePlan);
            }
        }
        evaludateOpertionTimePlanList.forEach(operationTimePlan->{
            EvaluateOptionalEquipmentResult evaluateOptionalEquipmentResult = new EvaluateOptionalEquipmentResult()
            {{
                setEquipment(operationTimePlan.getEquipment());
                setDetails(new ArrayList<DetailPlan>());
                setStartedTime(Duration.ofSeconds(Long.MAX_VALUE));
                setEndedTime(Duration.ZERO);
                setScore(operationTimePlan.getScore());
            }};

            for (IOperationTimeCalculator.OperationTimePlanItem item : operationTimePlan.getPlan()) {
                if (evaluateOptionalEquipmentResult.getStartedTime().getSeconds() > item.getStartedTime().getSeconds()) {
                    evaluateOptionalEquipmentResult.setStartedTime(item.getStartedTime());
                }
                if (evaluateOptionalEquipmentResult.getEndedTime().getSeconds() < item.getEndedTime().getSeconds()) {
                    evaluateOptionalEquipmentResult.setEndedTime(item.getEndedTime());
                }
                evaluateOptionalEquipmentResult.getDetails().add(new EvaluateOptionalEquipmentResult.DetailPlan(){{
                    setStartedTime(item.getStartedTime());
                    setEndedTime(item.getEndedTime());
                    setQuantity(item.getQuantity());
                }});
            }
            rst.add(evaluateOptionalEquipmentResult);
        });
        return rst;
    }

    /**
     * 添加设备评分规则
     * @param evaluateEquipment
     */
    public void addEvaluateEquipment(EvaluateEquipment evaluateEquipment){
        evaluateEquipmentList.add(evaluateEquipment);
    }

    /**
     * 完全匹配工序设备选择
     * @param task
     * @param equipments
     * @return
     */
    private List<Equipment> matchEquipment(Task task, List<Equipment> equipments) {
        return equipments;
    }

}
