package net.northking.cctp.upperComputer.driver.proxy;


import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager;
import net.northking.cctp.upperComputer.deviceManager.thread.WindowsAndLinuxIosDeviceInitThread;
import net.northking.cctp.upperComputer.driver.usbmuxd.AppleDevice;
import net.northking.cctp.upperComputer.driver.usbmuxd.UsbMuxd;
import net.northking.cctp.upperComputer.driver.usbmuxd.UsbMuxdConnectFailedException;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.entity.DeviceConnectionDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 专门用于转发WebDriverAgent接口的逻辑<br>
 * 设备断开连接时请求，会自动等待到设备重连并自动再次传递请求数据。<br>
 */

public class WebDriverAgentApiProxy {

    private static Logger log = LoggerFactory.getLogger(WebDriverAgentApiProxy.class);
    /**
     * WebDriverAgent api监听端口
     */
    private static final int WEB_DRIVER_AGENT_API_PORT = 8100;

    private static final String PORT_REGEX = "^(((25[0-5]|2[0-4]d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))\\:([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$";

    /**
     * 要转发的设备序列号
     */
    private final String udid;

    /**
     * 转发监听的端口号
     */
    private final int listenPort;

    private ProxyThread proxyThread;

    private ServerSocket socketSocket;

    public WebDriverAgentApiProxy(String udid, int listenPort) {
        this.udid = udid;
        this.listenPort = listenPort;
    }

    /**
     * 开始监听端口并接受连接
     */
    public void startListen() {
        try  {
            socketSocket= new ServerSocket(listenPort);
            log.info("监听端口：" + listenPort + " 通向设备[" + udid + "] 端口：" + WEB_DRIVER_AGENT_API_PORT);
            ((WindowsAndLinuxIosDeviceInitThread)IOSDeviceManager.getInstance().getIosInitThread(udid)).apiPortReady();
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Socket socket = socketSocket.accept();
                    proxyThread = new ProxyThread();
                    proxyThread.requestSocket = socket;
                    proxyThread.inputStream = socket.getInputStream();
                    proxyThread.outputStream = socket.getOutputStream();
                    proxyThread.start();
                } catch (IOException e) {
                    log.warn("设备【{}】8100代理端口【{}】通信异常",udid,listenPort,e);
                    break;
                }
            }
            log.warn("设备【{}】8100代理端口【{}】已释放",udid,listenPort);
        } catch (IOException e) {
            log.error("监听端口发生错误", e);
            if (e instanceof BindException) {
                log.info("端口：{}已经被占用，干掉重新启动",listenPort);
                killProxyPort();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException interruptedException) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
    }

    private void killProxyPort() {
        if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Windows")) {  //windows上位机
            Set<String> process = findWindowPortProcess();
            releaseWindowsPortByPid(process);
        } else if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Linux")) {
            Set<String> process = findLinuxPortProcess();
            releaseLinuxPortByPid(process);
        } else {
            log.warn("不知上位机系统系统:{}.....",UpperComputerManager.getInstance().getOperatingSystem());
        }
    }

    private void releaseLinuxPortByPid(Set<String> process) {
        List<String> killCmds = new ArrayList<>(3);
        killCmds.add(0,"kill");
        killCmds.add(1,"-9");
        if (!CollectionUtils.isEmpty(process)) {
            ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true);
            for (String singleProcess : process) {
                log.info("诛杀进程：{}",singleProcess);
                killCmds.add(2, singleProcess);
                Process pro = null;
                try {
                    pro = processBuilder.command(killCmds).start();
                    pro.waitFor();
                } catch (IOException e) {
                    log.warn("杀进程：{}出现错误，失败..........",singleProcess,e);
                } catch (InterruptedException e) {
                    log.warn("线程终止了......");
                    break;
                }
            }
        }
    }

    private Set<String> findLinuxPortProcess() {
        Set<String> processSet = new HashSet<>();
        List<String> findCmds = new ArrayList<>(5);
        findCmds.add("lsof");
        findCmds.add("-i:"+listenPort);
        LineNumberReader inputStream = null;
        try {
            Process process = new ProcessBuilder(findCmds).redirectErrorStream(true).start();
            inputStream = new LineNumberReader(new InputStreamReader(process.getInputStream(),"UTF-8"));
            String line = null;
            List<List<String>> resultProcess = new ArrayList<>();
            while (null != (line = inputStream.readLine())) {
                log.debug("设备【{}】端口打印数据：{}", udid, line);
                String[] s = line.split(" ");
                List<String> lineResult = new ArrayList<>();
                for (String item : s) {
                    if (StringUtils.hasText(item)) {
                        lineResult.add(item);
                    }
                }
                resultProcess.add(lineResult);
            }
            int index = 0;
            for (int i = 0; i < resultProcess.size(); i++) {
                List<String> list = resultProcess.get(i);
                if (i == 0) {
                    for (int j = 0; j < list.size(); j++) {
                        if ("PID".equalsIgnoreCase(list.get(j))) {
                            index = j;
                            log.debug("第{}个是pid",index+1);
                            break;
                        }
                    }
                } else {
                    processSet.add(list.get(index));
                }
            }
        } catch (IOException e) {
            log.info("设备：{}，执行端口占用命令失败",udid,e);
        }finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return processSet;
    }

    private void releaseWindowsPortByPid(Set<String> process) {
        List<String> killCmds = new ArrayList<>(4);
        killCmds.add(0,"taskkill");
        killCmds.add(1,"/F");
        killCmds.add(2,"/PID");
        if (!CollectionUtils.isEmpty(process)) {
            ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true);
            for (String singleProcess : process) {
                log.info("诛杀进程：{}",singleProcess);
                killCmds.add(3, singleProcess);
                Process pro = null;
                try {
                    pro = processBuilder.command(killCmds).start();
                    pro.waitFor();
                } catch (IOException e) {
                    log.warn("杀进程：{}出现错误，失败..........",singleProcess,e);
                } catch (InterruptedException e) {
                    log.warn("线程终止了......");
                    break;
                }
            }
        }
    }

    private Set<String> findWindowPortProcess() {
        List<String> findCmds = new ArrayList<>(5);
        findCmds.add("cmd");
        findCmds.add("/c");
        findCmds.add("netstat");
        findCmds.add("-ano|findstr");
//        findCmds.add("|");
//        findCmds.add("");
        findCmds.add(listenPort+"");
        Set<String> processList = new HashSet<>();
        Pattern pattern = Pattern.compile(PORT_REGEX);
        LineNumberReader inputStream = null;
        try {
            Process process = new ProcessBuilder(findCmds).redirectErrorStream(true).start();
            inputStream = new LineNumberReader(new InputStreamReader(process.getInputStream(),"GBK"));
            String line = null;
            while (null != (line = inputStream.readLine())) {
                log.debug("设备【{}】端口打印数据：{}", udid, line);
                String[] s = line.split(" ");
                    for (int i = 0; i < s.length; i++) {
                        Matcher matcher = pattern.matcher(s[i]);
                        if (matcher.find()) {
                            log.debug("监听信息：{}",s[i]);
                            String port = s[i].substring(s[i].lastIndexOf(":") + 1);
                            if (port.equals(listenPort+"")) {
                                processList.add(s[s.length - 1]);
                                break;
                            }
                        }
                    }
            }
            log.info("设备【{}】端口【{}】释放已完成",udid,listenPort);
        } catch (IOException e) {
            log.info("设备：{}，执行端口占用或者杀掉命令失败",udid,e);
        }finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return processList;
    }

    public void stop8100Proxy() {
        if (null != this.socketSocket && !this.socketSocket.isClosed()) {
            try {
                this.socketSocket.close();
            } catch (IOException e) {
                log.warn("设备【{}】的8100端口关闭异常。。。。。。。。。。。。。。",udid,e);
            }
            this.socketSocket = null;
        }
    }

    private class ProxyThread extends Thread {

        Socket requestSocket;

        InputStream inputStream;

        OutputStream outputStream;

        @Override
        public void run() {
            UsbMuxd.ConnectOperator operator = null;

            UsbMuxd usbmuxd = new UsbMuxd();
            try {
                List<DeviceConnectionDetail> connectionDetailList = usbmuxd.listDevices();
                for (DeviceConnectionDetail detail : connectionDetailList) {
                    if (detail.serialNumber.equalsIgnoreCase(udid) && detail.connectStatus == DeviceConnectionDetail.ConnectStatus.ATTACHED) {
                        AppleDevice appleDevice = new AppleDevice(detail);
                        try {
                            operator = usbmuxd.connectDeviceLocalPort(appleDevice, WEB_DRIVER_AGENT_API_PORT);
                        } catch (IOException e) {
                            log.warn("设备已连接，但无法连接WebDriverAgent端口:" + WEB_DRIVER_AGENT_API_PORT);
                        }
                        break;
                    }
                }
                if (operator == null) {
                    log.warn("未能建立与设备[" + udid + "]建立端口" + WEB_DRIVER_AGENT_API_PORT + "的连接，WDA已启动？");
                } else {
//                    log.info("已建立与设备[" + udid + "]建立端口" + WEB_DRIVER_AGENT_API_PORT + "的连接");
                    ProxyOutputStreamThread outputThread = new ProxyOutputStreamThread();
                    outputThread.connectOperator = operator;
                    outputThread.outputStream = outputStream;
                    outputThread.requestSocket = requestSocket;
                    outputThread.parentThread = this;
                    outputThread.start();
                    byte[] buffer = new byte[4096];
                    while (!isInterrupted() && !requestSocket.isClosed() && !operator.socket.isClosed()) {
                        try {
                            int count = inputStream.read(buffer);
                            if (count > 0) {
                                operator.outputStream.write(buffer, 0, count);
                            }
                            if (inputStream.available() == 0) {
                                operator.outputStream.flush();
                            }
                            if (count == -1) {
                                requestSocket.close();
                                operator.socket.close();
                            }
                        } catch (SocketTimeoutException ignore) {

                        } catch (Exception e) {
                            break;
                        }
                    }
                    outputThread.interrupt();
                }
            } catch (UsbMuxdConnectFailedException e) {
                log.warn("无法连接到usbmuxd", e);
            } catch (IOException e) {
                log.warn("发生输入输出错误，可能无法连接usbmuxd，确定连接了设备？", e);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (!requestSocket.isClosed()) {
                    try {
                        requestSocket.close();
                    } catch (IOException ignored) {

                    }
                }
                if (operator != null) {
                    try {
//                        log.info("关闭与设备[" + udid + "]的连接");
                        operator.close();
                    } catch (Exception ignored) {

                    }
                }

            }
//            log.info("客户访问结束" + requestSocket.getRemoteSocketAddress().toString());
        }

    }

    private static class ProxyOutputStreamThread extends Thread {

        Socket requestSocket;

        OutputStream outputStream;

        UsbMuxd.ConnectOperator connectOperator;

        Thread parentThread;

        @Override
        public void run() {
            byte[] buffer = new byte[4096];
            while (!isInterrupted() && requestSocket.isConnected() && connectOperator.socket.isConnected()) {
                try {
                    int count = connectOperator.inputStream.read(buffer);
                    if (count > 0) {
                        outputStream.write(buffer, 0, count);
                    } else if (count == -1) {
                        break;
                    }
                } catch (IOException e) {
                    break;
                }
            }
//            log.info("与设备的通信结束");
            try {
                connectOperator.close();
            } catch (Exception ignored) {
            }
            try {
                requestSocket.close();
            } catch (IOException ignored) {
            }
        }
    }
}
