package com.ruoyi.sim.service.impl;

import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.sim.config.SimConfig;
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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

import static com.ruoyi.sim.service.impl.CommSendService.Const.*;

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

    interface Const {

        /**
         * 报文长度
         */
        int MSG_LENGTH = 20;

        String PREFIX = "AA";

        String SUFFIX = "55";

        /**
         * orientation
         */
        String ORN_SEND = "01";

        /**
         * orientation
         */
        String ORN_RECEIVE = "02";

        String CMD_DATA_PLACE_HOLDER = "00000000";

        /**
         * 故障下发
         */
        String CMD_SET_FAULT = "01";
        /**
         * 故障清清除
         */
        String CMD_CLEAR_FAULT = "02";
        /**
         * 设备类型读取
         */
        String CMD_READ_TYPE = "03";
        /**
         * 状态读取 RESISTANCE电阻值
         */
        String CMD_READ_FAULT_RESISTANCE = "03";

        String CMD_ID_GET_SN = "B1";
        int LENGTH_2 = 2;
        int LENGTH_8 = 8;
        int LENGTH_20 = 20;
    }

    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 SimConfig config;

    /**
     *
     */
    public void init() {

    }

    /**
     * 定时任务。
     */
    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.listAllType2LoopReadStateByExamId(e.getExamId());
            listREF.forEach(ref -> {
                Sim s = simService.selectSimBySimId(e.getSimId());
                Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
                if (f != null &&
                        Fault.TYPE_3.equals(f.getFaultType()) &&
                        Fault.State.ENABLE.equals(f.getFaultState())
                ) {
                    readOneFaultResistance(s, ref, f, RealExamFault.State.LOOP_READ);
                }
            });
        });
    }

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

    public void readOneExamAtLast(RealExam re) {
        l.info("readOneExamAtLast");
        List<RealExamFault> listREF = realExamFaultService.listAllType2LoopReadStateByExamId(re.getExamId());
        listREF.forEach(ref -> {
            Sim s = simService.selectSimBySimId(re.getSimId());
            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
            readOneFaultResistance(s, ref, f, RealExamFault.State.FINISH);
        });
        // 计算扣分。
        // 最后都读取到，才算扣分。
        {
            calculateScore(re.getExamId());
        }
    }

    @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 (isSocketOk()) {
            readAllAsync();
        } else {
            scheduledConnect();
        }
    }

    /**
     * todo:????
     * 定时任务。
     */
    public void scheduledConnect() {
        l.info("scheduledConnect");
        try {
            if (!isReachable(config.getRouterIp())) {
                // ping 不通。
                l.warn("ping not ok");
                simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
            }
            if (!isReachable(config.getRs485Ip())) {
                // todo:ping 不通。
                l.warn("ping not ok");
                simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
            }
            openSocket();
        } catch (IOException e) {
            catchException(e);
        }
        if (isSocketOk()) {
            checkAllSimStateAsync();
        }
    }

    /**
     * 查找所有没有被手动禁用，并order by sim_num的模拟器列表。检查所有模拟器状态。
     */
    public void checkAllSimState() {
        List<Sim> list = simService.listAllEnable();
        list.forEach(s -> {
            checkOneSimState(s);
        });
    }

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

    public void checkOneSimState(Sim s) {
        l.info(s.toString());
        // check todo:
        if (Objects.isNull(s)) {
            return;
        }
        if (Sim.State.DISABLE.equals(s.getSimState())) {
            l.warn("sim DISABLE getSimId = {}", s.getSimId());
            return;
        }
        //
        try {
            SimMsg sm = new SimMsg();
            String sendMsg = buildSendMsgReadSimType(s.getSimNum());
            sm.setSendMsg(sendMsg);
            String receiveMsg = send(sendMsg, s);
            sm.setReceiveMsg(receiveMsg);
            simReceiveService.checkOneSimState(sm, s);
        } catch (IOException e) {
            catchException(e);
        }
    }

    /**
     * static lock
     * todo:
     */
    public static boolean clearListFaultLocked = false;

    /**
     * 清除一个考试的，对应的某型号一台模拟器的，所有设备故障。
     *
     * @param re
     */
    public void clearListFaultByRealExam(RealExam re) {
        // check
        if (Objects.isNull(re)) {

        }
        // about lock
        if (clearListFaultLocked) {
            l.warn("clearListFaultLocked = true");
            return;
        }
        //
        clearListFaultLocked = true;
        {
            realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
        }
        //
        List<RealExamFault> list = realExamFaultService.listAllType2InitStateByExamId(re.getExamId());
        list.forEach(ref -> {
            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
            if (faultService.isDisable(f.getFaultId())) {
                l.warn("故障{}-Disable", ref.getFaultId());
                return;
            }
            l.info("f.toString() = {}", f);
            Sim s = simService.selectSimBySimId(re.getSimId());
            l.info("s.toString() = {}", s);
            // check
            if (Objects.isNull(f)) {

            }
            clearOneFault(s, ref, f);
        });
        //
        clearListFaultLocked = false;
    }

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

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

    /**
     * @param s
     * @param reF 可以为空，表示不关联考试的。
     * @param f
     */
    public void clearOneFault(Sim s, RealExamFault reF, Fault f) {
        l.info("故障清除:getSimId = {},fault.getName = {}", s.getSimId(), f.getName());
        // check todo:

        //
        try {
            // step1
            SimMsg sm1 = new SimMsg();
            String sendMsg1 = buildSendMsgClearFault(s.getSimNum(), f.getBindHardwareMsg());
            sm1.setSendMsg(sendMsg1);
            String receiveMsg1 = send(sendMsg1, s);
            sm1.setReceiveMsg(receiveMsg1);
            simReceiveService.clearOneFault(sm1, s, reF, f);
            // step2
            if (reF != null && realExamFaultService.isState(reF.getRefId(), RealExamFault.State.CLEARED)) {
                writeOneFault(s, reF, f);
            }
        } catch (IOException e) {
            catchException(e);
        }
    }


    public void writeOneFault(Sim s, RealExamFault ref, Fault f) {
        l.info("下发故障:getSimId = {},fault.getName = {}", s.getSimId(), f.getName());
        try {
            // todo:ref is null.

            // 下发故障
            SimMsg sm1 = new SimMsg();
            String sendMsg1 = buildSendMsgWriteFault(s.getSimNum(), f.getBindHardwareMsg());
            sm1.setSendMsg(sendMsg1);
            String receiveMsg1 = send(sendMsg1, s);
            sm1.setReceiveMsg(receiveMsg1);
            // todo:

            // 读取一次当前电阻代表值作为出题值。
            SimMsg sm2 = new SimMsg();
            String sendMsg2 = buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
            sm2.setSendMsg(sendMsg2);
            String receiveMsg2 = send(sendMsg2, s);
            sm2.setReceiveMsg(receiveMsg2);
            simReceiveService.setFaultQuestionValue(sm2, s, ref, f);
            if (ref != null) {
                // 修改关联状态。
                RealExamFault f1 = realExamFaultService.selectRealExamFaultByRefId(ref.getRefId());
                f1.setRefState(RealExamFault.State.LOOP_READ);
                realExamFaultService.updateRealExamFault(f1);

                //
                {
                    boolean allWritten = realExamFaultService.isAllType2StateXiaFa(f1.getExamId());
                    if (allWritten) {
                        // 如果全部下发完毕 修改RealExam状态
                        l.info("allWritten id = {}", f1.getExamId());
                        realExamService.updateOneState(f1.getExamId(), RealExam.State.SIM_PREPARE_OK);
                    }
                }
            } else {
                l.info("ref is null");
            }
        } catch (IOException e) {
            catchException(e);
        }
    }


    public void readOneFaultResistance(Sim s, RealExamFault reF, Fault f, String refState) {
        l.info("readOneFaultResistance");
        try {
            SimMsg sm = new SimMsg();
            String sendMsg = buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
            sm.setSendMsg(sendMsg);
            String receiveMsg = send(sendMsg, s);
            sm.setReceiveMsg(receiveMsg);
            simReceiveService.setFaultAnswerValue(sm, s, reF, f, refState);
        } catch (IOException e) {
            catchException(e);
        }
    }

    @Async("tp-comm")
    public void readOneFaultResistanceAsync(Sim s, RealExamFault reF, Fault f, String refState) {
        l.info("readOneFaultResistanceAsync");
        readOneFaultResistance(s, reF, f, refState);
    }

    public void test() {
        try {
            {
                String sendMsg = buildSendMsgReadFaultResistance("01", "04");
                send(sendMsg, null);
            }
            {
                String sendMsg = buildSendMsgReadFaultResistance("01", "05");
                send(sendMsg, null);
            }
            {
                String sendMsg = buildSendMsgReadFaultResistance("01", "06");
                send(sendMsg, null);
            }
        } catch (IOException e) {
            catchException(e);
        }
    }

    /**
     * 设备类型读取
     *
     * @param simNum sim.sim_num
     */
    public String buildSendMsgReadSimType(final String simNum) {
        return buildSendMsg(simNum, CMD_READ_TYPE, CMD_ID_GET_SN);
    }

    /**
     * 故障下发
     *
     * @param simNum          sim.sim_num
     * @param bindHardwareMsg fault.bind_hardware_msg
     */
    public String buildSendMsgWriteFault(final String simNum, final String bindHardwareMsg) {
        return buildSendMsg(simNum, CMD_SET_FAULT, bindHardwareMsg);
    }

    /**
     * 状态读取
     *
     * @param simNum          sim.sim_num
     * @param bindHardwareMsg fault.bind_hardware_msg
     */
    public String buildSendMsgReadFaultResistance(final String simNum, final String bindHardwareMsg) {
        return buildSendMsg(simNum, CMD_READ_FAULT_RESISTANCE, bindHardwareMsg);
    }

    /**
     * 故障清清除
     *
     * @param simNum          sim.sim_num
     * @param bindHardwareMsg fault.bind_hardware_msg
     */
    public String buildSendMsgClearFault(final String simNum, final String bindHardwareMsg) {
        return buildSendMsg(simNum, CMD_CLEAR_FAULT, bindHardwareMsg);
    }

    public String buildSendMsg(final String simNum, final String cmd, final String cmdId) {
        return buildSendMsg(simNum, cmd, cmdId, CMD_DATA_PLACE_HOLDER);
    }

    public String buildSendMsg(final String simNum, final String cmd, final String cmdId, final String data) {
        if (StringUtils.isEmpty(simNum) || StringUtils.isEmpty(cmd) || StringUtils.isEmpty(cmdId) || StringUtils.isEmpty(data)) {
            throw new IllegalArgumentException("buildSendMsg isEmpty");
        }
        if (simNum.length() != LENGTH_2) {
            throw new IllegalArgumentException("buildSendMsg length error");
        }
        if (cmd.length() != LENGTH_2) {
            throw new IllegalArgumentException("buildSendMsg length error");
        }
        if (cmdId.length() != LENGTH_2) {
            throw new IllegalArgumentException("buildSendMsg length error");
        }
        if (data.length() != LENGTH_8) {
            throw new IllegalArgumentException("buildSendMsg length error");
        }
        StringBuffer m = new StringBuffer();
        m.append(PREFIX);
        m.append(simNum);
        m.append(ORN_SEND);
        m.append(cmd);
        m.append(cmdId);
        m.append(data);
        m.append(SUFFIX);
        final String mFinal = m.toString();
        if (mFinal.length() != LENGTH_20) {
            throw new IllegalArgumentException("buildSendMsg length error");
        }
        return mFinal;
    }

    /**
     * send hex message
     *
     * @param sendMsg
     * @param s       可以为空，更新最后发送/接收时间 用。
     * @return
     */
    public synchronized String send(final String sendMsg, final Sim s) throws IOException {
        if (!config.isCommGlobal()) {
            l.warn("isCommGlobal == false [模拟器通信被禁用！]");
            return "";
        }
        l.info("********sendMsg == [{}]", sendMsg);
        //
        {
            try {
                Thread.currentThread().sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        //
        String receiveMsg = null;
        if (!isSocketOk()) {
            openSocket();
            l.warn("socket not open!");
            return "";
        }
        InputStream is = cachedSocket.getInputStream();
        OutputStream os = cachedSocket.getOutputStream();
        os.write(hexStrToByteArrs(sendMsg));
        if (s != null) {
            simService.updateLastSentTime(s);
        }
        byte[] buffer = new byte[1024];
        int length = is.read(buffer);
        StringBuffer sbHex = new StringBuffer();
        for (int i = 0; i < length; i++) {
            sbHex.append(String.format("%02X", buffer[i]));
        }
        receiveMsg = sbHex.toString();
        l.info("********receiveMsg = [{}]", receiveMsg);
        if (!checkReceiveMsg(receiveMsg)) {
            // todo:
            l.warn("checkReceiveMsg fail receiveMsg = {}", receiveMsg);
            return "";
        }
        if (s != null) {
            simService.updateLastReceivedTime(s);
        }
        return receiveMsg;
    }

    public void openSocket() throws IOException {
        l.info(config.toString());
        if (!config.isCommGlobal()) {
            l.warn("isCommGlobal == false [模拟器通信被禁用！]");
            return;
        }
        if (!isSocketOk()) {
            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
            l.info("openSocket cachedSocket is null");
            cachedSocket = new Socket(config.getRs485Ip(), config.getRs485Port());
            // setSoTimeout todo:????
            // cachedSocket.setSoTimeout(2000);
        } else {
            l.info("openSocket cachedSocket is ok");
        }
    }

    public boolean isSocketOk() {
        if (cachedSocket != null && cachedSocket.isConnected()) {
            return true;
        }
        return false;
    }

    /**
     * todo:异常精细处理
     *
     * @throws IOException
     */
    public void closeSocket() throws IOException {
        if (cachedSocket != null) {
            cachedSocket.close();
        }
        cachedSocket = null;
    }

    public 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;
    }

    /**
     * ping
     *
     * @param ipV4
     * @return
     * @throws IOException
     */
    public boolean isReachable(String ipV4) throws IOException {
        InetAddress ia = InetAddress.getByName(ipV4);
        return ia.isReachable(2000);
    }

    /**
     * check receiveMsg
     *
     * @param receiveMsg
     * @return
     */
    public boolean checkReceiveMsg(String receiveMsg) {
        if (StringUtils.isEmpty(receiveMsg)) {
            return false;
        }
        if (receiveMsg.length() != CommSendService.Const.LENGTH_20) {
            return false;
        }
        final String orn = StringUtils.substring(receiveMsg, 4, 6);
        if (!CommSendService.Const.ORN_RECEIVE.equals(orn)) {
            return false;
        }
        if (!StringUtils.startsWith(receiveMsg, CommSendService.Const.PREFIX)) {
            return false;
        }
        if (!StringUtils.endsWith(receiveMsg, CommSendService.Const.SUFFIX)) {
            return false;
        }
        return true;
    }

    private void catchException(IOException e) {
        l.warn("catchException", e);

        // e.printStackTrace();
        // throw new RuntimeException(e);
        {
            clearListFaultLocked = false;
        }
        try {
            closeSocket();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}
