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.api.busi.IMesQueueOrderPushService;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesCellEquipContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesEquipVariableCollectContext;
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.mes.pcn.serviceimpl.fsm.IShippingDispatchService;
import cn.estsh.i3plus.mes.pcn.serviceimpl.fsm.IStepService;
import cn.estsh.i3plus.mes.pcn.util.StringUtil;
import cn.estsh.i3plus.mes.pcn.websocket.StationWebSocket;
import cn.estsh.i3plus.platform.common.tool.TimeTool;
import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil;
import cn.estsh.i3plus.pojo.base.enumutil.MesPcnEnumUtil;
import cn.estsh.i3plus.pojo.mes.bean.MesQueueOrderPush;
import cn.estsh.i3plus.pojo.mes.bean.MesQueueOrderPushCellCfg;
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 cn.estsh.impp.framework.boot.util.SpringContextsUtil;
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;
import java.util.stream.Collectors;

/**
 * @Description : 工位工单接收工步
 * @Author : wangjie
 **/
@Slf4j
@Service("mesWorkOrderQueueAcceptStepService")
public class MesWorkOrderQueueAcceptStepService extends BaseStepService {

    @Autowired
    private IMesProductionProcessContextStepService productionProcessContextStepService;

    @Autowired
    private IMesProductionDispatchContextStepService productionDispatchContextStepService;

    @Autowired
    private IMesProductionCustomContextStepService productionCustomContextStepService;

    @Autowired
    private IMesQueueOrderPushService queueOrderPushService;

    @Autowired
    private IFsmCommonService fsmCommonService;

    @Autowired
    private IShippingDispatchService shippingDispatchService;

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

    @Override
    public void title(StationRequestBean reqBean) {
        this.sendMessage(reqBean, new StationResultBean().resultObj(MesPcnExtConstWords.STEP_DISABLE_SCAN), String.format("工步: %s", reqBean.getStepName()), MesPcnEnumUtil.STATION_BUSI_TYPE.STEP_TITLE, MesPcnEnumUtil.STATION_DATA_TYPE.TITLE);
    }

    @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());

        //存储生产过程上下文对象
        productionProcessContextStepService.dispatchProductionProcessContext(reqBean, productionProcessContext);

        //处理排序线推单上下文, 返回推送工位类型对应的配置信息 【来源工位】
        List<MesQueueOrderPushCellCfg> queueOrderPushCellCfgList = productionProcessContextStepService.dispatchQueueOrderPushCellCfgContext(reqBean, MesExtEnumUtil.QUEUE_ORDER_CELL_PUSH_TYPE.TARGET.getValue());
        if (CollectionUtils.isEmpty(queueOrderPushCellCfgList)) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(),
                String.format("生产线[%s]工位[%s]未配置[%s]类型的生产队列推送工位配置信息!", reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), MesExtEnumUtil.QUEUE_ORDER_CELL_PUSH_TYPE.TARGET.getDescription()));

        MesCellEquipContext cellEquipContext = productionProcessContext.getCurCellEquip();

        //推送来源代码集合查询创建状态的生产队列工位推送信息
        List<MesQueueOrderPush> queueOrderPushList = queueOrderPushService.getQueueOrderPushList(reqBean.getOrganizeCode(), queueOrderPushCellCfgList);
        if (CollectionUtils.isEmpty(queueOrderPushList) || queueOrderPushList.size() < cellEquipContext.getCavity()) {
            return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog().checkRepeat(), stepResult,
                    false, MesPcnEnumUtil.STATION_BUSI_TYPE.GUIDE, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT,
                    CollectionUtils.isEmpty(queueOrderPushList) ? "当前未接收到工位工单队列,持续监听中..." : String.format("当前接收到到工位工单队列个数[%s]不满足腔数[%s],持续监听中...", queueOrderPushList.size(), cellEquipContext.getCavity()),
                    getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);
        }

        //搜集需要加锁的推送来源代码
        List<String> pushSourceCodeList2NeedLock = queueOrderPushCellCfgList.stream().filter(o -> (null != o && !StringUtils.isEmpty(o.getIsNeedLock()))).map(MesQueueOrderPushCellCfg::getPushSourceCode).collect(Collectors.toList());

        //当前即将执行的推送队列
        List<MesQueueOrderPush> curPushList = null;

        try {
            //验证是否执行加锁
            for (MesQueueOrderPush queueOrderPush : queueOrderPushList) {
                if (null == queueOrderPush || StringUtils.isEmpty(queueOrderPush.getWorkOrderNo())) continue;
                //当前遍历中的代码不考虑加锁
                if (!CollectionUtils.isEmpty(pushSourceCodeList2NeedLock) && pushSourceCodeList2NeedLock.contains(queueOrderPush.getPushSourceCode())) {
                    //当前遍历中的代码加锁;  获取不到锁的情况下退出循环
                    if (!tryLock(reqBean, queueOrderPush.getPushSourceCode())) break;
                    //获取排序线工单队列推送锁数据, 返回的值是工位代码
                    String lockedCellCode = productionCustomContextStepService.getSortQueuePushLockContext(reqBean, queueOrderPush.getId().toString());
                    //如果当前推送信息的ID已被其他工位锁定, 验证锁定的工位是否在线, 不在线直接清除当前推送信息的ID的LOCK
                    if (!StringUtils.isEmpty(lockedCellCode) && !lockedCellCode.equals(reqBean.getWorkCellCode())) {
                        if (!CollectionUtils.isEmpty(StationWebSocket.getStationWebSocketList(
                                shippingDispatchService.getActorClientInfo(reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), lockedCellCode)))) continue;
                        else productionCustomContextStepService.removeSortQueuePushLockContext(reqBean, queueOrderPush.getId().toString());
                    }

                    queueOrderPush.setIsNeedLock(CommonEnumUtil.TRUE_OR_FALSE.TRUE.getValueStr());
                }

                //ADD到当前即将执行的推送队列中
                if (CollectionUtils.isEmpty(curPushList)) curPushList = new ArrayList<>();
                curPushList.add(queueOrderPush);

                //满足腔数的情况下退出循环
                if (curPushList.size() >= cellEquipContext.getCavity()) break;
            }

            //验证是否满足腔数
            if (!CollectionUtils.isEmpty(curPushList) && curPushList.size() == cellEquipContext.getCavity()) {

                //搜集工单号
                List<String> workOrderNoList = curPushList.stream().filter(o -> null != o).map(MesQueueOrderPush::getWorkOrderNo).collect(Collectors.toList());

                //发送工步内容
                productionCustomContextStepService.sendStepContextMessage(reqBean, workOrderNoList.toString(), MesExtEnumUtil.CELL_MESSAGE_SOURCE.READ);

                List<MesEquipVariableCollectContext> equipVariableCollectContextList = new ArrayList<>();
                workOrderNoList.stream().filter(o -> !StringUtils.isEmpty(o)).forEach(o ->
                        equipVariableCollectContextList.add(new MesEquipVariableCollectContext(reqBean.getOrganizeCode(), o, MesExtEnumUtil.CELL_MESSAGE_SOURCE.READ.getValue())));
                //保存上下文扫/读信息:加工单
                productionDispatchContextStepService.dispatchScanWorkOrderNoContext(reqBean, equipVariableCollectContextList);

                this.sendMessage(reqBean, new StationResultBean().writeDbLog().checkRepeat(), String.format("获取到工位工单推送队列%s!", workOrderNoList.toString()), MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT);

                //加工单验证工步 【排序】
                stepResult = ((IStepService) SpringContextsUtil.getBean("mesWorkOrderCheckSortStepService")).executeInState(reqBean);

                if (stepResult.isCompleted()) {

                    //保存上下文推单信息
                    productionDispatchContextStepService.dispatchSortQueuePushContext(reqBean, curPushList);

                    log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- QUEUE_ORDER_PUSH:{}",
                            reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), JSONObject.toJSONString(curPushList));

                    return stepResult;
                }
            }

        } finally {
            //当需要加锁且满足腔数的情况下, 保存排序线工单队列推送锁数据
            Map<String, String> queuePushMap2Locked = CollectionUtils.isEmpty(curPushList) ? null :
                    curPushList.stream().filter(o -> (null != o && !StringUtils.isEmpty(o.getIsNeedLock()))).collect(Collectors.toMap(o -> o.getId().toString(), MesQueueOrderPush::getPushSourceCode));
            if (!CollectionUtils.isEmpty(queuePushMap2Locked) && curPushList.size() >= cellEquipContext.getCavity()) {
                //保存排序线工单队列推送锁数据【保存LOCK信息:item=id,value=工位】【保存当前工位锁定的信息:item=工位,value=idList】
                productionCustomContextStepService.dispatchSortQueuePushLockContext(reqBean, new ArrayList<>(queuePushMap2Locked.keySet()));
            }
            //解锁
            if (!CollectionUtils.isEmpty(queuePushMap2Locked)) queuePushMap2Locked.values().forEach(o -> unLock(reqBean, o));
        }

        if (!stepResult.isCompleted()) {
            stepThreadSleepAndSendTaskCompleteAndThrowEx(reqBean, stepResult, MesPcnEnumUtil.STATION_DATA_TYPE.EXP_TEXT,
                    getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);
        }

        return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog().checkRepeat(), stepResult,
                false, MesPcnEnumUtil.STATION_BUSI_TYPE.GUIDE, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT,
                CollectionUtils.isEmpty(curPushList) ? "当前未接收到工位工单队列,持续监听中..." : String.format("当前接收到到工位工单队列个数[%s]不满足腔数[%s],持续监听中...", curPushList.size(), cellEquipContext.getCavity()),
                getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);

    }

    //加锁
    private Boolean tryLock(StationRequestBean reqBean, String pushSourceCode) {
        try {
            String key = new StringJoiner(MesPcnExtConstWords.AND).add(reqBean.getWorkCenterCode()).add(pushSourceCode).toString();
            ReentrantLock lock = lockMap.computeIfAbsent(key, item -> new ReentrantLock(true));
            //500ms内拿不到锁的情况下监控锁是否已经超时
            if (!lock.tryLock(MesPcnExtConstWords.FIVE_HUNDRED, TimeUnit.MILLISECONDS) && checkTimeOut(reqBean, pushSourceCode, key, lock)) return false;

            log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- TRY_LOCK [{}] SUCCESS",
                    reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), pushSourceCode);

            lockTimeMap.put(key, TimeTool.getNowTime(true));
            return true;
        } catch (Exception e) {
            log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- TRY_LOCK [{}] EXCEPTION: {}",
                    reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), pushSourceCode, e.toString());
            return false;
        }
    }

    //监控超过10秒的情况下进行自动解锁
    private Boolean checkTimeOut(StationRequestBean reqBean, String pushSourceCode, String key, ReentrantLock lock) {
        try {
            String lockTime = lockTimeMap.get(key);
            if ((StringUtils.isEmpty(lockTime) || TimeTool.getSecoundsBetweenTime(1, lockTime, TimeTool.getNowTime(true)) >= MesPcnExtConstWords.TEN) && lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) lock.unlock();
                else lockMap.remove(key);
            }
        } catch (Exception e) {
            log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- CHECK_TIME_OUT [{}] EXCEPTION: {}",
                    reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), pushSourceCode, e.toString());
        }
        return true;
    }

    //解锁
    private void unLock(StationRequestBean reqBean, String pushSourceCode) {
        try {
            String key = new StringJoiner(MesPcnExtConstWords.AND).add(reqBean.getWorkCenterCode()).add(pushSourceCode).toString();
            lockMap.computeIfPresent(key, (k, v) -> {
                if (!v.isHeldByCurrentThread() || !v.isLocked()) return v;
                v.unlock();
                log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- UNLOCK [{}] SUCCESS",
                        reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), pushSourceCode);
                return v;
            });
        } catch (Exception e) {
            log.info("工厂{}生产线{}工位{}:FSM STATE DISPATCHER --- DO STEP --- {} EXEC --- UNLOCK [{}] EXCEPTION: {}",
                    reqBean.getOrganizeCode(), reqBean.getWorkCenterCode(), reqBean.getWorkCellCode(), StringUtil.toLowerCaseFirst(this.getClass().getSimpleName()), pushSourceCode, e.toString());
        }
    }

}
