package com.ruoyi.sim.service.impl;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.sim.config.SimConfig;
import com.ruoyi.sim.config.SimDebugConfig;
import com.ruoyi.sim.domain.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

import static com.ruoyi.sim.service.impl.CommConst.*;

/**
 * 硬件通信
 * send service.
 * "commSendService"
 */
@Service
public class CommSendService {

    private static final Logger l = LoggerFactory.getLogger(CommSendService.class);
    /**
     * 缓存的Socket连接。
     */
    private Socket cachedSocket = null;
    @Autowired
    private CommReceiveService simReceiveService;
    @Autowired
    private SimService simService;
    @Autowired
    private FaultService faultService;
    @Autowired
    private RealExamService realExamService;
    @Autowired
    private RealExamFaultService realExamFaultService;
    @Autowired
    private RealExamCollectionService realExamCollectionService;
    @Autowired
    private SimConfig config;
    @Autowired
    private CommBuildService commBuildService;

    /**
     * 定时任务。
     */
    public void scheduledLoopRead() {
        readAll();
    }

    public void readAll() {
        l.info("readAll");
        List<RealExam> listRe = realExamService.listAllByStatus(RealExam.State.ANSWERING);
        listRe.forEach(e -> {
            if (e == null) {
                return;
            }
            List<RealExamFault> listRef = realExamFaultService.listAllType2State2and3ByExamId(e.getExamId());
            listRef.forEach(ref -> {
                Sim s = simService.selectSimBySimId(e.getSimId());
                Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
                if (f != null &&
                        Fault.Type.REAL_GZBW.equals(f.getFaultType()) &&
                        Fault.State.ENABLE.equals(f.getFaultState())
                ) {
                    readOneSimOneFaultResistance(s, ref, f, null);
                }
            });
        });
    }

    /**
     * Async version.
     */
    @Async("tp-comm")
    public void readAllAsync() {
        readAll();
    }

    public void readOneExamAtLast(RealExam re) {
        l.info("readOneExamAtLast");
        List<RealExamFault> list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId());
        for (RealExamFault ref : list) {
            Sim s = simService.selectSimBySimId(re.getSimId());
            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
            readOneSimOneFaultResistance(s, ref, f, RealExamFault.State.FINISH);
        }
        // 计算扣分。
        // 最后都读取到，才算扣分。
        {
            // todo:暂时不算。
            // calculateScore(re.getExamId());
        }
    }

    public void readOneSimAtLastByDebug(Sim s) {
        l.info("readOneSimAtLastByDebug");
        List<Fault> list = faultService.listType3(s.getSimType());
        for (Fault f : list) {
            readOneSimOneFaultResistance(s, null, f, null);
        }
    }

    public AjaxResult debugReadAllFaultResistanceBySimNum(String simNum) {
        // check

        //
        // 打开socket
        {
            AjaxResult ar1 = openSocket();
            if (ar1.isError()) {
                return ar1;
            }
        }
        //
        Sim s = simService.uniqueBySimNum(simNum);
        if (s == null || !Sim.State.ONLINE.equals(s.getSimState())) {
            return AjaxResult.error("未连接维修模拟器，请检查连接！");
        }
        // todo: aj改造
        readOneSimAtLastByDebug(s);
        return AjaxResult.success("成功读取！");
    }

    @Async("tp-comm")
    public void readOneExamAtLastAsync(RealExam re) {
        l.info("readOneExamAtLastAsync");
        readOneExamAtLast(re);
    }

    /**
     * 计算减分(不包括超时)。汇总到deduction_total_score字段。
     *
     * @param realExamId
     */
    public void calculateScore(Long realExamId) {
        realExamService.updateOneState(realExamId, RealExam.State.CALCULATING_SCORE);
        int minus = 0;
        // 排除故障部分 + 维修报告选择部分
        realExamFaultService.calculateMinusByRealExamId(realExamId);
        // 超时部分
        int timeoutMinute = 0;
        //
        realExamService.updateOneState(realExamId, RealExam.State.GOT_REPORT);
    }


    /**
     * todo:????
     * 定时任务。
     */
    public void scheduledReadSim() {
        l.info("scheduledReadSim");
        if (!realExamCollectionService.existOpened()) {
            l.info("没有open的考试集合");
            return;
        }
        readAll();
        //

        // AA010102010000000055

        // AA010102010000000055

        String simNum = "01";
        String simType = "0002";
//        for (int i = 0; i < 3; i++) {
//            debugReadSimType(simNum);
//        }
//        debugClearAllFault(simNum, simType);
//        debugWriteAllFault(simNum, simType);
//        for (int i = 0; i < 3; i++) {
//            debugReadAllFaultResistance(simNum, simType);
//        }

        //  debugClearOneFault(simNum, "02");


        // boolean bRea = isReachable("123.112.16.165");
        // debugReadSimType(simNum);
        // l.info("bRea:" + bRea);

        l.info("cachedSocket.isConnected():" + cachedSocket.isConnected());
        l.info("cachedSocket.isBound():" + cachedSocket.isBound());
        l.info("cachedSocket.isClosed():" + cachedSocket.isClosed());
        l.info("cachedSocket.isInputShutdown():" + cachedSocket.isInputShutdown());
        l.info("cachedSocket.isOutputShutdown():" + cachedSocket.isOutputShutdown());


//            debugReadAllFaultResistance(simNum, simType);

//            debugWriteAllFault(simNum, simType);
//            for (int i = 0; i < 5; i++) {
//                debugReadAllFaultResistance(simNum, simType);
//            }


//        if (isSocketOk()) {
//            readAllAsync();
//        } else {
//            scheduledConnect();
//        }
    }

    /**
     * 连接情况 的 定时任务。
     * 执行频率: 2min debug:30s
     */
    public void scheduledConnect() {
        l.info("scheduled####Connect  连接情况 的 定时任务");
        if (!SimDebugConfig.SCHEDULED_CONNECT) {
            l.info("连接情况 的 定时任务被禁用！");
            return;
        }
        if (!realExamCollectionService.existOpened()) {
            l.info("没有open的考试集合");
            return;
        }
        if (!isReachable(config.getRouterIp())) {
            l.warn("ping RouterIp not ok");
            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
            return;
        }
        if (!isReachable(config.getRs485Ip())) {
            l.warn("ping Rs485Ip not ok");
            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
            return;
        }

        openSocket();
        if (isCachedSocketOk()) {
            checkAllSimState();
        }
    }

    public void checkOneSimState(final Sim s) {
        // check
        if (s == null) {
            return;
        }
        if (Sim.State.DISABLE.equals(s.getSimState())) {
            l.warn("sim DISABLE,模拟器被禁用,sim = {}", s);
            return;
        }
        if (StringUtils.isBlank(s.getSimType()) || StringUtils.isBlank(s.getSimNum())) {
            l.warn("sim error data {}", s);
            return;
        }
        //
        SimMsg smS = commBuildService.buildSendMsgReadSimType(s.getSimNum());
        SimMsg smR = send(smS, s, RETRY_COUNT_0, SLEEP_SHORT);
        if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
            l.info("isNotBlank");
            commFailCountClearOne(s.getSimId());
        }
        simReceiveService.checkOneSimState(smR, s);
    }

    /**
     * 查找所有没有被手动禁用，并order by sim_num的模拟器列表。检查所有模拟器状态。
     */
    public void checkAllSimState() {
        RealExamCollection ecF = realExamCollectionService.selectRealExamCollectionOpened();
        l.info("ecF.getSimType() = {}", ecF.getSimType());
        if (ecF != null) {
            List<Sim> list = simService.listAllEnableBySimType(ecF.getSimType());
            l.info("checkAllSimState list.size() = {}", list.size());
            list.forEach(this::checkOneSimState);
        }
    }

    @Async("tp-comm")
    public void checkAllSimStateAsync() {
        checkAllSimState();
    }

    /**
     * 清除一个考试的，对应的某型号一台模拟器的，所有故障部位。
     *
     * @param re
     */
    public void clearOneSimAllFaultByExam(RealExam re) {
        l.info("clearOneSimAllFaultByExam = {}", re);
        // check

        // 更新Exam状态。
        {
            realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
        }
        //
        List<RealExamFault> list = realExamFaultService.listAllType2InitStateByExamId(re.getExamId());
        if (list != null) {
            l.info("清除exam list = {}", list.size());
        }
        assert list != null;
        list
                .forEach(ref -> {
                    Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
                    if (faultService.isDisable(f.getFaultId())) {
                        l.warn("故障 {} -被禁用", f.getName());
                        throw new IllegalArgumentException("故障被禁用");
                    }
                    Sim s = simService.selectSimBySimId(re.getSimId());
                    // check

                    //
                    clearOneSimOneFault(s, ref, f);
                });
    }

    public void clearOneSimAllFaultBySim(Sim s) {
        l.info("clearOneSimAllFaultBySim = {}", s);
        faultService.listType3EnableBySimType(s.getSimType())
                .forEach(f -> {
                    clearOneSimOneFault(s, null, f);
                });
    }

    /**
     * Async version.
     *
     * @param re
     */
    @Async("tp-comm")
    public void clearListFaultByRealExamAsync(RealExam re) {
        clearOneSimAllFaultByExam(re);
    }

    public void clearAll() {
        // todo:
        simService.listAllEnable().forEach(s -> {
            String simType = s.getSimType();
            List<Fault> listF = faultService.listType3EnableBySimType(simType);
            listF.forEach(f -> {
                clearOneSimOneFault(s, null, f);
            });
        });
    }

    /**
     * debug读取模拟器类型序列号
     *
     * @param simNum
     * @return
     */
    public SimMsg debugReadSimType(final String simNum) {
        SimMsg sm = commBuildService.buildSendMsgReadSimType(simNum);
        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
    }

    /**
     * debug清除一个故障
     *
     * @param simNum
     * @param bindHardwareMsg
     * @return
     */
    public SimMsg debugClearOneFault(final String simNum, final String bindHardwareMsg) {
        SimMsg sm = commBuildService.buildSendMsgClearFault(simNum, bindHardwareMsg);
        return send(sm, null, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
    }

    public AjaxResult debugClearAllOnlineSimAllFault() {
        simService.listAllOnline().forEach(s -> {
            AjaxResult ar = debugClearAllFaultBySimNum(s.getSimNum());
        });
        return AjaxResult.success("清除成功，清除所有在线模拟器所有故障！");
    }

    /**
     * debug清除所有故障
     *
     * @param simNum
     * @return
     */
    public AjaxResult debugClearAllFaultBySimNum(final String simNum) {
        Sim s = simService.uniqueBySimNum(simNum);
        if (s == null) {
            return AjaxResult.error("清除失败，对应simNum模拟器不存在！");
        }
        if (!Sim.State.ONLINE.equals(s.getSimState())) {
            return AjaxResult.error("清除失败，模拟器尚未在线！");
        }
        List<SimMsg> list = new ArrayList<>();
        for (String b : getGZBWBySimType(s.getSimType())) {
            SimMsg sm = debugClearOneFault(simNum, b);
            list.add(sm);
            if (sm != null && !sm.getOk()) {

            }
        }
        return AjaxResult.success("清除成功，清除当前模拟器所有故障！");
    }

    /**
     * @param s
     * @param reF 可以为空，表示不关联考试的，单独执行的。调试模式下为空。
     * @param f
     */
    public void clearOneSimOneFault(Sim s, RealExamFault reF, Fault f) {
        l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}",
                s.getSimNum(), f.getBindHardwareMsg(), f.getName());
        // check todo:

        // step1
        SimMsg smS = commBuildService.buildSendMsgClearFault(s.getSimNum(), f.getBindHardwareMsg());
        SimMsg smR = send(smS, s, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
        if (reF != null) {
            simReceiveService.clearOneFault(smR, s, reF, f);
        } else {
            l.info("reF == null");
        }

        // step2

        // 下发故障独立运行。下面屏蔽。

//        if (reF != null &&
//                realExamFaultService.isState(reF.getRefId(), RealExamFault.State.CLEARED)) {
//            if (reF.getFlag().equals(RealExamFault.Flag.YES)) {
//                writeOneFault(s, reF, f);
//            } else if (reF.getFlag().equals(RealExamFault.Flag.NO)) {
//                RealExamFault f1 = realExamFaultService.selectRealExamFaultByRefId(reF.getRefId());
//                f1.setRefState(RealExamFault.State.LOOP_READ);
//            }
//        }
    }

    /**
     * debug下发一个故障
     *
     * @param simNum
     * @param bindHardwareMsg
     * @return
     * @throws IOException
     */
    public SimMsg debugWriteOneFault(final String simNum, final String bindHardwareMsg) {
        SimMsg sm = commBuildService.buildSendMsgWriteFault(simNum, bindHardwareMsg);
        return send(sm, null, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
    }

    /**
     * debug下发所有故障
     *
     * @param simNum
     * @return
     * @throws IOException
     */
    public List<SimMsg> debugWriteAllFault(final String simNum) {
        List<SimMsg> list = new ArrayList<>();
        String simType = simService.uniqueBySimNum(simNum).getSimType();
        for (String b : getGZBWBySimType(simType)) {
            list.add(debugWriteOneFault(simNum, b));
        }
        return list;
    }

    /**
     * todo:尚未实现
     * 实现方式类似 studentStartRealExam方法。
     *
     * @param simNum
     * @param faultIds
     * @param checkReplace 是否进行可换件检查
     * @return
     */
    public AjaxResult debugWriteSelectedFaultBySimNum(final String simNum,
                                                      final String[] faultIds,
                                                      final Boolean checkReplace) {
        // check faultIds 有效性

        //
        Sim s = simService.uniqueBySimNum(simNum);
        // check sim
        // 打开socket
        {
            AjaxResult ar1 = openSocket();
            if (ar1.isError()) {
                return ar1;
            }
        }
        // Step 2
        // msg判断，是否含有"故障部位"字符串
        {
            if (checkReplace) {

                AjaxResult arE2 = readOneSimAllFaultCheck(s);
                if (arE2.isError()) {
                    return arE2;
                }
            }
        }
        // Step 3 清除对应一台模拟器 所有故障部位故障。
        {
            clearOneSimAllFaultBySim(s);
        }
        // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。
        {
            Fault[] faults = new Fault[faultIds.length];
            for (int i = 0; i < faultIds.length; i++) {
                faults[i] = faultService.selectFaultByFaultId(faultIds[i]);
            }
            writeOneSimAllSelectFaultByDebug(s, faults);
        }
        // Step 5 读取
        {
            readOneSimAllFaultFirstTimeBySim(s);
        }
        return AjaxResult.success("下发故障流程执行成功!");
    }

    private String[] getGZBWBySimType(String simType) {
        if (StringUtils.isBlank(simType)) {
            return new String[0];
        }
        switch (simType) {
            case Sim.TYPE_0001 -> {
                return TYPE_1_BIND_MSG;
            }
            case Sim.TYPE_0002 -> {
                return TYPE_2_BIND_MSG;
            }
            case Sim.TYPE_0003 -> {
                return TYPE_3_BIND_MSG;
            }
            default -> {
                return new String[0];
            }
        }
    }

    /**
     * 下发所有选中的真实的故障部位。
     *
     * @param re
     */
    public void writeOneSimAllSelectFaultByExam(RealExam re) {
        // 更新Exam状态。
        realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
        List<RealExamFault> list = realExamFaultService.listAllType2FlagYesClearedStateByExamId(re.getExamId());
        for (RealExamFault ref : list) {
            Sim s = simService.selectSimBySimId(re.getSimId());
            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
            // 选中的才下发。
            writeOneSimOneFault(s, ref, f);
        }
    }

    public void writeOneSimAllSelectFaultByDebug(Sim s, Fault[] faults) {
        for (Fault f : faults) {
            writeOneSimOneFault(s, null, f);
        }
    }

    public void writeOneSimOneFault(Sim s, RealExamFault ref, Fault f) {
        l.info("下发故障:getSimId = {},fault.getName = {}", s.getSimId(), f.getName());
        // todo:ref is null.
        // 下发故障
        SimMsg smA1 = commBuildService.buildSendMsgWriteFault(s.getSimNum(), f.getBindHardwareMsg());
        SimMsg smA2 = send(smA1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
    }

    /**
     * 开始考试前检查读取。
     *
     * @param s
     * @return
     */
    public AjaxResult readOneSimAllFaultCheck(Sim s) {
        Fault q = new Fault();
        q.setFaultType(Fault.Type.REAL_GZBW);
        q.setSimType(s.getSimType());
        List<Fault> list = faultService.selectFaultList(q);
        List<Fault> listNG = new ArrayList<>();
        for (Fault f : list) {
            AjaxResult ar = readOneSimOneFaultCheck(s, f);
            if (ar.isError()) {
                listNG.add(f);
                l.info("故障部位[" + f.getBindHardwareMsg() + "][" + f.getReplaceName() + "]未正确安装;");
            } else {
                l.info("故障部位[" + f.getBindHardwareMsg() + "][" + f.getReplaceName() + "]安装ok;");
            }
        }
        if (listNG.isEmpty()) {
            return AjaxResult.success("所有故障部位检查没有问题。");
        }
        StringBuilder sbNG = new StringBuilder();
        for (Fault f : listNG) {
            sbNG.append("[" + f.getReplaceName() + "]零件异常;<br>");
        }
        sbNG.append("请检查后重新开始考试！");
        return AjaxResult.error(sbNG.toString());
    }


    /**
     * 检查读取。
     *
     * @param s
     * @param f
     * @return
     */
    public AjaxResult readOneSimOneFaultCheck(Sim s, Fault f) {
        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
        SimMsg sm2 = send(sm1, s, RETRY_COUNT_CHECK_ONE_FAULT, SLEEP_LONG);
        return simReceiveService.getOneFaultCheck(sm2, s, f);
    }

    /**
     * 第一次读取，作为出题值。
     *
     * @param re
     */
    public void readOneSimAllFaultFirstTimeByExam(RealExam re) {
        List<RealExamFault> list = realExamFaultService.listAllType2(re.getExamId());
        for (RealExamFault ref : list) {
            Sim s = simService.selectSimBySimId(re.getSimId());
            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
            readOneSimOneFaultFirstTime(s, ref, f);
        }
    }

    public void readOneSimAllFaultFirstTimeBySim(Sim s) {
        List<Fault> list = faultService.listType3(s.getSimType());
        for (Fault f : list) {
            readOneSimOneFaultFirstTime(s, null, f);
        }
    }

    /**
     * 第一次读取，作为出题值。
     *
     * @param s
     * @param ref debug调试模式为空。
     * @param f
     */
    public void readOneSimOneFaultFirstTime(Sim s, RealExamFault ref, Fault f) {
        // 读取一次当前电阻代表值作为出题值。
        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
        SimMsg sm2 = send(sm1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
        if (ref != null) {
            simReceiveService.setFaultQuestionValue(sm2, s, ref, f);
        } else {
            l.info("ref is null");
        }
    }

    /**
     * debug读取一个故障位置数据
     *
     * @param simNum
     * @param bindHardwareMsg
     * @return
     * @throws IOException
     */
    public SimMsg debugReadOneFaultResistance(final String simNum, final String bindHardwareMsg) {
        SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(simNum, bindHardwareMsg);
        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
    }

    /**
     * debug读取全部故障位置数据
     *
     * @param simNum
     * @return
     * @throws IOException
     */
    public List<SimMsg> debugReadAllFaultResistance(final String simNum) {
        List<SimMsg> list = new ArrayList<>();
        String simType = simService.uniqueBySimNum(simNum).getSimType();
        for (String b : getGZBWBySimType(simType)) {
            list.add(debugReadOneFaultResistance(simNum, b));
        }
        return list;
    }

    /**
     * @param s
     * @param reF
     * @param f
     * @param refState 中间轮询是null,交卷最后一次读取为finish状态。用来修改状态的。
     */
    public void readOneSimOneFaultResistance(Sim s, RealExamFault reF, Fault f, String refState) {
        l.info("readOneFaultResistance");
        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
        SimMsg sm2 = null;
        if (reF != null && refState != null) {
            if (RealExamFault.State.FINISH.equals(refState)) { // 是否最后一次读取。
                sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
            } else {
                sm2 = send(sm1, s, RETRY_COUNT_0, SLEEP_SHORT);
            }
        } else {
            sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
        }
        simReceiveService.setFaultAnswerValue(sm2, s, reF, f, refState);
    }

    /**
     * send hex message
     *
     * @param sm              发送
     * @param s               可以为空，更新最后发送/接收时间 用。
     * @param retryTotalCount 重试次数
     * @param sleep           不使用传入0
     * @return
     */
    public synchronized SimMsg send(final SimMsg sm, final Sim s, final int retryTotalCount, final long sleep) {
        try {
            if (!config.isCommGlobal()) {
                l.warn("isCommGlobal == false [模拟器通信被禁用！]");
                return sm;
            }
            if (sm == null || sm.getSendMsg() == null || StringUtils.isBlank(sm.getSendMsg())) {
                throw new IllegalArgumentException("SimMsg IllegalArgument");
            }
            if (sleep < 0) {
                throw new IllegalArgumentException("SimMsg sleep");
            }
            // log.
            {
                l.info("####SendMsg#### == [{}]", sm);
            }
            // 如果没有打开socket，顺道打开。正好后面要sleep
            openSocket();
            {
                // sleep ,追求顺序请求。
                if (sleep > 0) {
                    Thread.sleep(sleep);
                }
            }
            InputStream is = cachedSocket.getInputStream();
            OutputStream os = cachedSocket.getOutputStream();
            os.write(hexStrToByteArrs(sm.getSendMsg()));
            sm.setSendTime(DateUtils.getNowDate());
            if (s != null) {
                simService.updateLastSentTime(s);
            }
            byte[] buffer = new byte[LENGTH_20];
            int length = is.read(buffer);
            StringBuffer sbHex = new StringBuffer();
            for (int i = 0; i < length; i++) {
                sbHex.append(String.format("%02X", buffer[i]));
            }
            sm.setReceiveMsg(sbHex.toString());
            sm.setReceiveTime(DateUtils.getNowDate());
            // log.
            {
                l.info("####ReceiveMsg#### = [{}]", sm);
            }
            if (!commBuildService.checkReceiveMsg(sm.getReceiveMsg())) {
                // todo:
                l.warn("####Fail#### = {}", sm);
                return sm;
            }
            if (s != null) {
                simService.updateLastReceivedTime(s);
            }
        } catch (InterruptedException | IOException e) {   // SocketTimeoutException
            e.printStackTrace();
            // 失败计数
            boolean limit = commFailCountAdd1(Objects.requireNonNull(s).getSimId());
            if (limit) {
                simService.updateSimStateBySimId(s.getSimId(), Sim.State.OFFLINE);
                commFailCountClearOne(s.getSimId());
            }
            // 先考虑一台模拟器演示。
            // 进行重试 start
            if (sm.getRetryCount() == RETRY_COUNT_0) {
                l.warn("####RetryTotalCount不重试RETRY_COUNT_0#### = getSimMsgId = {}", sm.getSimMsgId());
                return sm;
            }
            if (sm.getRetryCount() < retryTotalCount) {
                sm.retryCountPlus1();
                send(sm, s, retryTotalCount, sleep);
                l.warn("####RetryTotalCount重试#### = {}", sm);
            } else {
                l.warn("####RetryTotalCount达到重试上限#### = {}", sm);
            }
            // 进行重试 end
        }
        return sm;
    }

    /**
     * todo:
     *
     * @param sm
     * @param s
     * @param retryTotalCount
     * @param sleep
     * @return
     */
    public SimMsg sendRetry(final SimMsg sm, final Sim s, final int retryTotalCount, final long sleep) {
        return null;
    }

    /**
     * cachedSocket is ok
     *
     * @return
     */
    public boolean isCachedSocketOk() {
        return (cachedSocket != null &&
                cachedSocket.isConnected() &&
                cachedSocket.isBound() &&
                !cachedSocket.isClosed());
    }

    /**
     * openSocket
     *
     * @return
     */
    public AjaxResult openSocket() {
        // check
        if (!config.isCommGlobal()) {
            l.warn("isCommGlobal == false [模拟器通信被禁用！]");
            return AjaxResult.error("模拟器通信被禁用！");
        }
        try {
            if (!isCachedSocketOk()) {
                l.info("openSocket cachedSocket is not ok!new socket!");
                cachedSocket = new Socket(config.getRs485Ip(), config.getRs485Port());
                cachedSocket.setSoTimeout(SOCKET_TIME_OUT);
                commFailCountClearAll();
            } else {
                l.info("openSocket cachedSocket is ok!");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return AjaxResult.error("开启连接失败！请检查物联网网关连接或配置。");
        } finally {

        }
        return AjaxResult.success("开启连接成功！");
    }

    /**
     * closeSocket
     *
     * @return
     */
    public AjaxResult closeSocket() {
        if (!config.isCommGlobal()) {
            l.warn("isCommGlobal == false [模拟器通信被禁用！]");
            return AjaxResult.error("模拟器通信被禁用！");
        }
        try {
            if (cachedSocket != null) {
                // todo:判断shutdown
                cachedSocket.getInputStream().close();
                cachedSocket.getOutputStream().close();
                cachedSocket.close();
            } else {
                l.info("cachedSocket == null!");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return AjaxResult.success("关闭连接失败！请检查物联网网关连接或配置。");
        } finally {
            cachedSocket = null;
            commFailCountClearAll();
        }
        return AjaxResult.success("关闭连接成功！");
    }

    /**
     * 等同于ping命令。
     *
     * @param ipV4
     * @return
     * @throws IOException
     */
    public boolean isReachable(String ipV4) {
        InetAddress ia = null;
        try {
            ia = InetAddress.getByName(ipV4);
            return ia.isReachable(2048);
        } catch (UnknownHostException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    private HashMap<Long, Integer> map = new HashMap<>();

    /**
     * @param simId
     * @return true超过通信失败次数限制
     */
    private boolean commFailCountAdd1(long simId) {
        l.info("map.containsKey(simId) = {}", map.containsKey(simId));
        if (map.containsKey(simId)) {
            map.put(simId, map.get(simId) + 1);
        } else {
            map.put(simId, 1);
        }
        return (map.get(simId) >= OFFLINE_LIMIT);
    }

    private void commFailCountClearOne(long simId) {
        map.remove(simId);
    }

    private void commFailCountClearAll() {
        map.clear();
    }

    /**
     * https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j/3.0.3
     */
    public void test02() {
        byte[] data = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02}; // 示例数据
        // int crc = ModbusUtils.calculateCRC(data);
        // System.out.println("CRC-16/MODBUS 校验值: " + Integer.toHexString(crc));
    }


    public static byte[] hexStrToByteArrs(String hexString) {
//        if (StringUtils.isEmpty(hexString)) {
//            return null;
//        }
        hexString = hexString.replaceAll(" ", "");
        int len = hexString.length();
        int index = 0;

        byte[] bytes = new byte[len / 2];

        while (index < len) {
            String sub = hexString.substring(index, index + 2);
            bytes[index / 2] = (byte) Integer.parseInt(sub, 16);
            index += 2;
        }
        return bytes;
    }

    public String logBytesToString(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        StringBuffer sbHex = new StringBuffer();
        byte[] buffer = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            sbHex.append(String.format("%02X", buffer[i]));
        }
        return sbHex.toString();
    }

    public static String bytesToHexV2(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b); // 将字节转换为十六进制字符串
            if (hex.length() == 1) {
                hexString.append('0'); // 如果是单个字符，补零
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
