package cn.estsh.i3plus.ext.mes.pcn.apiservice.serviceimpl.step;

import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionCustomContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionDispatchContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionProcessContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesContainerPackageDetailContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesProdRuleContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesProductionAssemblyContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesProductionProcessContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.util.MesPcnExtConstWords;
import cn.estsh.i3plus.mes.pcn.actor.shipping.dispatch.IFsmCommonService;
import cn.estsh.i3plus.mes.pcn.serviceimpl.fsm.BaseStepService;
import cn.estsh.i3plus.platform.common.tool.MathOperation;
import cn.estsh.i3plus.pojo.base.enumutil.MesPcnEnumUtil;
import cn.estsh.i3plus.pojo.mes.bean.MesStation;
import cn.estsh.i3plus.pojo.mes.bean.MesWorkCenter;
import cn.estsh.i3plus.pojo.mes.model.StationRequestBean;
import cn.estsh.i3plus.pojo.mes.model.StationResultBean;
import cn.estsh.i3plus.pojo.mes.model.StepResult;
import cn.estsh.i3plus.pojo.mes.util.MesExtEnumUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description : 站点扣减装配件工步
 * @Author : wangjie
 **/
@Slf4j
@Service("mesStationDeductionAssemblyStepService")
public class MesStationDeductionAssemblyStepService extends BaseStepService {

    @Autowired
    private IMesProductionProcessContextStepService productionProcessContextStepService;

    @Autowired
    private IMesProductionDispatchContextStepService productionDispatchContextStepService;

    @Autowired
    private IMesProductionCustomContextStepService productionCustomContextStepService;

    @Autowired
    private IFsmCommonService fsmCommonService;

    private final static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();

    @Override
    public StepResult init(StationRequestBean reqBean) {

        StepResult stepResult = StepResult.getSuccessComplete();

        String endlessLoopReadTimes = fsmCommonService.handleFsmWcpcMapDataForDoScanThenBackValue(reqBean, MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES);
        if (StringUtils.isEmpty(endlessLoopReadTimes)) endlessLoopReadTimes = MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES_DEFAULT;
        if (productionDispatchContextStepService.dispatchOverEndlessLoopReadTimes(reqBean, endlessLoopReadTimes)) {
            stepThreadSleepAndSendTaskCompleteAndThrowEx(reqBean, new StationResultBean().isWs(false).writeDbLog().checkRepeat(),
                    stepResult.isCompleted(false).msg(String.format("当前未能执行完成站点扣减装配件超过[%s]次!", endlessLoopReadTimes)),
                    MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);
        }

        //发送工步内容
        productionCustomContextStepService.sendStepContextMessage(reqBean);

        return stepResult;

    }

    @Override
    public StepResult execute(StationRequestBean reqBean) {

        StationResultBean resultBean = new StationResultBean();

        StepResult stepResult = StepResult.getSuccessComplete();

        //获取工位当前设备信息
        MesProductionProcessContext productionProcessContext = productionProcessContextStepService.dispatchCurCellEquipment(reqBean);

        //配置错误 抛出异常
        if (!productionProcessContext.getSuccess()) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), productionProcessContext.getMessage());

        //获取上下文产品加工规则数据信息集合
        List<MesProdRuleContext> prodRuleContextList = productionDispatchContextStepService.getProdRuleDataContext(reqBean);
        if (CollectionUtils.isEmpty(prodRuleContextList)) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), "当前不存在产品加工规则信息,请重置工序解决!");

        //验证是否存在装配件容器匹配
        Optional<MesProdRuleContext> optional = prodRuleContextList.stream().filter(o -> (null != o && !StringUtils.isEmpty(o.getIsMatchContainer()))).findFirst();
        if (null == optional || !optional.isPresent()) {
            return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, true,
                    MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "验证每腔不存在容器匹配的装配件,默认跳过站点扣减装配件!");
        }

        //获取站点用于扣料业务中进行LOCK
        Map<String, MesStation> stationMap2Lock = productionDispatchContextStepService.getLockStationContext(reqBean);
        List<String> stationNameList = CollectionUtils.isEmpty(stationMap2Lock) ? null : new ArrayList<>(stationMap2Lock.keySet());
        if (CollectionUtils.isEmpty(stationNameList)) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), "当前未获取到缓存的扣料站点信息,请重置工序解决!");

        MesWorkCenter workCenter = productionProcessContext.getWorkCenter();

        List<ReentrantLock> acquiredLocks = new ArrayList<>();
        try {
            if (!tryLock(acquiredLocks, stationNameList)) {
                return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog().checkRepeat(), stepResult, false,
                        MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "站点扣减装配件扣减失败,即将重试!");
            }

            //用于重置原料条码的缓存库存
            Map<String, MesContainerPackageDetailContext> remainQtyMap2Cache = new HashMap<>();

            //执行容器装配件的缓存扣减
            dispatchStationDeductionAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContextList, remainQtyMap2Cache);

            if (!stepResult.isCompleted()) return stepNonCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult.nextTriggerEvent(MesPcnExtConstWords.NEXT_TRIGGER_EVENT_FEEDING), stepResult.getMsg());

            //保存上下文产品加工规则信息集合
            productionDispatchContextStepService.dispatchProdRuleDataContext(reqBean, prodRuleContextList);

            //重置原料条码的缓存库存
            productionCustomContextStepService.dispatchContainerPackageDetailContext(reqBean.getOrganizeCode(), remainQtyMap2Cache);


        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog().checkRepeat(), stepResult, false,
                    MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "站点扣减装配件扣减异常,即将重试!");
        } finally {
            unLockAll(acquiredLocks);
        }

        return stepSuccessCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, "站点扣减装配件验证成功!");

    }

    //执行容器装配件的缓存扣减
    private void dispatchStationDeductionAssembly(StationRequestBean reqBean, StationResultBean resultBean, StepResult stepResult, MesWorkCenter workCenter,
                                                  List<MesProdRuleContext> prodRuleContextList, Map<String, MesContainerPackageDetailContext> remainQtyMap2Cache) {

        for (MesProdRuleContext prodRuleContext : prodRuleContextList) {
            if (null == prodRuleContext || StringUtils.isEmpty(prodRuleContext.getIsMatchContainer())) continue;
            List<MesProductionAssemblyContext> productionAssemblyContextList = dispatchStationDeductionAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContext, remainQtyMap2Cache);
            if (!stepResult.isCompleted()) break;
            prodRuleContext.assemblyDataJson(productionAssemblyContextList);
        }

    }

    //执行容器装配件的缓存扣减
    private List<MesProductionAssemblyContext> dispatchStationDeductionAssembly(StationRequestBean reqBean, StationResultBean resultBean, StepResult stepResult, MesWorkCenter workCenter,
                                                                                MesProdRuleContext prodRuleContext, Map<String, MesContainerPackageDetailContext> remainQtyMap2Cache) {

        List<MesProductionAssemblyContext> productionAssemblyContextList = prodRuleContext.getAssemblyDataContext(workCenter);
        for (MesProductionAssemblyContext productionAssemblyContext : productionAssemblyContextList) {
            if (null == productionAssemblyContext || productionAssemblyContext.getMatchType().compareTo(MesExtEnumUtil.ASSEMBLY_MATCH_TYPE.MATCH_TYPE_80.getValue()) != 0) continue;

            //未匹配的工序用量
            Double unMatchQty = MathOperation.mul(productionAssemblyContext.getQty(), prodRuleContext.getEachCavityQty());
            //扣料原料条码信息
            List<MesContainerPackageDetailContext> deductionContextList = null;
            //用于给装配件清单赋值
            List<String> barCodeList = null;
            //解析前面预先匹配的能够进行扣减的原料条码
            List<MesContainerPackageDetailContext> containerSnDataList = JSONObject.parseArray(productionAssemblyContext.getContainerSnData(), MesContainerPackageDetailContext.class);

            for (MesContainerPackageDetailContext containerPackageDetailContext : containerSnDataList) {
                if (StringUtils.isEmpty(containerPackageDetailContext)) continue;

                //获取缓存中的原料剩余数量,用于缓存扣减
                Double remainQtyCache;
                if (!remainQtyMap2Cache.containsKey(containerPackageDetailContext.getRemainKey())) {
                    remainQtyCache = productionCustomContextStepService.getContainerPackageDetailRemainQty(reqBean.getOrganizeCode(), containerPackageDetailContext.getRemainKey());
                    remainQtyMap2Cache.put(containerPackageDetailContext.getRemainKey(), new MesContainerPackageDetailContext(containerPackageDetailContext).remainQtyCached(remainQtyCache).remainQty(remainQtyCache));
                } else {
                    remainQtyCache = remainQtyMap2Cache.get(containerPackageDetailContext.getRemainKey()).getRemainQty();
                }

                //当前原料条码已经扣减完成
                if (MathOperation.compareTo(remainQtyCache, new Double(0)) <= 0) continue;

                //剩余未匹配的工序用量
                Double remainQty = MathOperation.sub(remainQtyCache, unMatchQty);
                //全部匹配完成
                if (MathOperation.compareTo(remainQty, new Double(0)) >= 0) {
                    remainQtyMap2Cache.get(containerPackageDetailContext.getRemainKey()).remainQty(remainQty);
                    unMatchQty = new Double(0);
                } else {
                    //未匹配完成, 0-负数=正数
                    remainQtyMap2Cache.get(containerPackageDetailContext.getRemainKey()).remainQty(new Double(0));
                    unMatchQty = MathOperation.sub(new Double(0), remainQty);
                }

                if (CollectionUtils.isEmpty(deductionContextList)) {
                    deductionContextList = new ArrayList<>();
                    barCodeList = new ArrayList<>();
                }
                deductionContextList.add(new MesContainerPackageDetailContext(containerPackageDetailContext)
                        .remainQty(remainQtyMap2Cache.get(containerPackageDetailContext.getRemainKey()).getRemainQty())
                        .deductionQty(MathOperation.sub(remainQtyCache, remainQtyMap2Cache.get(containerPackageDetailContext.getRemainKey()).getRemainQty()))
                );
                barCodeList.add(containerPackageDetailContext.getBarCode());

                //如果存在未匹配的工序用量
                if (MathOperation.compareTo(unMatchQty, new Double(0)) <= 0) break;

            }

            if (MathOperation.compareTo(unMatchQty, new Double(0)) > 0) {
                if (!StringUtils.isEmpty(productionAssemblyContext.getAssemblySn())) {
                    productionAssemblyContext.setAssemblyStatus(MesExtEnumUtil.ASSEMBLY_STATUS.ASSEMBLY_STATUS_30.getValue());
                    productionAssemblyContext.setAssemblySn(null);
                    productionAssemblyContext.setMatchDatetime(null);
                }
                stepResult.isCompleted(false).msg(String.format("站点扣减装配件时验证零件号[%s]当前缺料,请扫描上料条码!", productionAssemblyContext.getAssemblyPartNo()));
                break;
            } else {
                productionAssemblyContext.setAssemblySn(barCodeList.get(0));
                productionAssemblyContext.setContainerSnData(JSONObject.toJSONString(deductionContextList));
            }

        }

        return productionAssemblyContextList;
    }


    private Boolean tryLock(List<ReentrantLock> acquiredLocks, List<String> stationNameList) throws InterruptedException {

        Collections.sort(stationNameList);

        long remaining = TimeUnit.MILLISECONDS.toNanos(3000);
        long endTime = System.nanoTime() + remaining;

        for (String stationName : stationNameList) {

            if (StringUtils.isEmpty(stationName)) continue;

            ReentrantLock lock = lockMap.computeIfAbsent(stationName, o -> new ReentrantLock());

            if (!lock.tryLock(remaining, TimeUnit.NANOSECONDS)) return false;

            acquiredLocks.add(lock);

            remaining = endTime - System.nanoTime();
        }

        return true;

    }

    private void unLockAll(List<ReentrantLock> acquiredLocks) {
        for (int i = acquiredLocks.size() - 1; i >= 0; i --) acquiredLocks.get(i).unlock();
    }

}
