package com.dcs.equipment.utils;

import com.dcs.equipment.domain.DeviceRequest;
import com.dcs.equipment.exception.device.*;
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.PreDestroy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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");

    private static final ExecutorService ModbusThreadPoolTaskExecutor = Executors.newFixedThreadPool(10);

    @Value("${dcs.device.reconnectLimit}")
    private static int reconnectLimit;

    private static Map<String, ModbusMaster> masters = new HashMap<>();
    private static Map<String, AtomicBoolean> reconnectingStatus = new HashMap<>();

    public static ModbusMaster connect(String host, Integer port) throws ModbusIOException, UnknownHostException {
        String url = host + ":" + port;
        logger.info("正在连接到 ModbusTCP {} 服务端", url);
        TcpParameters tcpParameters = new TcpParameters();
        InetAddress address = null;
        try {
            address = InetAddress.getByName(host);
        } catch (UnknownHostException e) {
            logger.error("ModbusTCP {} 地址解析失败: {}", url, e.getMessage());
            throw new UnknownHostException("ModbusTCP " + url + " 地址解析失败");
        }
        tcpParameters.setHost(address);
        tcpParameters.setPort(port);
        tcpParameters.setKeepAlive(true);
        Modbus.setAutoIncrementTransactionId(true);
        ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
        try {
            master.connect();
            masters.put(url, master);
            reconnectingStatus.put(url, new AtomicBoolean(false));
            logger.info("与 ModbusTCP {} 连接成功", url);
        } catch (ModbusIOException e) {
            logger.error("与 ModbusTCP {} 连接失败: {}", url, e.getMessage());
            throw new ModbusIOException("ModbusTCP " + url + " 连接失败");
        }
        return master;
    }

    private static void reconnect(String host, Integer port) throws DeviceIOException {
        String url = host + ":" + port;
        AtomicBoolean reconnecting = reconnectingStatus.get(url);
        if (reconnecting.get()) return;

        if (reconnecting.compareAndSet(false, true)) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                try {
                    for (int i = 0; i < reconnectLimit; i++) { // 单次重连 最多尝试次数
                        ModbusMaster master = masters.get(url);
                        logger.warn("ModbusTCP {} 连接断开，开始重连...", url);
                        try {
                            connect(host, port);
                        } catch (UnknownHostException e) {
                            throw new RuntimeException(e);
                        } catch (ModbusIOException e) {
                            if (i == reconnectLimit - 1) throw new RuntimeException(e);
                        }
                        if (master.isConnected()) {
                            logger.info("ModbusTCP {} 重连成功！", url);
                            break;
                        }

                        try {
                            Thread.sleep(1000); // 每次重试间隔 1 秒
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                } finally {
                    reconnecting.set(false); // 重连结束，允许后续再次触发
                }
            }, ModbusThreadPoolTaskExecutor);
            try {
                future.get(); // 阻塞等待，并能感知异常
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                if (cause instanceof UnknownHostException || cause instanceof ModbusIOException) {
                    throw new DeviceIOException(cause.getMessage());
                } else {
                    logger.error("ModbusTCP {} 重连失败: {}", url, e.getMessage());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.error("ModbusTCP {} 重连线程中断: {}", url, e.getMessage());
                throw new RuntimeException(e);
            }
        }
    }

    public static void disconnect(String host, Integer port) {
        String url = host+":"+port;
        ModbusMaster master= masters.get(url);
        try {
            master.disconnect();
            if (masters.containsKey(url)) {
                masters.remove(url);
            }
            if (reconnectingStatus.containsKey(url)) {
                reconnectingStatus.remove(url);
            }
            logger.info("ModbusTCP {} 断开连接", url);
        } catch (ModbusIOException e) {
            logger.error("与 ModbusTCP {} 断开连接失败: {}", url, e.getMessage());
        }
    }
    @PreDestroy
    private void destroy() {
        Set<String> set = masters.keySet();
        for (String key : set) {
            String[] split = key.split(":");
            String host = split[0];
            Integer port = Integer.parseInt(split[1]);
            disconnect(host, port);
        }
    }

    /**
     * 生成设备请求列表
     * 按IP:端口 -> 协议ID -> 从机ID -> 功能码 -> 数据类型的层次结构分组
     */
    /**
     * 生成设备请求列表（合并所有分组与生成逻辑为一个方法）
     * 按IP:端口 -> 协议ID -> 从机ID -> 功能码 -> 数据类型的层次结构分组
     */
    public static List<DeviceRequest> getDeviceRequests(List<EquipmentParam> equipmentParamList) {
        if (equipmentParamList == null || equipmentParamList.isEmpty()) {
            return new ArrayList<>();
        }
        List<DeviceRequest> deviceRequestList = new ArrayList<>();

        // 1. 按IP:端口分组
        Map<String, List<EquipmentParam>> ipPortGroups = new HashMap<>();
        for (EquipmentParam equipment : equipmentParamList) {
            String ipAddress = equipment.getIpAddress();
            Integer port = equipment.getPort();
            if (ipAddress != null && port != null) {
                String key = ipAddress + ":" + port;
                ipPortGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(equipment);
            }
        }

        // 2. 遍历每个IP:端口组
        for (Map.Entry<String, List<EquipmentParam>> entry : ipPortGroups.entrySet()) {
            String[] ipPort = entry.getKey().split(":");
            String ipAddress = ipPort[0];
            Integer port = Integer.parseInt(ipPort[1]);
            List<EquipmentParam> groupEquipmentList = entry.getValue();

            // 3. 按协议ID分组
            Map<Integer, List<EquipmentParam>> protocolGroups = new HashMap<>();
            for (EquipmentParam equipment : groupEquipmentList) {
                Integer protocolId = equipment.getProtocolId();
                if (protocolId != null) {
                    protocolGroups.computeIfAbsent(protocolId, k -> new ArrayList<>()).add(equipment);
                }
            }

            // 4. 遍历每个协议组
            for (Map.Entry<Integer, List<EquipmentParam>> protocolEntry : protocolGroups.entrySet()) {
                Integer protocolId = protocolEntry.getKey();
                List<EquipmentParam> protocolEquipmentList = protocolEntry.getValue();

                // 5. 按从机ID分组
                Map<Integer, List<EquipmentParam>> slaveGroups = new HashMap<>();
                for (EquipmentParam equipment : protocolEquipmentList) {
                    Integer unitId = equipment.getUnitId();
                    if (unitId != null) {
                        slaveGroups.computeIfAbsent(unitId, k -> new ArrayList<>()).add(equipment);
                    }
                }

                // 6. 遍历每个从机组
                for (Map.Entry<Integer, List<EquipmentParam>> slaveEntry : slaveGroups.entrySet()) {
                    Integer unitId = slaveEntry.getKey();
                    List<EquipmentParam> slaveEquipmentList = slaveEntry.getValue();

                    // 7. 按功能码和数据类型分组地址
                    Map<String, Map<String, List<Integer>>> groups = new HashMap<>();
                    // 初始化分组结构
                    {
                        Map<String, List<Integer>> coilGroups = new HashMap<>();
                        coilGroups.put(COIL_DATA_TYPE, new ArrayList<>());
                        groups.put(COIL_FUNCTION_CODE, coilGroups);

                        Map<String, List<Integer>> inputGroups = new HashMap<>();
                        inputGroups.put(INT_DATA_TYPE, new ArrayList<>());
                        inputGroups.put(FLOAT_DATA_TYPE, new ArrayList<>());
                        groups.put(INPUT_FUNCTION_CODE, inputGroups);

                        Map<String, List<Integer>> holdingGroups = new HashMap<>();
                        holdingGroups.put(INT_DATA_TYPE, new ArrayList<>());
                        holdingGroups.put(FLOAT_DATA_TYPE, new ArrayList<>());
                        groups.put(HOLDING_FUNCTION_CODE, holdingGroups);
                    }
                    // 填充数据
                    for (EquipmentParam equipment : slaveEquipmentList) {
                        String registerCode = equipment.getRegisterCode();
                        String dataType = equipment.getDataType();
                        Integer address = equipment.getAddress();
                        if (registerCode != null && !registerCode.isEmpty() && dataType != null && address != null) {
                            Map<String, List<Integer>> dataTypeMap = groups.get(registerCode);
                            if (dataTypeMap != null && dataTypeMap.containsKey(dataType)) {
                                dataTypeMap.get(dataType).add(address);
                            }
                        }
                    }

                    // 8. 为每个功能码和数据类型组合生成请求
                    for (Map.Entry<String, Map<String, List<Integer>>> functionEntry : groups.entrySet()) {
                        String functionCode = functionEntry.getKey();
                        Map<String, List<Integer>> dataTypeGroups = functionEntry.getValue();

                        for (Map.Entry<String, List<Integer>> dataTypeEntry : dataTypeGroups.entrySet()) {
                            String dataType = dataTypeEntry.getKey();
                            List<Integer> addresses = dataTypeEntry.getValue();

                            if (addresses == null || addresses.isEmpty()) {
                                continue;
                            }

                            // 排序地址
                            addresses.sort(Integer::compareTo);

                            // 计算地址范围
                            int firstAddress = addresses.get(0);
                            int lastAddress = addresses.get(addresses.size() - 1) + 1;
                            int addressRange = lastAddress - firstAddress;

                            // 计算数据类型长度
                            int dataTypeLength = DATA_ADDRESS_LENGTH_MAP.get(dataType);
                            int maxReadLength = DATA_TYPE_LENGTH_MAP.get(dataType);

                            // 计算总读取长度
                            int totalReadLength = MathUtil.ceil(addressRange, dataTypeLength);

                            // 计算分段数量
                            int segmentCount = MathUtil.ceil(totalReadLength, maxReadLength);

                            // 生成每个分段的请求
                            for (int i = 1; i <= segmentCount; i++) {
                                DeviceRequest request = new DeviceRequest();

                                // 设置基本参数
                                request.setResource(functionCode);
                                request.setDataType(dataType);
                                request.setIpAddress(ipAddress);
                                request.setPort(port);
                                request.setUnitId(unitId);
                                request.setProtocol(protocolId);

                                // 计算偏移量
                                int offset = firstAddress + (maxReadLength * (i - 1) * dataTypeLength);
                                request.setOffsetOrIndex(offset);

                                // 计算读取数量
                                int quantity;
                                if (totalReadLength < maxReadLength * i) {
                                    // 最后一个分段
                                    quantity = (int) Math.ceil(totalReadLength - (maxReadLength * (i - 1)));
                                } else {
                                    // 完整分段
                                    quantity = (int) Math.ceil(maxReadLength);
                                }
                                request.setLengthOrQos(quantity);

                                deviceRequestList.add(request);
                            }
                        }
                    }
                }
            }
        }
        return deviceRequestList;
    }

    private static ModbusMaster getMaster(String host, Integer port) throws DeviceIOException {
        ModbusMaster master = masters.get(host+":"+port);
        if (master == null){
            try {
                return connect(host, port);
            } catch (ModbusIOException | UnknownHostException e) {
                throw new DeviceIOException(e);
            }
        }
        if (!master.isConnected()) {
            reconnect(host, port);
        }
        return master;
    }

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

    /**
     * 获取多个保持寄存器的整数值
     * @param slaveId 单元标识符 设备地址
     * @param offset 起始地址
     * @param quantity 读取数据个数
     */
    public static int[] getHoldingRegistersIntegerValues(String host, Integer port, int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        if (quantity > 125) {
            throw new ModbusNumberException("取值长度必须小于 125");
        }
        return getHoldingRegisterValues(host, port, slaveId, offset, quantity);
    }

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

    /**
     * 获取多个保持寄存器的浮点数值
     *
     * @param slaveId  单元标识符 设备地址
     * @param offset   起始地址
     * @param quantity 读取数据个数
     */
    public static float[] getHoldingRegistersFloatValues(String host, Integer port, int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        if (quantity > 62) {
            throw new ModbusNumberException("取值长度必须小于 62");
        }
        quantity = 2 * quantity; // float 类型占两个寄存器
        // 读取寄存器数据
        int[] registers = getHoldingRegisterValues(host, port, 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(String host, Integer port, int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        return getHoldingRegistersFloatValues(host, port, slaveId, offset, 1)[0];
    }

    /**
     * 写入寄存器值
     */
    public static void setRegisterValues(String host, Integer port, int slaveId, int offset, int[] value) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        ModbusMaster master = getMaster(host, port);
        if (value.length > 123) {
            throw new ModbusNumberException("取值长度必须小于 123");
        }
        master.writeMultipleRegisters(slaveId, offset, value);
    }

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

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

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

    /**
     * 设置多个保持寄存器的浮点数值
     */
    public static void setRegisterFloatValues(String host, Integer port, int slaveId, int offset, float[] values) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        if (values.length > 61) {
            throw new ModbusNumberException("取值长度必须小于等于 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(host, port, slaveId, offset, intValues);
    }

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

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

    /**
     * 获取多个输入寄存器的浮点数值
     */
    public static float[] getInputRegisterFloatValues(String host, Integer port, int slaveId, int offset, int quantity) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        if (quantity > 62) {
            throw new ModbusNumberException("取值长度必须小于 62");
        }
        quantity = 2 * quantity; // float 类型占两个寄存器
        // 读取寄存器数据
        int[] registers = getInputRegisterValues(host, port, 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(String host, Integer port, int slaveId, int offset) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        return getInputRegisterFloatValues(host, port, slaveId, offset, 1)[0];
    }

    public static void setCoilValues(String host, Integer port, int slaveId, int offset, boolean[] values) throws ModbusProtocolException, ModbusNumberException, ModbusIOException, DeviceIOException {
        ModbusMaster master = getMaster(host, port);
        if (values.length > 1968) {
            throw new ModbusNumberException("取值长度必须小于 1968");
        }
        master.writeMultipleCoils(slaveId, offset, values);
    }

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

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

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

    public static List<Object> getValues(DeviceRequest request) throws ModbusProtocolException, ModbusNumberException, DeviceIOException, ModbusIOException {
        String ipAddress = request.getIpAddress();
        Integer port = request.getPort();
        Integer unitId = request.getUnitId();
        Integer offsetOrIndex = request.getOffsetOrIndex();
        Integer lengthOrQos = request.getLengthOrQos();
        switch (request.getResource()) {
            case COIL_FUNCTION_CODE:
                return convertArrayToList(getCoilValues(ipAddress, port, unitId, offsetOrIndex, lengthOrQos));
            case INPUT_FUNCTION_CODE:
                switch (request.getDataType()) {
                    case INT_DATA_TYPE:
                        return convertArrayToList(getInputRegisterValues(ipAddress, port, unitId, offsetOrIndex, lengthOrQos));
                    case FLOAT_DATA_TYPE:
                        return convertArrayToList(getInputRegisterFloatValues(ipAddress, port, unitId, offsetOrIndex, lengthOrQos));
                }
            case HOLDING_FUNCTION_CODE:
                switch (request.getDataType()) {
                    case INT_DATA_TYPE:
                        return convertArrayToList(getHoldingRegisterValues(ipAddress, port, unitId, offsetOrIndex, lengthOrQos));
                    case FLOAT_DATA_TYPE:
                        return convertArrayToList(getHoldingRegistersFloatValues(ipAddress, port, unitId, offsetOrIndex, lengthOrQos));
                }
            default:
                return Collections.emptyList();
        }
    }


    public static Object getValue(DeviceRequest request) throws ModbusProtocolException, ModbusNumberException, DeviceIOException, ModbusIOException {
        return getValues(request).get(0);
    }


    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());
    }

    public static final String ip = "127.0.0.1";
    public static final int port = 22502;
    public static void main(String[] args) throws Exception {
        DeviceRequest request = new DeviceRequest();
        request.setUnitId(2);
        request.setDataType(COIL_DATA_TYPE);
        request.setResource(COIL_FUNCTION_CODE);
        request.setOffsetOrIndex(0);
        request.setLengthOrQos(2);
        request.setIpAddress(ip);
        request.setPort(port);
        List<Object> values = getValues(request);
        for (Object value : values) {
            System.out.println(value);
        }
        disconnect(ip, port);
    }
}
