package com.bs.mes.schedule.strategy;

import cn.hutool.core.date.DateUtil;
import com.ximai.mes.pro.schedule.*;
import com.ximai.mes.pro.schedule.impl.BaoshenScheduleAlgorithmAdapter;
import com.ximai.mes.pro.schedule.strategy.EquipmentSelectionResult;
import com.ximai.mes.pro.schedule.strategy.EquipmentSelectionStrategyImpl;
import com.ximai.mes.pro.schedule.strategy.EvaluateEquipmentFunction;
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class EquipmentSelectionStrategyImplTest {
    @Test public void evaluateOptionalEquipment() throws Exception {
        EquipmentSelectionStrategyImpl equipmentSelectionStrategy = new EquipmentSelectionStrategyImpl();
        Method methods = EquipmentSelectionStrategyImpl.class.getDeclaredMethod("evaluateOptionalEquipment", Task.class, List.class);
        methods.setAccessible(true);
        Task task = new Task(1l, null);
        task.setOrderQuantity(BigDecimal.valueOf(750));
        List<IOperationTimeCalculator.OperationTimePlan> operationTimePlans = new ArrayList<IOperationTimeCalculator.OperationTimePlan>();
        IOperationTimeCalculator.OperationTimePlan plan1 = new IOperationTimeCalculator.OperationTimePlan();
        plan1.setScore(1);
        List<IOperationTimeCalculator.OperationTimePlanItem> operationTimePlanItems = new ArrayList<IOperationTimeCalculator.OperationTimePlanItem>();
        IOperationTimeCalculator.OperationTimePlanItem plan1Item1 = new IOperationTimeCalculator.OperationTimePlanItem(BigDecimal.valueOf(500),
                Duration.ofHours(2), Duration.ofHours(4), "白班");
        operationTimePlanItems.add(plan1Item1);
        plan1.setPlan(operationTimePlanItems);

        IOperationTimeCalculator.OperationTimePlan plan2 = new IOperationTimeCalculator.OperationTimePlan();
        plan1.setScore(1);
        List<IOperationTimeCalculator.OperationTimePlanItem> operationTimePlanItems2 = new ArrayList<IOperationTimeCalculator.OperationTimePlanItem>();
        IOperationTimeCalculator.OperationTimePlanItem plan2Item1 = new IOperationTimeCalculator.OperationTimePlanItem(BigDecimal.valueOf(500),
                Duration.ofHours(3), Duration.ofHours(5), "中班");
        operationTimePlanItems2.add(plan2Item1);
        plan2.setPlan(operationTimePlanItems2);

        operationTimePlans.add(plan1);
        operationTimePlans.add(plan2);

        List<EvaluateOptionalEquipmentResult> rst = (List<EvaluateOptionalEquipmentResult>)methods.invoke(equipmentSelectionStrategy, task, operationTimePlans);
        Assert.assertEquals(rst.size(), 2);
        Assert.assertEquals(Duration.ofHours(4), rst.get(0).getEndedTime());
        Assert.assertEquals(BigDecimal.valueOf(500), rst.get(0).getDetails().get(0).getQuantity());
        Assert.assertEquals(Duration.ofHours(4), rst.get(1).getEndedTime());
        Assert.assertEquals(BigDecimal.valueOf(250), rst.get(1).getDetails().get(0).getQuantity());
    }

    @Test public void evaluateOptionalEquipment2() throws Exception {
        LocalDateTime scheduleStartDate=LocalDateTime.of(2022,5,5,0,0);
        CalendarManager calendarManager = new CalendarManager();
        Equipment resource1 = new Equipment() {{
            setId(1l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-1");
            setIsParallel(false);
            setCalendarManager(calendarManager);
        }};
        Equipment resource2 = new Equipment() {{
            setId(2l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-2");
            setIsParallel(false);
            setCalendarManager(calendarManager);
        }};
        Equipment resource3 = new Equipment() {{
            setId(3l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-3");
            setIsParallel(false);
            setCalendarManager(calendarManager);
        }};
        List<Equipment> equipmentList = new ArrayList<Equipment>();
        equipmentList.add(resource1);
        equipmentList.add(resource2);
        equipmentList.add(resource3);
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource1, scheduleStartDate, "22-05-05 12:30","22-05-05 16:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-05 13:30","22-05-05 17:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-06 13:30","22-05-06 17:30"));
        EquipmentSelectionStrategyImpl equipmentSelectionStrategy = new EquipmentSelectionStrategyImpl();
        Job job = new Job("1", Duration.ofHours(24));
        Task task = new Task(1l, job);
        task.setOrderQuantity(BigDecimal.valueOf(1000));
        task.setStandardWorkingTime(Duration.ofSeconds(20));
        task.setCalendarManager(calendarManager);
        task.setWorkCenterId(1l);
        task.setOptionalEquipments(equipmentList);
        task.setLimitedTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderLimitedTimeCalculator());
        task.setOperationTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderOperationTimeCalculator());
        EquipmentSelectionResult result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        List<EvaluateOptionalEquipmentResult> rst = result.getSelectList();
        Assert.assertEquals(2, rst.size());
        Assert.assertEquals(Duration.ofHours(12).plus(Duration.ofMinutes(30)).plus(Duration.ofHours(4)), rst.get(0).getEndedTime());
        Assert.assertEquals(Duration.ofHours(13).plus(Duration.ofMinutes(30)).plus(Duration.ofMinutes(94)), rst.get(1).getEndedTime());
    }

    //设备总评分测试
    @Test public void evaluateOptionalEquipmentScore() throws Exception {
        LocalDateTime scheduleStartDate=LocalDateTime.of(2022,5,5,0,0);
        CalendarManager calendarManager = new CalendarManager();
        Equipment.EquipmentFunction equipmentFunction1 = new Equipment.EquipmentFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
        }};

        Equipment.EquipmentFunction equipmentFunction2 = new Equipment.EquipmentFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
            setSacmForPasteComposite("Y");
            setSacmForThermalCutting("Y");
            setSacmForThermalSelfFusion("N");
            setSacmForCreasing("N");
            setSacmForLabeling("N");
            setSacmForBuckleUp("N");
            setSacmForOrgan("N");
        }};
        Equipment resource1 = new Equipment() {{
            setId(1l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-1");
            setIsParallel(false);
            setCalendarManager(calendarManager);
            setEquipmentFunction(equipmentFunction1);
        }};
        Equipment resource2 = new Equipment() {{
            setId(2l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-2");
            setIsParallel(false);
            setCalendarManager(calendarManager);
            setEquipmentFunction(equipmentFunction2);
        }};
        Equipment resource3 = new Equipment() {{
            setId(3l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-3");
            setIsParallel(false);
            setCalendarManager(calendarManager);
        }};
        List<Equipment> equipmentList = new ArrayList<Equipment>();
        equipmentList.add(resource1);
        equipmentList.add(resource2);
        equipmentList.add(resource3);
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource1, scheduleStartDate, "22-05-05 08:00","22-05-05 12:00"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource1, scheduleStartDate, "22-05-05 13:30","22-05-05 17:30"));

        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-05 13:30","22-05-05 17:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-06 08:00","22-05-06 12:00"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-06 13:30","22-05-06 17:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-07 08:00","22-05-07 12:00"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-07 13:30","22-05-07 17:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-08 08:00","22-05-08 12:00"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-08 13:30","22-05-08 17:30"));
        EquipmentSelectionStrategyImpl equipmentSelectionStrategy = new EquipmentSelectionStrategyImpl();
        Job job = new Job("1", Duration.ofHours(24));
        Task task = new Task(1l, job);
        task.setOrderQuantity(BigDecimal.valueOf(7200));
        task.setStandardWorkingTime(Duration.ofSeconds(5));
        task.setCalendarManager(calendarManager);
        task.setWorkCenterId(1l);
        task.setOptionalEquipments(equipmentList);
        task.setLimitedTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderLimitedTimeCalculator());
        task.setOperationTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderOperationTimeCalculator());
        EquipmentSelectionResult result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        List<EvaluateOptionalEquipmentResult> rst = result.getSelectList();
        System.out.println(result.getEvaluateDetail());
        Assert.assertEquals(2, rst.size());
        Assert.assertEquals(80, rst.get(0).getScore(), 0.1);
        Assert.assertEquals(70, rst.get(1).getScore(), 0.1);
        //添加设备功能点优先级
        equipmentSelectionStrategy.addEvaluateEquipment(new EvaluateEquipmentFunction());
        result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        rst = result.getSelectList();
        Assert.assertEquals(2, rst.size());
        Assert.assertEquals(80+6, rst.get(0).getScore(), 0.1);
        Assert.assertEquals(70+4, rst.get(1).getScore(), 0.1);

    }

    //验证设备硬性条件要求
    @Test public void evaluateOptionalEquipmentMatch() throws Exception {
        LocalDateTime scheduleStartDate=LocalDateTime.of(2022,5,5,0,0);
        CalendarManager calendarManager = new CalendarManager();
        Equipment.EquipmentFunction equipmentFunction1 = new Equipment.EquipmentFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
            setSacmForPasteComposite("Y");
            setSacmForThermalCutting("Y");
            setSacmForThermalSelfFusion("Y");
            setSacmForCreasing("Y");
            setSacmForLabeling("Y");
            setSacmForBuckleUp("Y");
            setSacmForOrgan("Y");
        }};
        Equipment.EquipmentAttribute equipmentAttribute1 = new Equipment.EquipmentAttribute(){{
            setTreatingWidthMin(BigDecimal.valueOf(12));
            setTreatingWidthMax(BigDecimal.valueOf(34));
            setTreatingThicknessMin(BigDecimal.valueOf(0.2));
            setTreatingThicknessMax(BigDecimal.valueOf(2));
            setPrintingMethod("单面印刷");
            setDieSunkOffsetOilTankNum(BigDecimal.valueOf(3));
        }};
        Equipment resource1 = new Equipment() {{
            setId(1l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-1");
            setIsParallel(false);
            setCalendarManager(calendarManager);
            setEquipmentFunction(equipmentFunction1);
            setEquipmentAttribute(equipmentAttribute1);
        }};
        Equipment.EquipmentFunction equipmentFunction2 = new Equipment.EquipmentFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
            setSacmForPasteComposite("Y");
            setSacmForThermalCutting("Y");
            setSacmForThermalSelfFusion("Y");
            setSacmForCreasing("Y");
            setSacmForLabeling("Y");
            setSacmForBuckleUp("Y");
            setSacmForOrgan("Y");
        }};
        Equipment.EquipmentAttribute equipmentAttribute2 = new Equipment.EquipmentAttribute(){{
            setTreatingWidthMin(BigDecimal.valueOf(12));
            setTreatingWidthMax(BigDecimal.valueOf(34));
            setTreatingThicknessMin(BigDecimal.valueOf(0.2));
            setTreatingThicknessMax(BigDecimal.valueOf(2));
            setPrintingMethod("单面印刷");
            setDieSunkOffsetOilTankNum(BigDecimal.valueOf(3));
        }};
        Equipment resource2 = new Equipment() {{
            setId(2l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-2");
            setIsParallel(false);
            setCalendarManager(calendarManager);
            setEquipmentFunction(equipmentFunction2);
            setEquipmentAttribute(equipmentAttribute2);
        }};
        Equipment.EquipmentFunction equipmentFunction3 = new Equipment.EquipmentFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
            setSacmForPasteComposite("Y");
            setSacmForThermalCutting("Y");
            setSacmForThermalSelfFusion("Y");
            setSacmForCreasing("Y");
            setSacmForLabeling("Y");
            setSacmForBuckleUp("Y");
            setSacmForOrgan("Y");
        }};
        Equipment.EquipmentAttribute equipmentAttribute3 = new Equipment.EquipmentAttribute(){{
            setTreatingWidthMin(BigDecimal.valueOf(12));
            setTreatingWidthMax(BigDecimal.valueOf(34));
            setTreatingThicknessMin(BigDecimal.valueOf(0.2));
            setTreatingThicknessMax(BigDecimal.valueOf(2));
            setPrintingMethod("单面印刷");
            setDieSunkOffsetOilTankNum(BigDecimal.valueOf(3));
        }};
        Equipment resource3 = new Equipment() {{
            setId(3l);
            setWorkCenterId(1l);
            setWorkUnitName("柔版印刷-3");
            setIsParallel(false);
            setCalendarManager(calendarManager);
            setEquipmentFunction(equipmentFunction3);
            setEquipmentAttribute(equipmentAttribute3);
        }};
        List<Equipment> equipmentList = new ArrayList<Equipment>();
        equipmentList.add(resource1);
        equipmentList.add(resource2);
        equipmentList.add(resource3);
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource1, scheduleStartDate, "22-05-05 12:30","22-05-05 16:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-05 13:30","22-05-05 17:30"));
        calendarManager.addWorkingCalendar(this.buildWorkingCalendar(resource2, scheduleStartDate, "22-05-06 13:30","22-05-06 17:30"));
        EquipmentSelectionStrategyImpl equipmentSelectionStrategy = new EquipmentSelectionStrategyImpl();
        Job job = new Job("1", Duration.ofHours(24));
        Task task = new Task(1l, job);
        task.setOrderQuantity(BigDecimal.valueOf(1000));
        task.setStandardWorkingTime(Duration.ofSeconds(20));
        task.setCalendarManager(calendarManager);
        task.setWorkCenterId(1l);
        task.setOptionalEquipments(equipmentList);
        task.setLimitedTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderLimitedTimeCalculator());
        task.setOperationTimeCalculator(new BaoshenScheduleAlgorithmAdapter.OrderOperationTimeCalculator());
        Task.ProcessFunction processFunction = new Task.ProcessFunction(){{
            setSacmForEdgeOpening("Y");
            setSacmForSlitting("Y");
            setSacmForMolarTooth("Y");
            setSacmForThermalComposite("Y");
        }};
        Task.ProcessRequestAttribute processRequestAttribute = new Task.ProcessRequestAttribute(){{
            setDieSunkOffsetOilTankNum(BigDecimal.valueOf(1));
            setThick(BigDecimal.valueOf(0.3));
            setPrintingWidth(BigDecimal.valueOf(20));
            setPrintingMethod("单面印刷");
        }};
        task.setProcessFunction(processFunction);
        task.setProcessRequestAttribute(processRequestAttribute);
        //所有设备满足要求
        EquipmentSelectionResult result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        List<EvaluateOptionalEquipmentResult> rst = result.getSelectList();
        Assert.assertEquals(2, rst.size());
        Assert.assertEquals(Duration.ofHours(12).plus(Duration.ofMinutes(30)).plus(Duration.ofHours(4)), rst.get(0).getEndedTime());
        Assert.assertEquals(Duration.ofHours(13).plus(Duration.ofMinutes(30)).plus(Duration.ofMinutes(94)), rst.get(1).getEndedTime());
        //所有设备不满足要求
        processRequestAttribute.setPrintingMethod("双面印刷");
        result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        rst = result.getSelectList();
        Assert.assertEquals(0, rst.size());
        //设备2不满足需求
        processRequestAttribute.setPrintingMethod("单面印刷");
        equipmentFunction2.setSacmForThermalComposite("N");
        result = equipmentSelectionStrategy.evaluateOptionalEquipment(task);
        rst = result.getSelectList();
        Assert.assertEquals(1, rst.size());
    }


    private Calendar.WorkingCalendar buildWorkingCalendar(Resource resource, LocalDateTime scheduleStartDate, String start, String end){

        return new Calendar.WorkingCalendar() {{
            LocalDateTime startDate = DateUtil.toLocalDateTime(DateUtil.parse(start,"yy-MM-dd HH:mm"));
            LocalDateTime endDate = DateUtil.toLocalDateTime(DateUtil.parse(end,"yy-MM-dd HH:mm"));
            setStartedTime(Duration.between(scheduleStartDate, startDate));
            setEndedTime(Duration.between(scheduleStartDate, endDate));
            setResource(resource);
        }};
    }
}
