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

import cn.hutool.core.date.StopWatch;
import com.ximai.common.exception.ServiceException;
import com.ximai.common.utils.MessageUtils;
import com.ximai.mes.pro.domain.task.WorkorderScheduleParams;
import com.ximai.mes.pro.schedule.*;
import com.ximai.mes.pro.schedule.strategy.EquipmentSelectionStrategy;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ThXMScheduleAlgorithmAdapter implements IScheduleAlgorithmAdapter {

    private AlgorithmDataSource algorithmDataSource;

    private AlgorithmResultProcess algorithmResultProcess;

    private EquipmentSelectionStrategy equipmentSelectionStrategy;

    private List<AlgorithmExtendDataSource> algorithmExtendDataSources;

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public List<ScheduledResult> schedule(LocalDateTime schedulingStartedDate, List<WorkorderScheduleParams> workorderScheduleParams) {
        logger.info("----------排产开始----------");
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<ScheduledResult> scheduleResults = new ArrayList<ScheduledResult>();

        StopWatch stopWatch2 = new StopWatch();
        stopWatch2.start();
        List<Resource> algResources = algorithmDataSource.getResource(schedulingStartedDate, workorderScheduleParams);
        List<Job> algJobs = algorithmDataSource.getJob(schedulingStartedDate, workorderScheduleParams, algResources);
        List<Calendar> algCalendar = algorithmDataSource.getCalendar(schedulingStartedDate, workorderScheduleParams, algResources);
        stopWatch2.stop();
        logger.info(String.format("排产数据加载总耗时：%s", stopWatch2.getTotalTimeSeconds()));
        if(algorithmExtendDataSources!=null){
            AlgorithmDataContext algorithmDataContext = new AlgorithmDataContext(){{
                setJobs(algJobs);
                setCalendars(algCalendar);
                setResources(algResources);
                setWorkorderIds(workorderScheduleParams.stream().map(s->s.getWorkorderId()).collect(Collectors.toList()));
            }};
            algorithmExtendDataSources.forEach(s->{
                s.extendData(algorithmDataContext);
            });
        }
        //按工作中心分组设备
        Map<Long, List<Equipment>> equipmentMap = algResources.stream().filter(s->s instanceof  Equipment).map(s->(Equipment)s)
                .collect(Collectors.groupingBy(s->((Equipment) s).getWorkCenterId()));
        //设置排产任务可选设备
        algJobs.forEach(job->{
            job.getTasks().forEach(task->{
                task.setOptionalEquipments(equipmentMap.getOrDefault(task.getWorkCenterId(), new ArrayList<Equipment>()));
                task.setLimitedTimeCalculator(new OrderLimitedTimeCalculator());
                task.setOperationTimeCalculator(new OrderOperationTimeCalculator());
            });
        });

        IAlgorithm algorithm = new GreedyAlgThXM(equipmentSelectionStrategy);
        List<Job> result = algorithm.schedule(algJobs, algResources, algCalendar);

        for (Job alg_job : result)
        {
            String productionOrderNo =(String) alg_job.getJobData().get("ProductionOrderNo");
            String identifier = (String) alg_job.getJobData().get("Identifier");
            switch (alg_job.getScheduleStatus())
            {
                case Delay:
                    scheduleResults.add(new ScheduledResult(){{
                        setJobID(alg_job.getId());
                        setResultType(ScheduleStatus.Delay);
                        setProductionOrderNo(productionOrderNo);
                        setIdentifier(identifier);
                    }});
                    break;
                case InadequateTime:
                    scheduleResults.add(new ScheduledResult(){{
                        setJobID(alg_job.getId());
                        setResultType(ScheduleStatus.Delay);
                        setProductionOrderNo(productionOrderNo);
                        setIdentifier(identifier);
                        setResultDesc(alg_job.getScheduleDesc());
                    }});
                    break;
                case Waiting:
                    scheduleResults.add(new ScheduledResult(){{
                        setJobID(alg_job.getId());
                        setResultType(ScheduleStatus.Waiting);
                        setProductionOrderNo(productionOrderNo);
                        setIdentifier(identifier);
                        setResultDesc(alg_job.getScheduleDesc());
                    }});
                    break;
            }
        }
        stopWatch.stop();
        //排产成功执行回调
        algorithmResultProcess.execute(schedulingStartedDate, result);
        logger.info("----------排产结束----------");
        logger.info(String.format("排产总耗时：%s", stopWatch.getTotalTimeSeconds()));
        return scheduleResults;
    }

    private OrderOperationTimeCalculator orderOperationTimeCalculator = new OrderOperationTimeCalculator();

    public static class OrderOperationTimeCalculator implements IOperationTimeCalculator
    {

        public OperationTimePlan calculate(TaskSchedulingContext context)
        {
            return (new CalculatorTemporary(context)).calculate();
        }

        @Data
        private class CalculatorTemporary
        {
            private TaskSchedulingContext context;
            /*
             * 换型时间
             */
            private Duration setupTime;
            /*
             * OEE
             */
            private BigDecimal OEE;

            /*
             * 标准时间
             */
            private Duration standardWorkingTime;

            /*
             * 是否外协
             */
            private boolean isExternal;
            /*
             * 外协返回时间
             */
            private Duration deliveryDate;

            private BigDecimal quantity;

            public CalculatorTemporary(TaskSchedulingContext context)
            {
                this.context = context;
                this.setSetupTime(context.getTask().getSetupTime());
                this.setStandardWorkingTime(context.getTask().getStandardWorkingTime());
                this.quantity = context.getTask().getOrderQuantity();
            }

            public OperationTimePlan calculate() {
                OperationTimePlan operationTimePlan = new OperationTimePlan();
                operationTimePlan.setEquipment(context.getEquipment());
                //添加固化/转间时间
                if(context.getTask().getPreTask()!=null){
                    Task preTask = context.getTask().getPreTask();
                    Duration curingLastTime = null;
                    Duration transferTimeLastTime = null;
                    if(preTask.getSplitTask()!=null&&preTask.getSplitTask().size()>0){
                        Duration tempLastTime = preTask.getSplitTask().get(0).getScheduledEndedTime();
                        Duration tempTransferLastTime = preTask.getSplitTask().get(0).getScheduledEndedTime();
                        for(Task task : preTask.getSplitTask()){
                            Equipment equip = task.getScheduledEquipment();
                            if(equip.getCuringTime()!=0){
                                tempLastTime = tempLastTime.plusMinutes(equip.getCuringTime());
                            }
                            if(equip.getTransferShopTime()!=0){
                                tempTransferLastTime = tempTransferLastTime.plusMinutes(equip.getTransferShopTime());
                            }
                            if(curingLastTime==null){
                                curingLastTime = tempLastTime;
                            }
                            if(curingLastTime.compareTo(tempLastTime)<0){
                                curingLastTime = tempLastTime;
                            }
                            if(transferTimeLastTime==null){
                                transferTimeLastTime = tempTransferLastTime;
                            }
                            if(curingLastTime.compareTo(tempTransferLastTime)<0){
                                transferTimeLastTime = tempTransferLastTime;
                            }
                        }
                    }else{
                        Equipment equip = preTask.getScheduledEquipment();
                        curingLastTime = preTask.getScheduledEndedTime();
                        if(equip.getCuringTime()!=0){
                            curingLastTime = curingLastTime.plusMinutes(equip.getCuringTime());
                        }
                        transferTimeLastTime = preTask.getScheduledEndedTime();
                        if(equip.getTransferShopTime()!=0){
                            transferTimeLastTime = transferTimeLastTime.plusMinutes(equip.getTransferShopTime());
                        }
                    }
                    context.getTask().getLmtStartedTime().addOptional(curingLastTime);
                    context.getTask().getLmtStartedTime().addOptional(transferTimeLastTime);

                }

                //空闲时间
                List<Calendar.LeisureCalendar> leisureCalendar = context.getEquipment().getLeisureCalendar().stream().filter(s->{
                    return s.getEndedTime().getSeconds()>context.getTask().getLmtStartedTime().get().getSeconds();
                }).collect(Collectors.toList());
                if (leisureCalendar.isEmpty()) {
                    operationTimePlan.setScheduledSuccess(false);
                    return operationTimePlan;
                }

                //修正第一个空闲时间的开始时间
                Calendar.LeisureCalendar firstItem = new Calendar.LeisureCalendar()
                {{
                    setResource(leisureCalendar.get(0).getResource());
                    setStartedTime(context.getTask().getLmtStartedTime().get().getSeconds() > leisureCalendar.get(0).getStartedTime().getSeconds()
                            ? context.getTask().getLmtStartedTime().get() : leisureCalendar.get(0).getStartedTime());
                    setEndedTime(leisureCalendar.get(0).getEndedTime());
                }};
                firstItem.getCalendarData().putAll(leisureCalendar.get(0).getCalendarData());
                leisureCalendar.set(0, firstItem);

                operationTimePlan = internalSchedule(leisureCalendar);
                return operationTimePlan;
            }

            /*
             * 计算一段固定的日历的排产
             */
            private OperationTimePlan internalSchedule(List<Calendar.LeisureCalendar> leisureCalendar) {
                Duration setupDuration = Duration.ofMinutes(Duration.ofNanos(setupTime.toNanos() + Duration.ofMinutes(1).toNanos() - 1).toMinutes());
                Duration temp2Duration = Duration.ofMinutes(Duration.ofNanos(standardWorkingTime.multipliedBy(quantity.longValue()).toNanos() + Duration.ofMinutes(1).toNanos() - 1).toMinutes());
                // 需求时间总时长
                Duration totalTime2 = temp2Duration.plus(setupDuration);
                OperationTimePlan operationTimePlan = new OperationTimePlan() {{
                    setEquipment(context.getEquipment());
                    setTotalTime(totalTime2);
                    setPlan(new ArrayList<OperationTimePlanItem>());
                }};
                if (leisureCalendar.isEmpty()) {
                    operationTimePlan.setScheduledSuccess(false);
                    return operationTimePlan;
                }
                List<OperationTimePlanItem> operationTimePlanItems = new ArrayList<OperationTimePlanItem>();
                OperationTimePlanItem currentOperationTimePlanItem = null;

                // 待排数量
                BigDecimal unScheduleQuantity = quantity;
                Duration remainTotalTime = totalTime2;
                //循环空闲时间排产，直到结束
                for (int i = 0; i < leisureCalendar.size(); i++) {
                    Calendar.LeisureCalendar calItem = leisureCalendar.get(i);
                    if(calItem.getEndedTime().compareTo(calItem.getStartedTime())<0){
                        throw new ServiceException(MessageUtils.message("pro.schedule.error.cal.error1"));
                    }
                    // 当前空闲段总时长
                    Duration calTime = calItem.getEndedTime().minus(calItem.getStartedTime());
                    //空闲时间足够排产
                    if (calTime.getSeconds() >= remainTotalTime.getSeconds()) {
                        operationTimePlan.setScheduledSuccess(true);
                        currentOperationTimePlanItem = new OperationTimePlanItem(unScheduleQuantity,
                                calItem.getStartedTime(), calItem.getStartedTime().plus(remainTotalTime), calItem.getShiftType());
                        operationTimePlanItems.add(currentOperationTimePlanItem);
                        operationTimePlan.setPlan(operationTimePlanItems);
                        return operationTimePlan;
                    } else {
                        // 空余时间可排产数量占比
                        BigDecimal scheduleQuantity = unScheduleQuantity.multiply(BigDecimal.valueOf(calTime.minus(setupTime).toNanos())).divide(BigDecimal.valueOf((remainTotalTime.toNanos())),4, BigDecimal.ROUND_DOWN);
                        remainTotalTime = remainTotalTime.minus(calTime);
                        unScheduleQuantity = unScheduleQuantity.subtract(scheduleQuantity);
                        currentOperationTimePlanItem = new OperationTimePlanItem(){{
                            setStartedTime(calItem.getStartedTime());
                            setEndedTime(calItem.getEndedTime());
                            setQuantity(scheduleQuantity);
                        }};
                        operationTimePlanItems.add(currentOperationTimePlanItem);
                    }
                }
                operationTimePlan.setPlan(operationTimePlanItems);
                operationTimePlan.setScheduledSuccess(false);
                return operationTimePlan;
            }
        }
    }

    private OrderLimitedTimeCalculator orderLimitedTimeCalculator = new OrderLimitedTimeCalculator();
    public static class OrderLimitedTimeCalculator implements ILimitedTimeCalculator {
        public LimitedTimeCalculatedResult calculate(LimitedTimeCalculateContext context) {
            return new LimitedTimeCalculatedResult()
            {{
                setLmtStartedTime(context.getPreviousTask().getScheduledEndedTime());
                setLmtEndedTime(context.getTask().getLmtEndedTime().get());
            }};
        }
    }

    public void setAlgorithmDataSource(AlgorithmDataSource algorithmDataSource) {
        this.algorithmDataSource = algorithmDataSource;
    }

    public void setAlgorithmResultProcess(AlgorithmResultProcess algorithmResultProcess) {
        this.algorithmResultProcess = algorithmResultProcess;
    }

    public void setEquipmentSelectionStrategy(EquipmentSelectionStrategy equipmentSelectionStrategy) {
        this.equipmentSelectionStrategy = equipmentSelectionStrategy;
    }

    public void setAlgorithmExtendDataSources(List<AlgorithmExtendDataSource> algorithmExtendDataSources) {
        this.algorithmExtendDataSources = algorithmExtendDataSources;
    }

}
