package com.jiayue.ipfcst.fileupload.IEC102;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.jiayue.ipfcst.common.data.entity.*;
import com.jiayue.ipfcst.fileupload.dto.Validate102Dto;
import com.jiayue.ipfcst.fileupload.util.*;
import io.netty.channel.ChannelHandlerContext;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 102服务端业务处理类
 *
 * @author xsl
 * @version 3.0
 */
@Service
public class ServerFor102StandardService extends Base102Service {
  @Transactional(propagation = Propagation.REQUIRED)
  public synchronized String handlerMessage(String receiveMessage, UploadObject uploadObject, UploadFileChannel uploadFileChannel, ElectricField electricField, Logger log, ChannelHandlerContext ctx) {
    String[] rmArray = IEC102Uitl.stingToArray(receiveMessage);
    String sendMessage = "";
    String sendCtrlChinese = "";
    String receiveCtrlChinese = "";

    if (FileConstant.validateMessage.get(uploadFileChannel.getId() + "") != null) {
      // 需要校验接收的帧是否正确
      Validate102Dto validate102Dto = FileConstant.validateMessage.get(uploadFileChannel.getId() + "");
      if (!validate102Dto.getMessageFirst().equals(rmArray[0])) {
        log.info(ctx.channel().remoteAddress() + " - " + "调度应该传" + validate102Dto.getMessageFirst() + "帧并且原因是" + validate102Dto.getMessageReason() + "，实际传" + IEC102Uitl.delimiterStringBySpace(receiveMessage) + ",断开通道重启");
//        super.totalFileUploadNums(uploadObject,uploadFileChannel, "调度应该传"+validate102Dto.getMessageFirst()+"帧，实际传"+IEC102Uitl.delimiterStringBySpace(receiveMessage), log);
        if ("10".equals(rmArray[0])) {
          channelCloseConnect(ctx, uploadObject, uploadFileChannel);
          return "";
        } else if ("68".equals(rmArray[0])) {
          if (!"0A".equals(rmArray[9])) {
            channelCloseConnect(ctx, uploadObject, uploadFileChannel);
            return "";
          }
        }
      }
    }

    if ("10".equals(rmArray[0])) {
      // 获取控制域,根据功能码判断业务流
      String fc = rmArray[1].substring(1);

      if (FileConstant.validateMessage.get(uploadFileChannel.getId() + "") != null) {
        // 需要校验接收的帧是否正确
        Validate102Dto validate102Dto = FileConstant.validateMessage.get(uploadFileChannel.getId() + "");
        if (!"".equals(validate102Dto.getMessageReason())) {
          if (!fc.equals(validate102Dto.getMessageReason())) {
            log.info(ctx.channel().remoteAddress() + " - " + "调度应该传10原因是" + validate102Dto.getMessageReason() + "，实际传" + IEC102Uitl.delimiterStringBySpace(receiveMessage) + ",断开通道重启");
            channelCloseConnect(ctx, uploadObject, uploadFileChannel);
            return "";
          }
        }
      }

      if ("0".equals(fc)) {
        receiveCtrlChinese = "复位通信";
        // 回复链路
        sendMessage = makeMessage10("00");
        sendCtrlChinese = "确认复位";
      } else if ("3".equals(fc)) {
        receiveCtrlChinese = "传送数据";
      } else if ("9".equals(fc)) {
        receiveCtrlChinese = "召唤链路";
        // 回复链路
        sendMessage = makeMessage10("0B");
        sendCtrlChinese = "回应链路请求帧";
      } else if ("A".equals(fc)) {
        receiveCtrlChinese = "召唤一级数据";
        // 先判断文件上报次数累计的缓存变量是否存在
        Map<String, FileMutableInteger> filterMap = FileConstant.uploadCountMap.entrySet().stream().filter(r -> Integer.parseInt(uploadObject.getObjectNo()) == Integer.parseInt(r.getKey().substring(0, r.getKey().indexOf("@"))))
          .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (oldValue, newValue) -> oldValue, LinkedHashMap::new));
        // 上报文件名
        String fileNameKey = "";
        if (MapUtil.isNotEmpty(filterMap)) {
          // 不是第一次要文件了
          for (Map.Entry<String, FileMutableInteger> entry : filterMap.entrySet()) {
            fileNameKey = entry.getKey();
            log.info(ctx.channel().remoteAddress() + " - " + "上传文件===>" + entry.getKey());
            break;
          }
        } else {
          // 从待上报的缓存中取一个文件放入次数变量中
          Map<String, UploadFileLog> readyUploadFileMap = super.filterFileByObjectNo(uploadObject.getObjectNo(), FileConstant.readyUploadFileMap, log);
          for (Map.Entry<String, UploadFileLog> entry : readyUploadFileMap.entrySet()) {
            // 从待上报缓存中取一个加入到累计次数变量中
            FileMutableInteger fileMutableInteger = new FileMutableInteger(0);
            FileConstant.uploadCountMap.put(entry.getKey(), fileMutableInteger);
            log.info(ctx.channel().remoteAddress() + " - " + "找到文件===>" + entry.getKey());
            fileNameKey = entry.getKey();
            break;
          }
        }
        if ("".equals(fileNameKey)) {
          sendCtrlChinese = "没有找到上报文件";
          sendMessage = makeMessage10("09");
          FileConstant.validateMessage.remove(uploadFileChannel.getId() + "");
        } else {
          // 生成传输文件的68帧
          sendMessage = uploadFile68(fileNameKey, electricField, uploadObject, uploadFileChannel, log);
          sendCtrlChinese = "通道：" + uploadFileChannel.getChannelName() + "上报：" + fileNameKey;
        }
      } else if ("B".equals(fc)) {
        receiveCtrlChinese = "召唤二级数据";
        // 获取此通道下的上报文件
        Map<String, UploadFileLog> readyUploadFileMap = super.filterFileByObjectNo(uploadObject.getObjectNo(), FileConstant.readyUploadFileMap, log);

        if (MapUtil.isNotEmpty(readyUploadFileMap)) {
          // 文件缓存中有上报文件
          sendMessage = makeMessage10("29");
          sendCtrlChinese = "希望向主站传输一级数据";
          Validate102Dto dto = new Validate102Dto();
          dto.setMessageFirst("10");
          dto.setMessageReason("A");
          FileConstant.validateMessage.put(uploadFileChannel.getId() + "", dto);
        } else {
          // 没有文件
          sendMessage = makeMessage10("09");
          sendCtrlChinese = "没有文件需要上报";
          Validate102Dto dto = new Validate102Dto();
          dto.setMessageFirst("10");
          dto.setMessageReason("B");
          FileConstant.validateMessage.put(uploadFileChannel.getId() + "", dto);
        }
      } else if ("C".equals(fc)) {
        receiveCtrlChinese = "下发数据通知";
      }
      log.info(ctx.channel().remoteAddress() + " - " + "接收报文：[" + receiveCtrlChinese + "]" + IEC102Uitl.delimiterStringBySpace(receiveMessage));
    } else {// 68开头报文
      // 判断传输原因
      String cot = rmArray[9];
      String[] send68Array = ArrayUtil.clone(rmArray);
      if ("E34".equals(electricField.getProvinceEnum().toString())){
        // 针对安徽升级，修改控制域为00
        send68Array[4] = "00";
      }
      if ("0A".equals(cot)) {
        receiveCtrlChinese = "文件接收结束";
        send68Array[9] = "0B";
        // 计算校验和
        String tempStr = StringUtils.join(send68Array, "");
        String crc = IEC102Uitl.makeChecksum(tempStr.substring(8, 34));
        send68Array[17] = crc;
        sendCtrlChinese = "确认文件传输结束";
        log.info(ctx.channel().remoteAddress() + " - " + "接收报文：[" + receiveCtrlChinese + "]" + IEC102Uitl.delimiterStringBySpace(receiveMessage));
        super.totalFileUploadNums(uploadObject, uploadFileChannel, "", log);
        // 文件状态成功
        sendMessage = ArrayUtil.join(send68Array, "");
      } else {
        // 文件状态失败
        if ("0D".equals(cot)) {
          receiveCtrlChinese = "文件重复传输";
          send68Array[9] = "0E";
          // 计算校验和
          String tempStr = StringUtils.join(send68Array, "");
          String crc = IEC102Uitl.makeChecksum(tempStr.substring(8, 26));
          send68Array[13] = crc;
          sendCtrlChinese = "确认文件重复传输";
        } else if ("0F".equals(cot)) {
          receiveCtrlChinese = "文件长度超出规定最大长度";
          send68Array[9] = "10";
          // 计算校验和
          String tempStr = StringUtils.join(send68Array, "");
          String crc = IEC102Uitl.makeChecksum(tempStr.substring(8, 26));
          send68Array[13] = crc;
          sendCtrlChinese = "确认传输文件过长";
        } else if ("11".equals(cot)) {
          receiveCtrlChinese = "文件名格式不符合要求";
          send68Array[9] = "12";
          // 计算校验和
          String tempStr = StringUtils.join(send68Array, "");
          String crc = IEC102Uitl.makeChecksum(tempStr.substring(8, 26));
          send68Array[13] = crc;
          sendCtrlChinese = "确认文件名格式不符合要求";
        } else if ("13".equals(cot)) {
          receiveCtrlChinese = "传输单帧报文长度过长";
          send68Array[9] = "14";
          // 计算校验和
          String tempStr = StringUtils.join(send68Array, "");
          String crc = IEC102Uitl.makeChecksum(tempStr.substring(8, 26));
          send68Array[13] = crc;
          sendCtrlChinese = "确认单帧报文长度过长";
        } else {
          receiveCtrlChinese = "接收的内容程序没做判断";
          sendCtrlChinese = "不是正常的报文回复内容";
        }
        log.info(ctx.channel().remoteAddress() + " - " + "接收报文：[" + receiveCtrlChinese + "]" + IEC102Uitl.delimiterStringBySpace(receiveMessage));
        super.totalFileUploadNums(uploadObject, uploadFileChannel, sendCtrlChinese, log);
        sendMessage = ArrayUtil.join(send68Array, "");
      }
      Validate102Dto dto = new Validate102Dto();
      dto.setMessageFirst("10");
      dto.setMessageReason("");
      FileConstant.validateMessage.put(uploadFileChannel.getId() + "", dto);
    }
    log.info(ctx.channel().remoteAddress() + " - " + "发送报文：[" + sendCtrlChinese + "]" + IEC102Uitl.delimiterStringBySpace(sendMessage));
    return sendMessage;
  }

  /**
   * 生成10帧报文
   * 格式说明：10|控制域|地址低字节|地址高字节|校验和|16
   *
   * @param ctrl 控制域字节
   * @return 10帧报文
   */
  private synchronized String makeMessage10(String ctrl) {
    String[] returnMessage = {"10", "ctrl", "FF", "FF", "crc", "16"};
    String crc = IEC102Uitl.makeChecksum(ctrl + returnMessage[2] + returnMessage[3]);
    returnMessage[1] = ctrl;
    returnMessage[4] = crc;
    String sendMessage = ArrayUtil.join(returnMessage, "");
    return sendMessage.toUpperCase();
  }

  /**
   * 传输文件的68帧报文
   * 格式说明：68|帧长低字节|帧长高字节|68|控制域|地址低字节|地址高字节|类型标识|可变限定词|传输原因|设备地址低字节|设备地址高字节|记录地址|文件名字节|文件内容字节|校验和|16
   * crc校验和：从[控制域]到[校验和]之前
   * 帧长：从[控制域]到[校验和]之前所有字节数
   *
   * @return 68帧报文
   */
  private synchronized String uploadFile68(String fileNameKey, ElectricField electricField, UploadObject uploadObject, UploadFileChannel uploadFileChannel, Logger log) {
    String[] returnMessage = {"68", "mlLow", "mlHeight", "68", "ctrl", "FF", "FF", "typeCode", "01", "reason", "FF", "FF", "00", "fileNameByte", "fileContentByte", "crc", "16"};
    String[] tempKey = new String[3];
    String tempFileName = fileNameKey;
    tempKey[0] = tempFileName.substring(0, tempFileName.indexOf("@", 0));
    tempFileName = tempFileName.substring(tempFileName.indexOf("@", 0) + 1);
    tempKey[1] = tempFileName.substring(0, tempFileName.indexOf("@", 0));
    tempKey[2] = tempFileName.substring(tempFileName.indexOf("@", 0) + 1);

    // 生成文件名及内容
    String fileName = tempKey[2];
    byte[] tempFileNameBytes = null;
    String charsetName;
    if ("E42".equals(electricField.getProvinceEnum().toString()) && ("E9".equals(tempKey[1]) || "E74".equals(tempKey[1]) || "E92".equals(tempKey[1]))) {
      // 湖北省新风机文件用GBK上报，其他用UTF-8
      charsetName = "GBK";
    } else {
      charsetName = uploadObject.getUploadFileCharSetEnum().getMessage();
    }
    try {
      tempFileNameBytes = fileName.getBytes(charsetName);
    } catch (Exception e) {
      log.error("转换文件名编码失败", e);
    }
    // 获取文件名称长度
    String fileNameLength = uploadFileChannel.getUploadFileNameLengthEnum().getMessage();
    String fileNameByte = ByteUtil.Byte2String(tempFileNameBytes);
    fileNameByte = StrUtil.padAfter(fileNameByte.replace(" ", ""), Integer.parseInt(fileNameLength) * 2, '0');
    // 获取本地物理文件路径
    String filePath = FileUtil.getFileUploadPath() + File.separator + "process" + File.separator + tempKey[0] + File.separator + tempKey[1] + File.separator + tempKey[2];

    String fileContentByte = FileConstant.fileContentMap.get(fileNameKey);
    if (StrUtil.hasBlank(fileContentByte)) {
      if ("E13".equals(electricField.getProvinceEnum().toString()) && ("E206".equals(tempKey[1]) || "E207".equals(tempKey[1]))) {
        fileContentByte = super.createFileMessageHexByExcel(filePath, log);
      }
      else{
        // 缓存中没有文件内容则新生成文件内容报文
        fileContentByte = super.createFileMessageHex(filePath, charsetName, log);
      }
    }
    // 生成不是最后一帧控制域码
    String ctrl = "28";
    String reason = "08";
    // 获取通道单次文件传输字节
    String singleByte = uploadFileChannel.getUploadFileSingleByteEnum().getMessage();
    if (fileContentByte.length() <= Integer.parseInt(singleByte) * 2) {
      // 一次可以传输完,生成控制域码
      ctrl = "08";
      reason = "07";

      Validate102Dto dto = new Validate102Dto();
      dto.setMessageFirst("68");
      dto.setMessageReason("0A");
      FileConstant.validateMessage.put(uploadFileChannel.getId() + "", dto);

    } else {
      // 将剩余的文件内容报文存入缓存中
      FileConstant.fileContentMap.put(fileNameKey, fileContentByte.substring(Integer.parseInt(singleByte) * 2));
      fileContentByte = fileContentByte.substring(0, Integer.parseInt(singleByte) * 2);

//      Validate102Dto dto = new Validate102Dto();
//      dto.setMessageFirst("10");
//      dto.setMessageReason("A");
      FileConstant.validateMessage.remove(uploadFileChannel.getId() + "");
    }
    // 生成类型标识
//    List<UploadFileCode> uploadFileCodeList = uploadFileCodeService.get();

    Map<String, Integer> codeMap = super.getFileCode();
    String typeCode = "";
    if ("E37".equals(electricField.getProvinceEnum().toString())) {
      Map<String, Integer> tempMap = new HashMap<>();
      // 山东
      for (Map.Entry<String, Integer> entry : codeMap.entrySet()) {
        if (entry.getKey().equals("E131") || entry.getKey().equals("E132") || entry.getKey().equals("E133") || entry.getKey().equals("E134") || entry.getKey().equals("E135") || entry.getKey().equals("E136") || entry.getKey().equals("E137") || entry.getKey().equals("E138") || entry.getKey().equals("E139") || entry.getKey().equals("E140")) {
          tempMap.put(entry.getKey(), entry.getValue());
        }
      }
      typeCode = Integer.toHexString(tempMap.get(tempKey[1]));
    } else {
      typeCode = Integer.toHexString(codeMap.get(tempKey[1]));
    }


    returnMessage[4] = ctrl;
    returnMessage[7] = typeCode;
    returnMessage[9] = reason;
    returnMessage[13] = fileNameByte;
    returnMessage[14] = fileContentByte;

    // 获取从[控制域]到[校验和]之前的报文
    StringBuffer tempSB = new StringBuffer("");
    for (int i = 4; i <= 14; i++) {
      tempSB.append(returnMessage[i]);
    }
    // 计算校验和
    String crc = IEC102Uitl.makeChecksum(tempSB.toString());
    returnMessage[15] = crc;
    // 计算报文的帧长
    String ml = StrUtil.padAfter(ByteUtil.decToHex(tempSB.length() / 2), 4, '0');
    String mlLow = ml.substring(0, 2);
    String mlHeight = ml.substring(2);
    returnMessage[1] = mlLow;
    returnMessage[2] = mlHeight;
    String sendMessage = ArrayUtil.join(returnMessage, "");
    return sendMessage.toUpperCase();
  }

  public void channelCloseConnect(ChannelHandlerContext ctx, UploadObject uploadObject, UploadFileChannel uploadFileChannel) {
    // 传输帧类型不对，断开通道重启
    Iterator<Map.Entry<String, FileMutableInteger>> countMap = FileConstant.uploadCountMap.entrySet().iterator();
    while (countMap.hasNext()) {
      Map.Entry<String, FileMutableInteger> entry = countMap.next();
      String key = entry.getKey();
      if (key.contains(uploadObject.getObjectNo() + "@")) {
        countMap.remove();
      }
    }

    Iterator<Map.Entry<String, String>> contentMap = FileConstant.fileContentMap.entrySet().iterator();
    while (contentMap.hasNext()) {
      Map.Entry<String, String> entry = contentMap.next();
      String key = entry.getKey();
      if (key.contains(uploadObject.getObjectNo() + "@")) {
        contentMap.remove();
      }
    }
    FileConstant.validateMessage.remove(uploadFileChannel.getId() + "");
    ctx.close();
  }
}
