package com.ximai.mes.pro.schedule;

import cn.hutool.core.bean.BeanUtil;
import com.ximai.mes.md.service.IMdWorkunitService;
import com.ximai.mes.pro.schedule.strategy.EquipmentSelectionResult;
import com.ximai.mes.pro.schedule.strategy.EquipmentSelectionStrategy;
import com.ximai.mes.pro.service.task.IProTaskWorkunitService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class GreedyAlg implements IAlgorithm{

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    public GreedyAlg(EquipmentSelectionStrategy equipmentSelectionStrategy){
        this.equipmentSelectionStrategy = equipmentSelectionStrategy;
    }

    @Override
    public List<Job> schedule(List<Job> jobs, List<Resource> resources, List<Calendar> calendars) {
        calendarManager = new CalendarManager();
        this.sortedJobs = jobs.stream().collect(Collectors.toList());
        this.resources = resources.stream().collect(Collectors.toList());

        for (Job job : jobs) {
            for (Task task : job.getTasks())
            {
                task.setCalendarManager(calendarManager);
            }
        }
        for (Resource res : resources) {
            res.setCalendarManager(calendarManager);
        }


        for (Calendar cld : calendars) {
            if (cld instanceof Calendar.WorkingCalendar) {
                calendarManager.addWorkingCalendar((Calendar.WorkingCalendar)cld);
            }
        }

        for (Calendar cld : calendars) {
            if (cld instanceof Calendar.OccupiedCalendar)
                calendarManager.addOccupiedCalendar((Calendar.OccupiedCalendar)cld);
        }

        scheduleJobs();

        return sortedJobs;
    }


    /**
     * Job集合排产
     * @return 本次被排产的Task个数
     */
    private int scheduleJobs() {
        AtomicInteger scheduledTaskCount = new AtomicInteger(0);
        sortedJobs.forEach(job -> scheduledTaskCount.addAndGet(scheduleJob(job)));
        return scheduledTaskCount.get();
    }

    /**
     * Job排产
     * @param job
     * @return 本次被排产的Task个数
     */
    private int scheduleJob(Job job) {
        int scheduledTaskCount = 0;
        job.setScheduleStatus(ScheduleStatus.Success);
        if (job.getTasks().size() > 0) {
            Task firstTask = job.getTasks().getFirst();
            Task task = null;
            scheduledTaskCount += scheduleTask(firstTask, null);
            for (int i=0;i<job.getTasks().size()-1;i++) {
                Task preTask = job.getTasks().get(i);
                task = job.getTasks().get(i+1);
                if(job.getScheduleStatus() == ScheduleStatus.Success) {
                    LimitedTimeCalculateContext limitedTimeCalculateContext = new LimitedTimeCalculateContext(job, task, preTask);
                    ILimitedTimeCalculator.LimitedTimeCalculatedResult limitedTimeCalculatedResult = task.getLimitedTimeCalculator().calculate(limitedTimeCalculateContext);
                    task.getLmtStartedTime().addOptional(limitedTimeCalculatedResult.getLmtStartedTime());
                    task.getLmtEndedTime().addOptional(limitedTimeCalculatedResult.getLmtEndedTime());

                    scheduledTaskCount += scheduleTask(task, preTask);
                }
            }
            job.setScheduledStartedTime(job.getTasks().getFirst().getScheduledStartedTime());
            job.setScheduledEndedTime(job.getTasks().getLast().getScheduledEndedTime());

            if (job.getScheduleStatus()!= ScheduleStatus.Waiting && job.getScheduledEndedTime()!=null
                    && job.getScheduledEndedTime().getSeconds() > job.getLmtEndedTime().get().getSeconds()) {
                job.setScheduleStatus(ScheduleStatus.Delay);
            }
        }
        return scheduledTaskCount;
    }

    /**
     * Task排产
     * @param task
     * @return 本次是否排产
     */
    private int scheduleTask(Task task, Task preTask) {
        task.setPreTask(preTask);
        if (task.getScheduleStatus() == ScheduleStatus.Success) {
            return 0;
        }

        if (task.getLmtEndedTime().Waiting() || task.getLmtStartedTime().Waiting()) {
            task.setScheduleStatus(ScheduleStatus.Waiting);
            task.getJob().setScheduleStatus(ScheduleStatus.Waiting);
            return 0;
        }
        EquipmentSelectionResult selectionResult  = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        logger.info(task.getProcessName()+"，设备评分结果");
        logger.info(selectionResult.getEvaluateDetail().toString());
        List<EvaluateOptionalEquipmentResult> evaluateResultList = selectionResult.getSelectList();
        if(task.isOutsourced()){
            //查询外委作业单元
            Optional<Resource> equipmentResource = this.resources.stream().filter(s-> IMdWorkunitService.OUTSOURCE_WORKUNIT_ID.equals(((Equipment)s).getId())).findFirst();
            if(!equipmentResource.isPresent()){
                task.setScheduleStatus(ScheduleStatus.InadequateTime);
                task.getJob().setScheduleStatus(ScheduleStatus.InadequateTime);
                task.getJob().setScheduleDesc("未找到外协作业单元");
                return 0;
            }
            //外委工序固定三天加工工时
            task.setScheduleStatus(ScheduleStatus.Success);
            //首工序外协，使用当前时间作业开始时间
            if(preTask==null){
                task.setScheduledStartedTime(Duration.ofSeconds(0));
            }else{
                task.setScheduledStartedTime(preTask.getScheduledEndedTime());//使用上道任务结束时间
            }
            task.setScheduledEndedTime(task.getScheduledStartedTime().plusDays(3));
            task.setScheduledEquipment((Equipment) equipmentResource.get());//固定外委作业单元
            Calendar.OccupiedCalendar calendar = new Calendar.OccupiedCalendar(){{
                setJob(task.getJob());
                setTask(task);
                setResource(equipmentResource.get());
                setStartedTime(task.getScheduledStartedTime());
                setEndedTime(task.getScheduledEndedTime());
                setQuantity(task.getJob().getQuantity());
            }};
            calendarManager.addOccupiedCalendar(calendar);
            return 1;
        }else if (evaluateResultList.isEmpty()) {
            task.setScheduleStatus(ScheduleStatus.InadequateTime);
            task.getJob().setScheduleStatus(ScheduleStatus.InadequateTime);
            if(selectionResult.getCause() == EquipmentSelectionResult.EquipmentSelectionCause.UN_MATCH){
                task.getJob().setScheduleDesc(String.format("工序%s，未匹配到可排产设备", task.getProcessName()));
            }else if(selectionResult.getCause() == EquipmentSelectionResult.EquipmentSelectionCause.UN_EQUIPMENT){
                task.getJob().setScheduleDesc(String.format("工序%s，未找到可排产设备", task.getProcessName()));
            }else {
                StringBuffer temp = new StringBuffer();
                task.getOptionalEquipments().forEach(s -> {
                    temp.append("-" + s.getWorkUnitName());
                });
                task.getJob().setScheduleDesc(temp.toString());
            }
            return 0;
        }

        //任务分派到多台设备，拆分任务
        if(evaluateResultList.size()>1){
            List<Task> splitTask = new ArrayList<Task>();

            task.setScheduleStatus(ScheduleStatus.Success);
            evaluateResultList.forEach(selectedEquipment->{
                if(task.getScheduledStartedTime()==null){
                    task.setScheduledStartedTime(selectedEquipment.getStartedTime());
                }
                if(task.getScheduledEndedTime()==null){
                    task.setScheduledEndedTime(selectedEquipment.getEndedTime());
                }
                if(task.getScheduledEndedTime().compareTo(selectedEquipment.getEndedTime())<0){
                    task.setScheduledEndedTime(selectedEquipment.getEndedTime());
                }
                Task subtask = new Task(0l, task.getJob());
                BeanUtil.copyProperties(task, subtask);
                subtask.setScheduledStartedTime(selectedEquipment.getStartedTime());
                subtask.setScheduledEndedTime(selectedEquipment.getEndedTime());
                subtask.setScheduledEquipment(selectedEquipment.getEquipment());
                BigDecimal scheduleQuantity= selectedEquipment.getDetails().stream().map(s->s.getQuantity()).reduce(BigDecimal.ZERO, BigDecimal::add);
                subtask.setOrderQuantity(scheduleQuantity);
                splitTask.add(subtask);
                for (EvaluateOptionalEquipmentResult.DetailPlan detailPlan : selectedEquipment.getDetails()) {
                    Calendar.OccupiedCalendar calendar = new Calendar.OccupiedCalendar(){{
                        setJob(task.getJob());
                        setTask(task);
                        setResource(selectedEquipment.getEquipment());
                        setStartedTime(detailPlan.getStartedTime());
                        setEndedTime(detailPlan.getEndedTime());
                        setQuantity(detailPlan.getQuantity());
                    }};
                    calendarManager.addOccupiedCalendar(calendar);
                }
            });
            task.setSplitTask(splitTask);
        }else{
            EvaluateOptionalEquipmentResult selectedEquipment = evaluateResultList.get(0);
            task.setScheduleStatus(ScheduleStatus.Success);
            task.setScheduledStartedTime(selectedEquipment.getStartedTime());
            task.setScheduledEndedTime(selectedEquipment.getEndedTime());
            task.setScheduledEquipment(selectedEquipment.getEquipment());

            for (EvaluateOptionalEquipmentResult.DetailPlan detailPlan : selectedEquipment.getDetails()) {
                Calendar.OccupiedCalendar calendar = new Calendar.OccupiedCalendar(){{
                    setJob(task.getJob());
                    setTask(task);
                    setResource(selectedEquipment.getEquipment());
                    setStartedTime(detailPlan.getStartedTime());
                    setEndedTime(detailPlan.getEndedTime());
                    setQuantity(detailPlan.getQuantity());
                }};
                calendarManager.addOccupiedCalendar(calendar);
            }
        }
        return evaluateResultList.size();
    }


    private List<Job> sortedJobs;

    private List<Resource> resources;

    private CalendarManager calendarManager;
    private EquipmentSelectionStrategy equipmentSelectionStrategy;


}
