package com.xisc.pm.app.service.autoboard.core;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import com.google.common.collect.Lists;
import com.xisc.pm.api.dto.PmProContYieldResposeDTO;
import com.xisc.pm.app.service.autoboard.config.CalcLog;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardExecParam;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardExecResult;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardPolicyGroupDTO;
import com.xisc.pm.app.service.autoboard.dto.PmAutoPdLog;
import com.xisc.pm.app.service.autoboard.dto.PmContBoardParam;
import com.xisc.pm.app.service.autoboard.dto.PmPdResultDTO;
import com.xisc.pm.infra.enums.FormPlateMode;

/**
 * 组板程序v1.0（S）只能组同长的S型组板
 * S型组板，采用贪心思想，已组合版宽度最近为最优原则匹配子板
 *  1. 长度接近极限长
 *  2. 优先最长宽度与最小宽度
 *  3. 不能组板 剩余最小宽度当匹配下一队首宽度时重新入队尾
 * @author jun.zeng
 *
 */
public class PmPdProgramS extends DefaultPmPdProgram {

    private Deque<PmContBoardParam> priorityQueue;
    private PmAutoBoardExecResult execResult;
    private PmAutoBoardExecParam current;
    private PmAutoBoardPolicyGroupDTO policyGroup;
    private FormPlateMode formPlateMode;
    // 同宽小板数,KEY 为宽度
    private Map<String , BigDecimal> sameWidSpCountMap;
    
    // 队列的副本
    private Deque<PmContBoardParam> priorityQueueCopies;
    

    public PmPdProgramS(Queue<PmContBoardParam> priorityQueue, PmAutoBoardExecResult execResult,
            PmAutoBoardExecParam current, PmAutoBoardPolicyGroupDTO policyGroup, FormPlateMode formPlateMode) {
        this.current = current;
        // S型组板需要使用双端队列
        this.priorityQueue = (Deque<PmContBoardParam>) priorityQueue;
        this.execResult = execResult;
        this.policyGroup = policyGroup;
        this.formPlateMode = formPlateMode;
        sameWidSpCountMap = new HashMap<String, BigDecimal>();
        for (PmContBoardParam pmContBoardParam : priorityQueue) {
            BigDecimal qty = execResult.calcRemainingPdQty(pmContBoardParam);
            String key = pmContBoardParam.getOrderWid().toPlainString();
            if(sameWidSpCountMap.containsKey(key)) {
                sameWidSpCountMap.put(key, sameWidSpCountMap.get(key).add(qty));
            } else {
                sameWidSpCountMap.put(key, qty);
            }
        }
        this.priorityQueueCopies = new LinkedList<PmContBoardParam>(this.priorityQueue);
    }

    @Override
    public Boolean call() throws Exception {
        if(priorityQueue.isEmpty()) {
            return true;
        }
        // 正在组板标记
        boolean doPdFlag = false;
        PmContBoardParam axisPmContBoardParam = null;
        BigDecimal szWid = BigDecimal.ZERO;
        BigDecimal szLen = BigDecimal.ZERO;
        BigDecimal allWt = BigDecimal.ZERO;
        BigDecimal spCount = BigDecimal.ZERO;
        List<BigDecimal> lens = new ArrayList<>();
        List<String> block1 = new ArrayList<>();
        List<String> block2 = new ArrayList<>();
        // 组板中的集合
        List<PmContBoardParam> pmContBoardParamsFirsts = new ArrayList<>();
        List<PmContBoardParam> pmContBoardParamsLasts = new ArrayList<>();
        // 
        b:while (!priorityQueue.isEmpty()) {

            PmContBoardParam pmContBoardParamFirst = priorityQueue.peekFirst();
            PmContBoardParam pmContBoardParamLast = priorityQueue.peekLast();
            BigDecimal firstWid = pmContBoardParamFirst.getOrderWid() ;
            BigDecimal lastWid = pmContBoardParamLast.getOrderWid() ;
            BigDecimal canLastSpCount = sameWidSpCountMap.get(pmContBoardParamLast.getOrderWid().toPlainString());
            BigDecimal canFirstCount = sameWidSpCountMap.get(firstWid.toPlainString());
            
            if(canLastSpCount.compareTo(BigDecimal.ZERO) <= 0) {
                // 合同已经组板，直接剔除。
                priorityQueue.pollLast();
            }
            if(canFirstCount.compareTo(BigDecimal.ZERO) <= 0) {
                // 合同已经组板，直接剔除。
                priorityQueue.pollFirst();
                continue;
            }
            if(firstWid.compareTo(lastWid) == 0) {
                // 如果上下同宽，最多组板小板为总小板数的一半
                canLastSpCount = canLastSpCount.divide(BigDecimal.valueOf(2L) , 0, RoundingMode.DOWN);
                canFirstCount = canFirstCount.divide(BigDecimal.valueOf(2L) , 0, RoundingMode.DOWN);
                if(canLastSpCount.compareTo(BigDecimal.ZERO) <= 0) {
                    CalcLog.log("当前分组内一块板不能组S型大板！orderNo=" + pmContBoardParamFirst.getOrderNo());
                    CalcLog.log(new PmAutoPdLog(current.getRequestId() , execResult.getVersion()  , current.getMoldId()).setAxisOrderNo(pmContBoardParamFirst.getOrderNo()).setBlock1(block1).setBlock2(block2)
                            .setFormPlateMode(formPlateMode.getCode()).setLens(lens).setMessage("当前分组内一块板不能组S型大板！orderNo=" + pmContBoardParamFirst.getOrderNo()));
                    priorityQueue.poll();
                    continue;
                }
            }
            
            BigDecimal qty = execResult.calcRemainingPdQty(pmContBoardParamFirst);
            if (BigDecimal.ZERO.compareTo(qty) >= 0) {
                priorityQueue.poll();
                continue;
            }
            if (!doPdFlag) {
                doPdFlag = true;
                axisPmContBoardParam = pmContBoardParamFirst;
                szLen = BigDecimal.ZERO;
                spCount = BigDecimal.ZERO;
                allWt = BigDecimal.ZERO;
                lens.clear();
                block1.clear();
                block2.clear();
            }
            BigDecimal tmpRemainLen = policyGroup.getMaxCombinePlateLen().subtract(szLen);
            BigDecimal quotient = tmpRemainLen.divide(pmContBoardParamFirst.getSmallPlateLen(), 0, RoundingMode.DOWN); // 整除
            // S型组板小板个数判断。
            boolean sWidSpCountzflag = true;
            if(spCount.add(qty).compareTo(canLastSpCount) >= 0 || spCount.add(qty).compareTo(canFirstCount) >= 0) {
                // 如果组板小板数
                sWidSpCountzflag = false;
            }
                    
            
            if (quotient.compareTo(qty) > 0 && sWidSpCountzflag) {
                // 如果当前合同剩余数量未达到最大组板长度，合同出队列。全部加到待组板队列中
                spCount = spCount.add(qty);
                szWid = axisPmContBoardParam.getOrderWid();
                szLen = szLen.add(pmContBoardParamFirst.getSmallPlateLen().multiply(qty));
                allWt = allWt.add(pmContBoardParamFirst.getOrderUnitAim().multiply(qty));
                for (BigDecimal i = qty; i.compareTo(BigDecimal.ZERO) > 0; i = i.subtract(BigDecimal.ONE)) {
                    lens.add(pmContBoardParamFirst.getSmallPlateLen());
                    block1.add(pmContBoardParamFirst.getOrderNo());
                }
                pmContBoardParamsFirsts.add(pmContBoardParamFirst);
                priorityQueue.poll();
            } else {
                if(quotient.compareTo(qty) > 0) {
                    quotient = qty;
                }
                if(spCount.add(quotient).compareTo(canFirstCount) > 0) {
                    quotient = canFirstCount.subtract(spCount);
                }
                // 如果小板数+当前剩余长可组小板数，仍大于移动侧可组板小板剩余总数,则限制组板数
                if(spCount.add(quotient).compareTo(canLastSpCount) > 0) {
                    quotient = canLastSpCount.subtract(spCount);
                }
                
                // 如果剩余数量满足需求，数量增加
                if(quotient.compareTo(BigDecimal.ZERO) > 0) {
                    spCount = spCount.add(quotient);
                    szWid = axisPmContBoardParam.getOrderWid();
                    szLen = szLen.add(pmContBoardParamFirst.getSmallPlateLen().multiply(quotient));
                    allWt = allWt.add(pmContBoardParamFirst.getOrderUnitAim().multiply(quotient));
                    pmContBoardParamsFirsts.add(pmContBoardParamFirst);
                    for (BigDecimal i = quotient; i.compareTo(BigDecimal.ZERO) > 0; i = i.subtract(BigDecimal.ONE)) {
                        lens.add(pmContBoardParamFirst.getSmallPlateLen());
                        block1.add(pmContBoardParamFirst.getOrderNo());
                    }
                } else {
                    if(pmContBoardParamsFirsts.isEmpty()) {
                        continue ;
                    }
                    pmContBoardParamFirst = pmContBoardParamsFirsts.get(pmContBoardParamsFirsts.size() - 1);
                    quotient = execResult.calcRemainingPdQty(pmContBoardParamFirst);
                }
                
                // 添加第二层板
                Map<String , PmContBoardParam> pmContBoardParamMap = new HashMap<>();
                BigDecimal lastSpCount = BigDecimal.ZERO;
                while(lastSpCount.compareTo(spCount) < 0) {
                    pmContBoardParamLast = priorityQueue.peekLast();
                    if(pmContBoardParamLast == null) {
                        doPdFlag = false;
                        for (PmContBoardParam pmContBoardParam : pmContBoardParamsLasts) {
                            // 如果不能组板，除轴合同外，其他合同全部入队。重新开始新的组板
                            if(pmContBoardParam.getOrderNo().equals(axisPmContBoardParam.getOrderNo())) {
                                priorityQueue.offerLast(pmContBoardParam);
                            }
                        }
                        for (PmContBoardParam pmContBoardParam : pmContBoardParamsFirsts) {
                            // 如果不能组板，除轴合同外，其他合同全部入队。重新开始新的组板
                            if(pmContBoardParam.getOrderNo().equals(axisPmContBoardParam.getOrderNo())) {
                                priorityQueue.offerFirst(pmContBoardParam);
                            }
                        }
                        pmContBoardParamsFirsts.clear();
                        pmContBoardParamsLasts.clear();
                        pmContBoardParamMap.clear();
//                        priorityQueue.offerFirst(pmContBoardParamFirst);
                        continue b;
                    }
                    pmContBoardParamsLasts.add(pmContBoardParamLast);
                    pmContBoardParamMap.put(pmContBoardParamLast.getOrderNo(), pmContBoardParamLast);
                    // 队尾合同剩余可组板数量
                    BigDecimal qtyLast = execResult.calcRemainingPdQty(pmContBoardParamLast);
                    // 计算block1占用了几块
                    for(String orderNo : block1) {
                        if(orderNo.equals(pmContBoardParamLast.getOrderNo())) {
                            // 如果第二层与第一层存在重复合同，校验第二层是否有足够的合同参与组板
                            qtyLast = qtyLast.subtract(BigDecimal.ONE);
                        }
                    }
                    if(qtyLast.compareTo(BigDecimal.ZERO) <=0) {
                        doPdFlag = false;
                        pmContBoardParamsFirsts.clear();
                        pmContBoardParamsLasts.clear();
                        pmContBoardParamMap.clear();
                        priorityQueue.pollFirst();
                        continue b;
                    }
                    // 第二层需要的小板数
                    BigDecimal remainCount = spCount.subtract(lastSpCount);
                    
                    if(qtyLast.compareTo(remainCount) > 0) {
                        // 如果合同可组板数大于第二层需要的组组板数。第二层组板数达到要求，直接赋值为总小板数
                        lastSpCount = spCount;
                    } else if(qtyLast.compareTo(remainCount) == 0) {
                        // 如果合同可组板数等于第二层需要的组组板数。第二层组板数达到要求，直接赋值为总小板数，并且队尾合同出队列
                        lastSpCount = spCount;
                        priorityQueue.pollLast();
                    } else {
                        // 如果小于的话，第二层子板数加上合同可组板数，并且合同出队列
                        lastSpCount = lastSpCount.add(qtyLast);
                        remainCount = qtyLast;
                        priorityQueue.pollLast();
                    }
                    // 添加合同到待组板记录中
                    for (BigDecimal i = remainCount; i.compareTo(BigDecimal.ZERO) > 0; i = i.subtract(BigDecimal.ONE)) {
                        szWid = axisPmContBoardParam.getOrderWid().add(pmContBoardParamLast.getOrderWid());
                        block2.add(pmContBoardParamLast.getOrderNo());
                        allWt = allWt.add(pmContBoardParamLast.getOrderUnitAim());
                    }
                }
                
                BigDecimal i = quotient;
                p: do {
                    List<PmContBoardParam> orderList = Lists.newArrayList(pmContBoardParamsFirsts);
                    orderList.addAll(pmContBoardParamsLasts);
                    PmProContYieldResposeDTO respose = checkResult(formPlateMode, orderList, policyGroup, current,execResult, szWid, szLen,
                            axisPmContBoardParam, lens, allWt , block1 , block2);
                    if (respose != null) {
                        // 如果能组板。记录组板结果；
                        PmPdResultDTO pmPdResult = new PmPdResultDTO(szLen, szWid, spCount.multiply(BigDecimal.valueOf(2)), allWt, lens, orderList
                                , formPlateMode.getCode(), block1, block2 , axisPmContBoardParam);
                        pmPdResult.setRespose(respose);
                        pmPdResult.setCurrent(current);
                        if(respose.getTransFlag() != null) {
                            pmPdResult.setTransFlag(respose.getTransFlag());
                        }
                        try {
                            BigDecimal firstSpcount = sameWidSpCountMap.get(firstWid.toPlainString()).subtract(spCount);
                            BigDecimal secondSpcount = sameWidSpCountMap.get(lastWid.toPlainString()).subtract(spCount);
                            if(firstWid.compareTo(lastWid) == 0) {
                                // 如果上线两层同宽校验宽度需要再次减一层小板数
                                secondSpcount = secondSpcount.subtract(spCount);
                            }
                            if(firstSpcount.compareTo(BigDecimal.ZERO) < 0 && secondSpcount.compareTo(BigDecimal.ZERO) < 0) {
                                throw new RuntimeException("小板数不足，第一层" + firstSpcount.toPlainString() + "，第二层" + secondSpcount.toPlainString());
                            }
                            boolean canExecFlag = execResult.addVirtualSlab(pmPdResult);
                            if(!canExecFlag) {
                                return false;
                            }
                            BigDecimal firstRemainQty = execResult.calcRemainingPdQty(axisPmContBoardParam);
                            if(firstRemainQty.compareTo(BigDecimal.ZERO) > 0 && !priorityQueue.isEmpty() 
                                    && !priorityQueue.peekFirst().getOrderNo().equals(axisPmContBoardParam.getOrderNo())) {
                                priorityQueue.offerFirst(axisPmContBoardParam);
                            }
                            sameWidSpCountMap.put(firstWid.toPlainString(), sameWidSpCountMap.get(firstWid.toPlainString()).subtract(spCount));
                            sameWidSpCountMap.put(lastWid.toPlainString(), sameWidSpCountMap.get(lastWid.toPlainString()).subtract(spCount));
                        } catch (Exception e) {
                            CalcLog.log(e.getMessage());
                            CalcLog.log(new PmAutoPdLog(current.getRequestId(), execResult.getVersion() , current.getMoldId()).setAxisOrderNo(axisPmContBoardParam.getOrderNo()).setBlock1(block1).setBlock2(null)
                                    .setFormPlateMode(formPlateMode.getCode()).setLens(lens).setMessage("组板失败："+e.getMessage()));
                        }
                        doPdFlag = false;
                        pmContBoardParamsFirsts.clear();
                        pmContBoardParamsLasts.clear();
                        pmContBoardParamMap.clear();
                        break p;
                    }
                    // 取第二层最后一个合同
                    PmContBoardParam calcPmContBoardParamList = pmContBoardParamMap.get(block2.get(block2.size() - 1));
                    // 剔除最后一个小板
                    i = i.subtract(BigDecimal.ONE);
                    szLen = szLen.subtract(pmContBoardParamFirst.getSmallPlateLen());
                    allWt = allWt.subtract(pmContBoardParamFirst.getOrderUnitAim());
                    allWt = allWt.subtract(calcPmContBoardParamList.getOrderUnitAim());
                    
                    spCount = spCount.subtract(BigDecimal.ONE);
                    if(spCount.compareTo(BigDecimal.ONE) < 0) {
                        // 剩余待组板数如果小于1直接结束组板过程。
                        doPdFlag = false ;
                        pmContBoardParamsFirsts.clear();
                        pmContBoardParamsLasts.clear();
                        pmContBoardParamMap.clear();
                        if(!priorityQueue.isEmpty()) {
                            // 如果队列非空，切当前队首合同为本次轴合同，则队首出队列。不再参与本次组板
                            PmContBoardParam newPmContBoardParamFirst = priorityQueue.peek();
                            if(newPmContBoardParamFirst.getOrderNo().equals(axisPmContBoardParam.getOrderNo())) {
                                priorityQueue.poll();
                            }
                        }
                        if(!priorityQueue.isEmpty() && ! priorityQueue.peekLast().getOrderNo().equals(calcPmContBoardParamList.getOrderNo())
                                && !calcPmContBoardParamList.getOrderNo().equals(axisPmContBoardParam.getOrderNo())) {
                            // 第二层剔除一个小板，如果合同数量减少。表示合同不参与本次组板计算，需要把合同重新放回队尾。
                            priorityQueue.offerLast(calcPmContBoardParamList);
                        }
                        BigDecimal firstQty = execResult.calcRemainingPdQty(axisPmContBoardParam);
                        sameWidSpCountMap.put(axisPmContBoardParam.getOrderWid().toPlainString(), sameWidSpCountMap.get(axisPmContBoardParam.getOrderWid().toPlainString()).subtract(firstQty));
                        break p; 
                    }
                    lens.remove(spCount.intValue());
                    block1.remove(spCount.intValue());
                    block2.remove(spCount.intValue());
                    if(!priorityQueue.isEmpty() && ! priorityQueue.peekLast().getOrderNo().equals(calcPmContBoardParamList.getOrderNo())) {
                        // 第二层剔除一个小板，如果合同数量减少。表示合同不参与本次组板计算，需要把合同重新放回队尾。
                        priorityQueue.offerLast(calcPmContBoardParamList);
                    }
                    if(i.compareTo(BigDecimal.ZERO) == 0) {
                        pmContBoardParamsFirsts.remove(pmContBoardParamsFirsts.size() - 1);
                        if(priorityQueue.isEmpty()) {
                            break p;
                        }
                        PmContBoardParam queueFirst = priorityQueue.peek();
                        if(pmContBoardParamsFirsts.size() > 0) {
                            if(!pmContBoardParamFirst.getOrderNo().equals(queueFirst.getOrderNo())) {
                                // 如果组板数组只剩一个合同，最后一个合同不入队列
                                priorityQueue.offerFirst(pmContBoardParamFirst);
                            }
                        } else {
                            // 如果第一个合同不入队列，需要把它剩余未组板的数量剔除
                            BigDecimal firstQty = execResult.calcRemainingPdQty(pmContBoardParamFirst);
                            sameWidSpCountMap.put(pmContBoardParamFirst.getOrderWid().toPlainString(), sameWidSpCountMap.get(pmContBoardParamFirst.getOrderWid().toPlainString()).subtract(firstQty));
                        }
                        if(pmContBoardParamsFirsts.size() == 0) {
                            doPdFlag = false ;
                        }
                        pmContBoardParamFirst = pmContBoardParamsFirsts.get(pmContBoardParamsFirsts.size() - 1);
                        i = execResult.calcRemainingPdQty(pmContBoardParamFirst);
                    }
                } while(doPdFlag && pmContBoardParamsFirsts.size() >= 1);
                
            }
        }
        if(!this.priorityQueueCopies.isEmpty() && current.isSecondTimeFlag()) {
            // 剔除队尾
            this.priorityQueueCopies.pollLast();
            this.priorityQueue = this.priorityQueueCopies;
            sameWidSpCountMap = new HashMap<String, BigDecimal>();
            // 重新统计数据
            for (PmContBoardParam pmContBoardParam : priorityQueue) {
                BigDecimal qty = execResult.calcRemainingPdQty(pmContBoardParam);
                String key = pmContBoardParam.getOrderWid().toPlainString();
                if(sameWidSpCountMap.containsKey(key)) {
                    sameWidSpCountMap.put(key, sameWidSpCountMap.get(key).add(qty));
                } else {
                    sameWidSpCountMap.put(key, qty);
                }
            }
            this.priorityQueueCopies = new LinkedList<PmContBoardParam>(this.priorityQueue);
            this.call();
        }
        return true;
    }

}
