package com.mvc.netty.xmodem;

import com.mvc.constant.DeviceUpgradeStatus;
import com.mvc.constant.PathConstant;
import com.mvc.model.upgrade.TUpgradePlanDetailModel;
import com.mvc.netty.FzBaowenModel;
import com.mvc.netty.dealconstant.ResourceTag;
import com.mvc.service.upgrade.TUpgradePlanDetailService;
import com.mvc.utils.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.*;

@Service
@Transactional
public class XModemService {
    private static Logger log = LoggerFactory.getLogger(XModemService.class);

    private static final byte FILL_BYTE = 0x1A;//不足FRAME_SIZE的填充字节
    private static final int PACKET_HEADER_SIZE = 3;//头部字节数(数据段前的)
    private static final int CRC_SIZE = 2;//CRC字节数

    /**
     * 由于MCU-NB接收问题,将XModem数据段的长度1024改为总长为1024
     */
    private static final int FRAME_SIZE = 512 - PACKET_HEADER_SIZE - CRC_SIZE;//指定XModel中数据段长度是128还是1024(当时项目使用的是Xmodem-1k)

    @Resource
    private Environment env;

    @Resource
    private TUpgradePlanDetailService upgradePlanDetailService;

    /**
     * 解析XModem数据
     *
     * @param ctx
     * @param dataArr
     */
    public void analysisXModemData(ChannelHandlerContext ctx, byte[] dataArr) {
        log.info("收到XModem指令：" + NumberUtil.bytesToHexStr(dataArr));
        ControlChar controlChar = readControlChar(dataArr);
        if (controlChar == null) {//收到的不是控制字符
            log.info("[" + ctx.channel().remoteAddress().toString() + "]收到非控制字符");
        } else {//收到的是控制字符
            responseCtlChar(ctx, controlChar);
        }
    }

    /**
     * 响应来自设备的控制指令
     *
     * @param ctx
     * @param controlChar
     */
    private void responseCtlChar(ChannelHandlerContext ctx, ControlChar controlChar) {
        HashMap<Integer, XModemTransferModel> localMap = XModemTransferModel.inheritableThreadLocal.get();
        XModemTransferModel model = localMap.get(ctx.hashCode());

        switch (controlChar) {
            case CTL_C:
                handleCTLC(ctx, localMap, model);
                break;
            case ACK:
                handleACK(ctx, localMap, model);
                break;
            case NAK:
                handleNAK(ctx, localMap, model);
                break;
            case CAN:
                handleCAN(ctx, localMap, model);
                break;
        }
    }

    /**
     * 处理C指令(首次请求)
     *
     * @param ctx
     * @param localMap
     * @param model
     */
    private void handleCTLC(ChannelHandlerContext ctx, HashMap<Integer, XModemTransferModel> localMap, XModemTransferModel model) {
        //只要收到C指令就计次,防止后面出错后,使设备无限升级 2024-03-14 guozhao
        updateUpgradeNum(ctx);
        if (model == null) {
            initTransferModel(ctx, localMap);
        } else {
            writeInfoLog(ctx, "控制指令顺序异常");
            model.setLastRevTime(new Date());
            model.setReissueNum(0);
        }
    }

    /**
     * 处理ACK指令(正常应答)
     *
     * @param ctx
     * @param model
     */
    private void handleACK(ChannelHandlerContext ctx, HashMap<Integer, XModemTransferModel> localMap, XModemTransferModel model) {
        writeInfoLog(ctx, "请求应答正常");
        if (model == null) {
            writeInfoLog(ctx, "控制指令顺序异常");
            return;
        }

        if (model.isSendOver()) {
            writeInfoLog(ctx, "流程结束后又收到应答");
            return;
        }
        if (model.getPackIndex() == model.getPackCount()) {
            if (model.isSendEot()) {//发送结束标记后,又收到了正常应答
                writeInfoLog(ctx, "EOT后收到ACK,发送流程正常完成");
                updateUpgradePlanDetailNotes(ctx,"已完整接收");
                printEndInfo(ctx, model);
                model.setSendOver(true);
                localMap.remove(ctx.hashCode());
                sendingCompleted(ctx.hashCode());
            } else {
                sendControlChar(ControlChar.EOT, ctx);
                model.setSendEot(true);
            }
        } else {
            model.setPackIndex(model.getPackIndex() + 1);
            model.setLastRevTime(new Date());
            sendContent(model, ctx);
        }
        model.setReissueNum(0);
    }

    private void printEndInfo(ChannelHandlerContext ctx, XModemTransferModel model) {
        StringBuilder sb = new StringBuilder();
        sb.append("升级包发送完成:[");
        sb.append(StringUtils.isEmpty(model.getDeviceSn()) ? "--" : model.getDeviceSn());
        sb.append("]总包数:");
        sb.append(model.getPackCount());
        sb.append(";传输时长:");
        sb.append(DateUtil.getSencondsbetweenTime(model.getCreateTime(), new Date()));
        writeInfoLog(ctx, sb.toString());
    }

    /**
     * 处理NAK指令(重发)
     *
     * @param ctx
     * @param localMap
     * @param model
     */
    private void handleNAK(ChannelHandlerContext ctx, HashMap<Integer, XModemTransferModel> localMap, XModemTransferModel model) {
        if (model == null) {//未能正常初始化 XModemTransferModel 可能文件不存在了
            writeInfoLog(ctx, "XModemTransferModel is null");
            return;
        }
        if (model.isSendOver()) {
            writeInfoLog(ctx, "流程结束后又收到应答");
            return;
        }
        writeInfoLog(ctx, "应答异常,请求重发");
        if (model == null) {//先收到NAK,认为是请求接收,初始化对象
            initTransferModel(ctx, localMap);
            return;
        }
        if (model.getPackIndex() == model.getPackCount()) {
            if (model.isSendEot()) {//发送结束标记后,又收到了正常应答
                sendControlChar(ControlChar.EOT, ctx);
            } else {
                model.setPackIndex(model.getPackIndex());
                model.setLastRevTime(new Date());
                sendContent(model, ctx);
            }
        } else {
            model.setPackIndex(model.getPackIndex());
            model.setLastRevTime(new Date());
            sendContent(model, ctx);
        }
        model.setReissueNum(0);
    }

    /**
     * 处理CAN指令(取消接收)
     *
     * @param ctx
     * @param localMap
     * @param model
     */
    private void handleCAN(ChannelHandlerContext ctx, HashMap<Integer, XModemTransferModel> localMap, XModemTransferModel model) {
        writeInfoLog(ctx, "终止接收");
        if (model == null) {
            writeInfoLog(ctx, "控制指令顺序异常");
        } else {
            //完善升级状态的记录 2024-11-06 guozhao
            updateUpgradePlanDetailStatus(ctx,DeviceUpgradeStatus.DEVICE_CANCEL);

            localMap.remove(ctx.hashCode());
            sendingCompleted(ctx.hashCode());
            ctx.channel().close();
        }
    }

    /**
     * 发送完成后执行的(包含终止)
     *
     * @param hashCode
     */
    private void sendingCompleted(Object hashCode) {
        //按逻辑,升级是最后一步,MessagePartModel可以从队列中移除了
        HashMap<Integer, FzBaowenModel> localMap = ThreadLocalUtils.threadLocal.get();
        localMap.remove(hashCode);
    }

    /**
     * 初始化传输过程记录(支持CRC16和NAK作为接收请求的开始)
     *
     * @param ctx
     * @param localMap
     */
    private void initTransferModel(ChannelHandlerContext ctx, HashMap<Integer, XModemTransferModel> localMap) {
        Date now = new Date();
        XModemTransferModel model;
        writeInfoLog(ctx, "请求接收文件");

        HashMap<Integer, FzBaowenModel> fzBaowenModelHashMap = ThreadLocalUtils.threadLocal.get();
        FzBaowenModel partModel = fzBaowenModelHashMap.get(ctx.hashCode());

        model = new XModemTransferModel();
        //model.setFileStream(readFileStream(ctx.hashCode()));
        model.setFileStream(readFileStream(partModel));
        if (model.getFileStream() == null) {
            writeInfoLog(ctx, "升级文件不存在");
            return;
        }
        model.setPackIndex(1);
        model.setChannelHandlerContext(ctx);
        int packCount = model.getFileStream().length / FRAME_SIZE;
        if (packCount * FRAME_SIZE < model.getFileStream().length) {
            packCount++;
        }
        model.setPackCount(packCount);
        model.setLastRevTime(now);
        model.setCreateTime(now);
        model.setDeviceSn(partModel.getSn());
        localMap.put(ctx.hashCode(), model);
        sendContent(model, ctx);
    }

    /**
     * 发送内容（文件）
     *
     * @param model
     * @param ctx
     */
    private void sendContent(XModemTransferModel model, ChannelHandlerContext ctx) {
        byte[] buffer = packagingData(model);
        ByteBuffer writeBuffer = ByteBuffer.allocate(buffer.length);
        writeBuffer.put(buffer);
        writeBuffer.flip();
        writeBuffer.rewind();
        ByteBuf buff = Unpooled.copiedBuffer(writeBuffer);
        ctx.writeAndFlush(buff);
        writeBuffer.clear();
    }

    /**
     * 发送控制指令
     *
     * @param controlChar
     * @param ctx
     */
    private void sendControlChar(ControlChar controlChar, ChannelHandlerContext ctx) {
        byte[] buffer = new byte[1];
        buffer[0] = controlChar.getValue();
        sendDataToTerminal(buffer, ctx);
        String remote = ctx.channel().remoteAddress().toString();
        log.info("[" + remote + "]发送指令[" + controlChar.getAscii() + "]");
    }

    /**
     * 发送数据到终端
     *
     * @param buffer
     * @param ctx
     */
    private void sendDataToTerminal(byte[] buffer, ChannelHandlerContext ctx) {
        ByteBuffer writeBuffer = ByteBuffer.allocate(buffer.length);
        writeBuffer.put(buffer);
        writeBuffer.flip();
        writeBuffer.rewind();
        ByteBuf buff = Unpooled.copiedBuffer(writeBuffer);
        ctx.writeAndFlush(buff);
        writeBuffer.clear();
    }

    /**
     * 获取文件流
     *
     * @param
     * @return
     */
    /*private byte[] readFileStream(Object hashCode) {
        HashMap<Integer, FzBaowenModel> localMap = ThreadLocalUtils.threadLocal.get();
        FzBaowenModel partModel = localMap.get(hashCode);
        if (partModel != null && partModel.getUpgradePlanModel() != null) {
            String filePath = env.getProperty(ResourceTag.DownloadPathTag.getTag()) + PathConstant.DIR_UPGRADE_PACKAGE + PathConstant.DIR_UPGRADE_PACKAGE_MCU;
            filePath = filePath + partModel.getUpgradePlanModel().getPackageModel().getFileName();
            log.info("升级文件路径[" + filePath + "]");
            ////filePath = "E:/mcu/test.bin";//测试文件
            try {
                byte[] fileBuffer = FileUtil.readBytesFile(filePath);
                return fileBuffer;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }*/
    private byte[] readFileStream(FzBaowenModel partModel) {
        if (partModel != null && partModel.getUpgradePlanModel() != null) {
            String filePath = env.getProperty(ResourceTag.DownloadPathTag.getTag()) + PathConstant.DIR_UPGRADE_PACKAGE + PathConstant.DIR_UPGRADE_PACKAGE_MCU;
            filePath = filePath + partModel.getUpgradePlanModel().getPackageModel().getFileName();
            log.info("升级文件路径[" + filePath + "]");
            ////filePath = "E:/mcu/test.bin";//测试文件
            try {
                byte[] fileBuffer = FileUtil.readBytesFile(filePath);
                return fileBuffer;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 读取控制字符
     *
     * @param dataArr
     * @return
     */
    public ControlChar readControlChar(byte[] dataArr) {
        return dataArr.length == 1 ? ControlChar.getEnumByValue(dataArr[0]) : null;
    }

    /**
     * 数据组包
     *
     * @param model
     * @return
     */
    private byte[] packagingData(XModemTransferModel model) {
        //头标志*1 + 包序列*1 + 包序列反码*1 + 包数据(128/1024) + CRC16*2     --    (固定长度5)
        //头标志与包数据的长度要匹配
        byte[] source = model.getFileStream();
        int index = model.getPackIndex();

        int size = FRAME_SIZE + PACKET_HEADER_SIZE + CRC_SIZE;
        byte[] buffer = new byte[size];
        buffer[0] = FRAME_SIZE == 128 ? ControlChar.SOH.getValue() : ControlChar.STX.getValue();

        //包序(1-255)可循环使用
        buffer[1] = (byte) ((index - 1) % 255 + 1);
        buffer[2] = (byte) ~buffer[1];//包序反码(网文上有歧义,有的说反码有的说补码,正数的补码=原码=反码,因此也不影响)

        int dataOffset = PACKET_HEADER_SIZE;
        if (index * FRAME_SIZE > source.length) {//当前包源数据不够长了
            int remLength = source.length - (index - 1) * FRAME_SIZE;//剩余长度
            System.arraycopy(source, (index - 1) * FRAME_SIZE, buffer, dataOffset, remLength);

            //不足FRAME_SIZE的用FILL_BYTE填充
            Arrays.fill(buffer, dataOffset + remLength, size - CRC_SIZE, FILL_BYTE);
        } else {
            System.arraycopy(source, (index - 1) * FRAME_SIZE, buffer, PACKET_HEADER_SIZE, FRAME_SIZE);
        }

        //为MCU节省空间,仍使用651中的校验公式 2024-01-24 guozhao
        byte[] crcBuffer = new byte[FRAME_SIZE];//校验范围从内容部分开始
        System.arraycopy(buffer, PACKET_HEADER_SIZE, crcBuffer, 0, FRAME_SIZE);
        int calcValue = CRC16Util.calcCrc16(crcBuffer);
        byte[] crc = NumberUtil.intTo2bytes(calcValue);    //根据内容计算出来的CRC值

        System.arraycopy(crc, 0, buffer, size - CRC_SIZE, CRC_SIZE);

        log.info("发送第[{}]包:{}", index, NumberUtil.bytesToHexStr(buffer));
        return buffer;
    }

    /**
     * 补发数据
     *
     * @param model
     */
    private void resendData(XModemTransferModel model) {
        if (model.getPackIndex() == model.getPackCount()) {
            if (model.isSendEot()) {//发送结束标记后,又收到了正常应答
                sendControlChar(ControlChar.EOT, model.getChannelHandlerContext());
            } else {
                model.setPackIndex(model.getPackIndex());
                model.setLastRevTime(new Date());
                sendContent(model, model.getChannelHandlerContext());
            }
        } else {
            model.setPackIndex(model.getPackIndex());
            model.setLastRevTime(new Date());
            sendContent(model, model.getChannelHandlerContext());
        }
        model.setReissueNum(model.getReissueNum() + 1);
    }

    /**
     * 更新计划中的执行次数
     *
     * @param ctx
     */
    private void updateUpgradeNum(ChannelHandlerContext ctx) {
        TUpgradePlanDetailModel detailModel = getCurrentUpgradePlanDetailModel(ctx);
        if (detailModel != null) {
            int num = detailModel.getFailNum() == null ? 1 : detailModel.getFailNum() + 1;
            detailModel.setFailNum(num);
            detailModel.setUpgradeTime(new Date());//此时相当于开始时间 2024-04-15 guozhao
            upgradePlanDetailService.updateTUpgradePlanDetail(detailModel);
        }
    }

    /**
     * 更新升级计划明细状态
     *
     * @param ctx
     * @param status
     */
    private void updateUpgradePlanDetailStatus(ChannelHandlerContext ctx, DeviceUpgradeStatus status) {
        TUpgradePlanDetailModel detailModel = getCurrentUpgradePlanDetailModel(ctx);
        if (detailModel != null) {
            detailModel.setUpgradeStatus(status.getFlag());
            detailModel.setUpgradeTime(new Date());
            upgradePlanDetailService.updateTUpgradePlanDetail(detailModel);
        }
    }


    /**
     * 更新升级计划明细补充信息
     * 为了更好的分析升级过程
     * @param ctx
     * @param notes
     */
    private void updateUpgradePlanDetailNotes(ChannelHandlerContext ctx, String notes){
        TUpgradePlanDetailModel detailModel = getCurrentUpgradePlanDetailModel(ctx);
        if (detailModel != null) {
            detailModel.setStationName(notes);
            upgradePlanDetailService.updateTUpgradePlanDetail(detailModel);
        }
    }

    private TUpgradePlanDetailModel getCurrentUpgradePlanDetailModel(ChannelHandlerContext ctx) {
        //计次,当前设备在升级计划中的执行次数
        HashMap<Integer, FzBaowenModel> fzBaowenModelHashMap = ThreadLocalUtils.threadLocal.get();
        if (fzBaowenModelHashMap == null) {
            return null;
        }
        FzBaowenModel partModel = fzBaowenModelHashMap.get(ctx.hashCode());
        if (partModel != null && partModel.getUpgradePlanModel() != null) {
            Optional<TUpgradePlanDetailModel> optional = partModel.getUpgradePlanModel().getDetails().stream().filter(s -> s.getDeviceSn().equals(partModel.getSn())).findFirst();
            if (optional.isPresent()) {
                return optional.get();
            }
        }
        return null;
    }

    /**
     * 写入Info日志
     *
     * @param ctx
     * @param info
     */
    private void writeInfoLog(ChannelHandlerContext ctx, String info) {
        log.info("[{}]{}", ctx.channel().remoteAddress(), info);
    }

    /**
     * 下面的代码是处理过程中会出现的异常情况，并未使用,服务端是被动的,理论不需要处理非正常逻辑(超时问题在Netty连接中已经控制) ---
     */
    private static final long TIMEOUT_4_MINUTES = 4 * 60 * 1000;//超时时间

    private void statusCheckThread(int hashCode) {
        log.info("{}启动状态检测线程", hashCode);

        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        threadPool.execute(() -> runStatusCheck(hashCode));
    }

    //运行状态检测逻辑
    private void runStatusCheck(int hashCode) {
        try {
            while (true) {
                HashMap<Integer, XModemTransferModel> localMap = XModemTransferModel.inheritableThreadLocal.get();
                XModemTransferModel model = localMap.get(hashCode);

                if (model == null) {
                    break;
                } else {
                    checkAndHandleStatus(model);
                }

                Thread.sleep(1000);
            }

            log.info("------------状态检测线程超时退出------------");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("状态检测线程被中断", e);
        }
    }

    //检测并处理状态
    private void checkAndHandleStatus(XModemTransferModel model) {
        String remote = model.getChannelHandlerContext().channel().remoteAddress().toString();
        Date now = new Date();
        long elapsedTimeSinceLastReceive = now.getTime() - model.getLastRevTime().getTime();

        if (elapsedTimeSinceLastReceive > 3000 && !model.isSendOver()) {
            handleNoResponse(remote, model);
        }

        if (elapsedTimeSinceLastReceive > 30000) {
            handleCloseConnection(remote, model);
        }

        if (now.getTime() - model.getCreateTime().getTime() > TIMEOUT_4_MINUTES) {
            handleCloseConnection(remote, model);
        }
    }

    //无应答,主动补发
    private void handleNoResponse(String remote, XModemTransferModel model) {
        if (model.getReissueNum() < 3) {
            log.info("[{}]无应答,补发数据", remote);
            resendData(model);
        } else {
            log.info("[{}]补发数据超限", remote);
        }
    }

    //断开连接
    private void handleCloseConnection(String remote, XModemTransferModel model) {
        log.info("[{}]关闭连接", remote);
        HashMap<Integer, XModemTransferModel> localMap = XModemTransferModel.inheritableThreadLocal.get();
        localMap.remove(model.hashCode());
        model.getChannelHandlerContext().channel().close();
    }
    /** 备用异常处理 结束---------------------- */
}
