package com.dcs.equipment.utils;

import com.dcs.equipment.domain.DeviceRequest;
import com.dcs.hnyz.domain.EquipmentParam;
import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
import com.intelligt.modbus.jlibmodbus.master.ModbusMasterFactory;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.dcs.equipment.constants.ModbusConstants.*;
import static com.dcs.equipment.domain.ModbusTcpEnum.DATA_ADDRESS_LENGTH_MAP;
import static com.dcs.equipment.domain.ModbusTcpEnum.DATA_TYPE_LENGTH_MAP;

/**
 * @author: wangpx
 * @date: 2025-03-06 13:39
 */
@Component
public class ModbusUtil {
    private static final Logger logger = LoggerFactory.getLogger("modbusTcp");
    @Value("${dcs.modbus.host}")
    private String HOST;
    @Value("${dcs.modbus.port}")
    private int PORT;

    public static void main(String[] args) throws Exception {
        TcpParameters tcpParameters = new TcpParameters();
        InetAddress address = null;
        try {
            address = InetAddress.getByName("200.200.200.10");
        } catch (UnknownHostException e) {
            // 日志记录 广播异常信息
            e.printStackTrace();
            MASTER = null;
        }
        tcpParameters.setHost(address);
        tcpParameters.setPort(502);
        tcpParameters.setKeepAlive(true);
        Modbus.setAutoIncrementTransactionId(true);
        ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
        MASTER = master;
//        master.connect();// 开启连接
        try {
            MASTER.connect();
        } catch (ModbusIOException e) {
            // 日志记录 广播异常信息
            e.printStackTrace();
            MASTER = null;
        }
        // 从机模拟 1线圈 2输入 3保持
        int i = 1;
        float[] holdingRegistersFloatValues = getHoldingRegistersFloatValues(1, 712, 26);
        for (float f : holdingRegistersFloatValues) {
            System.out.println("第 " + i++ + " Value: " + f);
        }
        MASTER.disconnect();
    }

    public static List<DeviceRequest> getDeviceRequests(List<EquipmentParam> equipmentParamList) {
        // 保存modbus请求信息
        List<DeviceRequest> deviceRequestList = new ArrayList<>();
        // 根据功能码分类的设备信息
        Map<String, List<EquipmentParam>> responseMap = new HashMap<>();
        // 根据功能码分类的设备地址信息
        Map<String, Map<String, List<Integer>>> addressMap = new HashMap<>();
        // 线圈数据
        responseMap.put(COIL_FUNCTION_CODE, new ArrayList<>());
        // 输入寄存器数据
        responseMap.put(INPUT_FUNCTION_CODE, new ArrayList<>());
        // 保持寄存器数据
        responseMap.put(HOLDING_FUNCTION_CODE, new ArrayList<>());
        // 线圈数据存储地址
        Map<String, List<Integer>> coilAddressMap = new HashMap<>();
        coilAddressMap.put(COIL_DATA_TYPE, new ArrayList<>());
        addressMap.put(COIL_FUNCTION_CODE, coilAddressMap);
        // 输入寄存器数据存储地址
        Map<String, List<Integer>> inputAddressMap = new HashMap<>();
        inputAddressMap.put(INT_DATA_TYPE, new ArrayList<>());
        inputAddressMap.put(FLOAT_DATA_TYPE, new ArrayList<>());
        addressMap.put(INPUT_FUNCTION_CODE, inputAddressMap);
        // 保持寄存器数据存储地址
        Map<String, List<Integer>> holdingAddressMap = new HashMap<>();
        holdingAddressMap.put(INT_DATA_TYPE, new ArrayList<>());
        holdingAddressMap.put(FLOAT_DATA_TYPE, new ArrayList<>());
        addressMap.put(HOLDING_FUNCTION_CODE, holdingAddressMap);
        // 保存寄存器数据和地址数据
        for (EquipmentParam equipment : equipmentParamList) {
            String registerCode = equipment.getRegisterCode();
            if (registerCode != null && !registerCode.isEmpty()) {
                responseMap.get(registerCode).add(equipment);
                addressMap.get(registerCode).get(equipment.getDataType()).add(equipment.getAddress());
            }
        }

        // 将地址排序并计算分段读取
        addressMap.forEach((registerCode, dataTypeMap) -> {
            if (dataTypeMap == null || dataTypeMap.isEmpty()) { // dataTypeMap为空时，跳过
                return;
            }
            for (Map.Entry<String, List<Integer>> entry : dataTypeMap.entrySet()) {
                if (entry.getValue() == null || entry.getValue().isEmpty()) { // 地址为空时，跳过
                    continue;
                }
                String dataType = entry.getKey();
                List<Integer> addresses = entry.getValue();
                addresses.sort(Integer::compareTo);
                int lastAddress = addresses.get(addresses.size() - 1) + 1;
                int firstAddress = addresses.get(0);
                // 需要读取的长度
                int readQuantityLength = MathUtil.ceil((lastAddress - firstAddress), DATA_ADDRESS_LENGTH_MAP.get(dataType));
                // 根据读取长度分段读取 超过一次可读取的长度的地址将被分为多个请求
                for (int i = 1; i <= MathUtil.ceil(readQuantityLength, DATA_TYPE_LENGTH_MAP.get(dataType)); i++){
                    DeviceRequest deviceRequest = new DeviceRequest();
                    // TODO 设备从机地址需要从数据库中获取
                    deviceRequest.setUnitId(1);
                    deviceRequest.setResource(registerCode);
                    deviceRequest.setDataType(dataType);
                    deviceRequest.setOffsetOrIndex(addresses.get(0) + (DATA_TYPE_LENGTH_MAP.get(dataType) * (i - 1) * DATA_ADDRESS_LENGTH_MAP.get(dataType)));
                    int quantity;
                    // 读取长度是否超过一次可读取的长度
                    if (readQuantityLength < DATA_TYPE_LENGTH_MAP.get(dataType) * i) {
                        // 未超过
                        quantity = (int) Math.ceil(readQuantityLength - (DATA_TYPE_LENGTH_MAP.get(dataType) * (i - 1)));
                    } else {
                        // 超过
                        quantity = (int) Math.ceil(DATA_TYPE_LENGTH_MAP.get(dataType));
                    }
                    deviceRequest.setLengthOrQos(quantity);
                    deviceRequestList.add(deviceRequest);
                }
            }
        });
        return deviceRequestList;
    }

    private static ModbusMaster MASTER;

    private static final int reconnectingCount = 5; // 单次重连 最多尝试次数

    @PostConstruct // 启动时spring容器调用该方法初始化
    public void init() {
        initMasterSetting();
        try {
            MASTER.connect();
            logger.info("连接 PLC");
        } catch (ModbusIOException e) {
            logger.error("PLC 初始化连接失败");
        }
    }

    private void initMasterSetting() {
        TcpParameters TCP_PARAMETERS = new TcpParameters();
        InetAddress address = null;
        try {
            address = InetAddress.getByName(HOST);
        } catch (UnknownHostException e) {
            logger.error("PLC 地址解析失败，请检查地址是否正确");
        }
        TCP_PARAMETERS.setHost(address);
        TCP_PARAMETERS.setPort(PORT);
        MASTER = ModbusMasterFactory.createModbusMasterTCP(TCP_PARAMETERS);
        Modbus.setAutoIncrementTransactionId(true);
    }

    @PreDestroy
    public void destroy() {
        if (MASTER!= null && MASTER.isConnected()) {
            try {
                MASTER.disconnect();
                logger.info("PLC 断开连接");
            } catch (ModbusIOException e) {
                logger.error("与 PLC 断开连接失败");
            }
        }
    }

    private static final AtomicBoolean reconnecting = new AtomicBoolean(false);

    private static ModbusMaster getMaster() throws ModbusIOException {
        if (!MASTER.isConnected()) {
            reconnect(); // 启动后台重连，不会重复启动
            throw new ModbusIOException("PLC 连接断开，正在尝试自动重连...");
        }
        return MASTER;
    }

    private static void reconnect() {
        if (reconnecting.get()) return; // 已在重连中，直接返回

        if (reconnecting.compareAndSet(false, true)) {
            new Thread(() -> {
                try {
                    for (int i = 0; i < reconnectingCount; i++) { // 单次重连 最多尝试次数
                        try {
                            logger.warn("PLC 连接断开，开始重连...");
                            MASTER.connect();
                            if (MASTER.isConnected()) {
                                logger.info("PLC 重连成功！");
                                break;
                            }
                        } catch (ModbusIOException e) {
                            logger.error("PLC 重连失败: {}", e.getMessage());
                        }

                        try {
                            Thread.sleep(1000); // 每次重试间隔 1 秒
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                } finally {
                    reconnecting.set(false); // 重连结束，允许后续再次触发
                }
            }, "PLC-Reconnect-Thread").start();
        }
    }

    /**
     * 获取保持寄存器值
     */
    private static int[] getHoldingRegisterValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        ModbusMaster master = getMaster();
        return master.readHoldingRegisters(slaveId, offset, quantity);
    }

    /**
     * 获取多个保持寄存器的整数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     * @param quantity 读取数据个数
     */
    public static int[] getHoldingRegistersIntegerValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        if (quantity > 125) {
            throw new ModbusNumberException("Quantity must be less than or equal to 125");
        }
        return getHoldingRegisterValues(slaveId, offset, quantity);
    }

    /**
     * 获取单个保持寄存器的整数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     */
    public static int getHoldingRegisterIntegerValue(int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        return getHoldingRegistersIntegerValues(slaveId, offset, 1)[0];
    }

    /**
     * 获取多个保持寄存器的浮点数值
     *
     * @param slaveId  单元标识符 设备地址
     * @param offset   起始地址
     * @param quantity 读取数据个数
     */
    public static float[] getHoldingRegistersFloatValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        if (quantity > 62) {
            throw new ModbusNumberException("Quantity must be less than or equal to 62");
        }
        quantity = 2 * quantity; // float 类型占两个寄存器
        // 读取寄存器数据
        int[] registers = getHoldingRegisterValues(slaveId, offset, quantity);
        // 将寄存器数据转换为浮点数数组
        float[] floatValues = new float[quantity / 2]; // 每两个寄存器组成一个浮点数
        for (int i = 0; i < floatValues.length; i++) {
            int reg1 = registers[i * 2];    // 第一个寄存器
            int reg2 = registers[i * 2 + 1]; // 第二个寄存器
            // 将两个寄存器的值合并成一个 32 位的整数
            int combined = (reg1 << 16) + (reg2 & 0xFFFF);
            // 将 32 位整数转换为浮点数
            floatValues[i] = Float.intBitsToFloat(combined);
        }
        return floatValues;
    }

    /**
     * 获取单个保持寄存器的浮点数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     */
    public static float getHoldingRegisterFloatValue(int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        return getHoldingRegistersFloatValues(slaveId, offset, 1)[0];
    }

    /**
     * 写入寄存器值
     */
    public static void setRegisterValues(int slaveId, int offset, int[] value) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        ModbusMaster master = getMaster();
        if (value.length > 123) {
            throw new ModbusNumberException("Value length must be less than or equal to 123");
        }
        master.writeMultipleRegisters(slaveId, offset, value);
    }

    /**
     * 设置单个保持寄存器的整数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     * @param value 写入的整数值
     */
    public static void setRegisterValue(int slaveId, int offset, int value) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        setRegisterValues(slaveId, offset, new int[]{value});
    }

    // TODO 新增写入 DInt 类型数据的方法

    /**
     * 设置单个保持寄存器的浮点数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     * @param value 写入的整数值
     */
    public static void setRegisterFloatValue(int slaveId, int offset, float value) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        int intValue = Float.floatToIntBits(value);
        int reg1 = (intValue >> 16) & 0xFFFF;
        int reg2 = intValue & 0xFFFF;
        setRegisterValues(slaveId, offset, new int[]{reg1, reg2});
    }

    /**
     * 设置多个保持寄存器的浮点数值
     */
    public static void setRegisterFloatValues(int slaveId, int offset, float[] values) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        if (values.length > 61) {
            throw new ModbusNumberException("Value length must be less than or equal to 61");
        }
        int[] intValues = new int[values.length * 2];
        for (int i = 0; i < values.length; i++) {
            int intValue = Float.floatToIntBits(values[i]);
            int reg1 = (intValue >> 16) & 0xFFFF;
            int reg2 = intValue & 0xFFFF;
            intValues[i * 2] = reg1;
            intValues[i * 2 + 1] = reg2;
        }
        setRegisterValues(slaveId, offset, intValues);
    }

    /**
     * 获取多个输入寄存器值
     */
    public static int[] getInputRegisterValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        ModbusMaster master = getMaster();
        return master.readInputRegisters(slaveId, offset, quantity);
    }

    public static int getInputRegisterIntegerValue(int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        return getInputRegisterValues(slaveId, offset, 1)[0];
    }

    /**
     * 获取多个输入寄存器的浮点数值
     */
    public static float[] getInputRegisterFloatValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        if (quantity > 62) {
            throw new ModbusNumberException("Quantity must be less than or equal to 62");
        }
        quantity = 2 * quantity; // float 类型占两个寄存器
        // 读取寄存器数据
        int[] registers = getInputRegisterValues(slaveId, offset, quantity);
        // 将寄存器数据转换为浮点数数组
        float[] floatValues = new float[quantity / 2];  // 每两个寄存器组成一个浮点数
        for (int i = 0; i < floatValues.length; i++) {
            int reg1 = registers[i * 2];    // 第一个寄存器
            int reg2 = registers[i * 2 + 1]; // 第二个寄存器
            // 将两个寄存器的值合并成一个 32 位的整数
            int combined = (reg1 << 16) + (reg2 & 0xFFFF);
            // 将 32 位整数转换为浮点数
            floatValues[i] = Float.intBitsToFloat(combined);
        }
        return floatValues;
    }

    public static float getInputRegisterFloatValue(int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        return getInputRegisterFloatValues(slaveId, offset, 1)[0];
    }

    public static void setCoilValues(int slaveId, int offset, boolean[] values) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        ModbusMaster master = getMaster();
        if (values.length > 1968) {
            throw new ModbusNumberException("Value length must be less than or equal to 1968");
        }
        master.writeMultipleCoils(slaveId, offset, values);
    }

    public static void setCoilValue(int slaveId, int offset, boolean value) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        setCoilValues(slaveId, offset, new boolean[]{value});
    }

    public static boolean[] getCoilValues(int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        ModbusMaster master = getMaster();
        return master.readCoils(slaveId, offset, quantity);
    }

    public static boolean getCoilValue(int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        return getCoilValues(slaveId, offset, 1)[0];
    }

    public static List<Object> getValues(DeviceRequest request) throws ModbusProtocolException, ModbusNumberException, ModbusIOException {
        switch (request.getResource()) {
            case COIL_FUNCTION_CODE:
                return convertArrayToList(getCoilValues(request.getUnitId(), request.getOffsetOrIndex(), request.getLengthOrQos()));
            case INPUT_FUNCTION_CODE:
                switch (request.getDataType()) {
                    case INT_DATA_TYPE:
                        return convertArrayToList(getInputRegisterValues(request.getUnitId(), request.getOffsetOrIndex(), request.getLengthOrQos()));
                    case FLOAT_DATA_TYPE:
                        return convertArrayToList(getInputRegisterFloatValues(request.getUnitId(), request.getOffsetOrIndex(), request.getLengthOrQos()));
                }
            case HOLDING_FUNCTION_CODE:
                switch (request.getDataType()) {
                    case INT_DATA_TYPE:
                        return convertArrayToList(getHoldingRegisterValues(request.getUnitId(), request.getOffsetOrIndex(), request.getLengthOrQos()));
                    case FLOAT_DATA_TYPE:
                        return convertArrayToList(getHoldingRegistersFloatValues(request.getUnitId(), request.getOffsetOrIndex(), request.getLengthOrQos()));
                }
            default:
                return Collections.emptyList();
        }
    }

    private static List<Object> convertArrayToList(Object array) {
        List<Object> list = new ArrayList<>();
        if (array instanceof boolean[]) {
            for (boolean value : (boolean[]) array) {
                list.add(value);
            }
        } else if (array instanceof int[]) {
            for (int value : (int[]) array) {
                list.add(value);
            }
        } else if (array instanceof float[]) {
            for (float value : (float[]) array) {
                list.add(value);
            }
        }
        return list;
    }
    public static boolean objToBoolean(Object obj) {
        return Boolean.parseBoolean(obj.toString());
    }

    public static Integer objToInt(Object obj) {
        return Integer.parseInt(obj.toString());
    }

    public static Float objToFloat(Object obj) {
        return Float.parseFloat(obj.toString());
    }
}
