package com.chinaitop.depot.intelligent.socket;

import com.chinaitop.depot.intelligent.common.ConstantClass;
import com.chinaitop.depot.intelligent.utils.DataUtils;
import com.unissoft.model.TypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author qingsong.han
 * @description: socketServer 服务监听
 * @create 2021-09-06 16:52
 */
@Component
public class SocketServer {
    private final static Logger log = LoggerFactory.getLogger(SocketServer.class);
    /**
     * 硬件配置端口 client 为 ip:2001
     * 测试地址:
     * SocketServer 监听地址为10.100.208.233:2002(衡水深州库固定SocketClient地址为10.100.208.233)
     * 服务器生产环境地址:
     * SocketServer 监听地址为10.100.209.27:2002(衡水深州库固定SocketClient地址为10.100.209.27)
     */
    private static final int port = 0x7D2;
    // 连续请求积压数>1,否则为50
    private static final int backlog = 3;
    // 设置连接超时时间
    private static final int timeout = 0x2BF20;
    // 计数防止死循环
    private int temp = 0;
    private static final int tempCount = 0x40;
    // 本类注入ServerSocket
    private ServerSocket serverSocket;
    private ExecutorService executorService;//线程池
    private final int PoolSize = 8;//单个CPU线程池大小

    public SocketServer() throws IOException {
        /*
         * 获取服务器ip.硬件配置固定地址,所以服务器也必须固定
         * 衡水深州内网固定服务端(DNS):
         * 测试环境
         *  IP地址: 10.100.208.233
         *  子网掩码: 255.255.254.0
         *  默认网关: 10.100.209.254
         * 生产环境(服务器ip地址)
         *  IP地址: 10.100.209.27
         */
        InetAddress localHost = InetAddress.getLocalHost(); // 获取本机ip(监听地址为服务器地址需要PLC修改)
        serverSocket = new ServerSocket(port, backlog, localHost); // 连接请求队列的长度为3
        // //Runtime的availableProcessor()方法返回当前系统的CPU数目
        int i = Runtime.getRuntime().availableProcessors();
        executorService = Executors.newFixedThreadPool(i * PoolSize);
        log.info("SocketServer服务器启动!");
    }

    /**
     * 多线程接收PLC调用回写编码
     *
     * @param type 需要监听PLC动态数据功能类型
     *             TODO 仓上控制柜 1:测气,0:自然窗,0:轴流窗,0:轴流风机,
     *             TODO 仓下控制柜 0:通风口
     */
    public /*synchronized*/ String service(String ip, String orders, int type) {
        String resultPLC = "";
        Socket socket;
        try {
            while (true) {
                serverSocket.setSoTimeout(timeout); // 连接超时时间
                // client 默认端口全部为2001
                socket = serverSocket.accept(); // 从连接请求队列中取出连接
                InetAddress inetAddress = socket.getInetAddress(); // 带斜杠的地址: /10.100.208.209
                log.info("New connection accepted: {}", inetAddress);
                // 判断是否存在client 连接(ip:2001)
                String hostAddress = inetAddress.getHostAddress(); // 去掉/
                if (hostAddress.equals(ip)) {
                    log.info(LocalDateTime.now() + "***连接成功,设备的ip及其端口号: {}", socket.getRemoteSocketAddress());
                    outByte(socket, orders); // 输出指令
                    log.info(LocalDateTime.now() + "***指令发送成功: {}", orders);
                    // 清除PLC控制柜指令,使其断开连接上一条指令(如有其它断开PLC指令使用else if 添加判断)
                    Thread.sleep(ConstantClass.TYPE_SLEEP);
                    int length = orders.length();
                    if (length == ConstantClass.CLEAN_NLL_DOWN.length()) {
                        outByte(socket, ConstantClass.CLEAN_NLL_DOWN);
                        log.info(LocalDateTime.now() + "***PLC断开仓下控制柜指令发送成功:{}", ConstantClass.CLEAN_NLL_DOWN);
                    } else {
                        outByte(socket, ConstantClass.CLEAN_NLL_UP);
                        log.info(LocalDateTime.now() + "***PLC断开仓上控制柜指令发送成功:{}", ConstantClass.CLEAN_NLL_UP);
                    }
                    if (type == 1 && !orders.equals(ConstantClass.TYPE_ON)) {
                        // 创建线程池对象 // TODO 优化方案 1.多线程使用线程池,防止服务器宕机

                        /*ExecutorService executorService = Executors.newFixedThreadPool(16); // 创建固定大小线程池
                        ServerThread serverThread = new ServerThread(socket);
                        // 启动线程等待返回(测气)
                        executorService.submit(serverThread);*/

//                        executorService.execute(new ServerThread(socket));
                        executorService.submit(new ServerThread(socket));

//                        new Thread(new ServerThread(socket)).start(); // 启动线程等待返回(测气)
                        resultPLC = ConstantClass.C_SOH_S;
                    } else {
                        try {
                            resultPLC = inputByte(socket); // 单线程直接接收返回
                        } finally {
                            socket.close();
                        }
                    }
                    break;
                }
                temp++;
                if (temp == tempCount) {
                    log.info("设备IP配置有误,与数据库没有匹配的设备");
                    resultPLC = ConstantClass.C_SOH_10039;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultPLC;
    }

    /**
     * 发送socket输出流指令编码
     *
     * @param socket tcp
     * @param orders 十六进制字符串编码
     * @throws IOException
     */
    private void outByte(Socket socket, String orders) throws IOException {
        // 字符串转十六进制byte数组
        byte[] bytes = DataUtils.hexStringToByteArray(orders);
        // socket输出流
        OutputStream os = socket.getOutputStream();
        os.write(bytes);
        os.flush();
    }

    /**
     * TODO 未使用 没有返回编码解释
     * TODO 已测出: 成功: 01, 启动或关闭超时: A3 本地模式: 54, 55: 开到位(单个开) ,53: 关到位(关单个),4D:已是当前状态
     * TODO 注: 如何远程清除故障
     * <p>
     * 适用于单次请求
     *
     * @param socket 接收PLC调用回写编码
     * @return 十六进制2位格式
     * @throws IOException io流
     */
    private String inputByte(Socket socket) throws IOException {
        // socket 接收返回输入信号
        InputStream inputStream = socket.getInputStream();
        int read = inputStream.read(); // 默认读取长度为(-1~255)
        log.info(LocalDateTime.now() + "***接收到PLC返回编码(十进制): {}", read);
        // 十进制转十六进制 占位2 并格式化大写(示例: 01)
        String result = String.format("%02x", read).toUpperCase();
        log.info(LocalDateTime.now() + "***接收到PLC返回编码(十六进制): {}", result);
        return result;
    }

    /**
     * PLC 返回结果处理(衡水深州库PLC) 已知: 成功: 01, 启动或关闭超时: A3 本地模式: 54
     *
     * @param hexString 十六进制字符串
     * @return 字符串明文解释
     */
    public String responseResultForPLC(String hexString) {
        switch (hexString) {
            case ConstantClass.C_SOH:
                return TypeEnum.C_SOH.getDesc();
            case ConstantClass.C_SOH_TO:
                return TypeEnum.C_SOH_TO.getDesc();
            case ConstantClass.C_SOH_L:
                return TypeEnum.S_2D_SOH.getDesc();
            case ConstantClass.C_SOH_z:
                return TypeEnum.S_2D_SOH.getDesc(); // 仓上控制柜00 表示本地模式, 01 表示远程模式
            case ConstantClass.C_SOH_S:
                return TypeEnum.S_SOH.getDesc(); // 检测开始
            case ConstantClass.C_SOH_10039:
                return TypeEnum.S_STX.getDesc(); // 通信异常
            default:
                return TypeEnum.C_SOH.getDesc();
        }
    }

}
