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

import com.dd.plist.NSDictionary;
import com.dd.plist.PropertyListFormatException;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.UsbMuxdPayload;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.entity.DeviceConnectionDetail;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.request.ConnectDevicePortRequest;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.request.ListDevicesRequest;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.request.ListenDeviceRequest;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.response.ListDeviceResponse;
import net.northking.cctp.upperComputer.driver.usbmuxd.payload.usbmuxd.response.ResultResponse;
import net.northking.cctp.upperComputer.driver.usbmuxd.service.LockdownService;
import org.apache.commons.lang3.StringUtils;
import org.newsclub.net.unix.AFUNIXSocket;
import org.newsclub.net.unix.AFUNIXSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.ArrayList;

/**
 * 对Usbmuxd程序的直接操作类<br>
 * 默认为本机USB多路复用程序，也可通过网络连接其它机器的usbmuxd程序<br>
 */
public class UsbMuxd {

    private final Logger logger = LoggerFactory.getLogger(UsbMuxd.class);

    /**
     * 连接usbmuxd的方式
     */
    private ConnectType connectType;

    /**
     * usbmuxd的unix socket文件所在地址<br>
     * 仅当connnectType为UNIX_SOCKET时有值有效
     */
    private String unixSocketPath = null;

    /**
     * 提供usbmuxd服务的主机地址<br>
     * 仅当connectType为NETWORK时有值有效
     */
    private String host = null;

    /**
     * 提供usbmuxd服务的主机的监听端口<br>
     * 仅当connectType为NETWORK时不为0且有效
     */
    private int port = 0;

    /**
     * 自动根据环境选择连接方式创建UsbMuxd操作实例<br>
     * 如果判断存在/var/run/usbmuxd，则认为是Linux/Unix/macOS环境之一，自动采用UnixSocket方式通信。<br>
     * 否则，则连接iTunes在Windows下监听的默认端口:27015。
     */
    public UsbMuxd() {
        File file = new File("/var/run/usbmuxd");
        if (file.exists()) {
            initWithUnixSocket(file.getAbsolutePath());
        } else {
            initWithNetwork("127.0.0.1", 27015);
        }
    }

    /**
     * 创建UsbMuxd操作实例<br>
     * 连接指定的usbmuxd（UnixSocket方式）
     *
     * @param unixSocketPath unix socket文件地址
     */
    UsbMuxd(String unixSocketPath) {
        initWithUnixSocket(unixSocketPath.trim());
    }

    /**
     * 创建UsbMuxd操作实例<br>
     * 连接指定的usbmuxd（网络方式）
     *
     * @param host 提供usbmuxd服务的地址
     * @param port 提供usbmuxd服务的端口
     */
    UsbMuxd(String host, int port) {
        initWithNetwork(host, port);
    }

    private void initWithUnixSocket(String unixSocketPath) {
        if (unixSocketPath == null) {
            throw new IllegalArgumentException("unixSocketPath不能为null");
        }
        if (unixSocketPath.isEmpty() || StringUtils.isBlank(unixSocketPath)) {
            throw new IllegalArgumentException("unixSocketPath必须有内容");
        }
        File file = new File(unixSocketPath);
        if (!file.exists()) {
            throw new IllegalArgumentException("unix socket文件：[" + unixSocketPath + "]不存在");
        }
        if (!file.canRead()) {
            throw new IllegalArgumentException("unix socket文件：[" + unixSocketPath + "]不可读取");
        }
        if (!file.canWrite()) {
            throw new IllegalArgumentException("unix socket文件：[" + unixSocketPath + "]不可写入");
        }
        connectType = ConnectType.UNIX_SOCKET;
        this.unixSocketPath = unixSocketPath;
    }

    private void initWithNetwork(String host, int port) {
        if (host == null) {
            throw new IllegalArgumentException("host不能为null");
        }
        if (host.isEmpty() || StringUtils.isBlank(host)) {
            throw new IllegalArgumentException("host不能为空内容");
        }
        if (port < 1 || port > 65535) {
            throw new IllegalArgumentException("端口号不符合要求，当前值为:" + port);
        }
        connectType = ConnectType.NETWORK;
        this.host = host;
        this.port = port;
    }

    /**
     * 获取已连接设备信息
     *
     * @return 已连接的设备信息集合
     * @throws UsbMuxdConnectFailedException Usbmuxd连接错误，可能是没有连接任何苹果设备
     * @throws IOException                   输入输出或解析错误
     */
    public ArrayList<DeviceConnectionDetail> listDevices() throws UsbMuxdConnectFailedException, IOException {
        Socket socket = createSocket();

        ListDevicesRequest listDevicePayload = new ListDevicesRequest();
        try (OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream()) {
            listDevicePayload.write(outputStream);
            NSDictionary readData = UsbMuxdPayload.read(inputStream);
            ListDeviceResponse response = new ListDeviceResponse(readData);
            return response.connectionDetails;
        } catch (PropertyListFormatException | ParseException | ParserConfigurationException | SAXException e) {
            throw new IOException("读取出错", e);
        }
    }

    public boolean fullDeviceInfo(AppleDevice appleDevice) {
        LockdownService lockdownService;

        try {
            lockdownService = new LockdownService(this, appleDevice);
        } catch (IOException e) {
            logger.error("与设备" + appleDevice.getConnectionDetail().serialNumber + "的lockdown服务建立连接时发生IO异常", e);
            return false;
        }

        appleDevice.setDeviceName(lockdownService.queryValue(LockdownService.ValueName.DEVICE_NAME));
        appleDevice.setProductType(lockdownService.queryValue(LockdownService.ValueName.PRODUCT_TYPE));
        appleDevice.setProductVersion(lockdownService.queryValue(LockdownService.ValueName.PRODUCT_VERSION));
        appleDevice.setWifiAddress(lockdownService.queryValue(LockdownService.ValueName.WIFI_ADDRESS));
        return true;
    }


    /**
     * 连接到指定设备上的程序监听的本地端口
     *
     * @param appleDevice 要连接的设备
     * @param port        要连接的端口号
     * @return 建立的socket
     * @throws UsbMuxdConnectFailedException Usbmuxd连接错误，可能是没有连接任何苹果设备
     * @throws IOException                   输入输出或解析错误
     */
    public ConnectOperator connectDeviceLocalPort(AppleDevice appleDevice, int port) throws UsbMuxdConnectFailedException, IOException {
        Socket socket = createSocket();

        ConnectDevicePortRequest connectDevicePortPayload = new ConnectDevicePortRequest(appleDevice, port);
        return new ConnectOperator(socket, connectDevicePortPayload);
    }

    /**
     * 开始观察设备连接变动<br>
     * 返回输入流，之后从流中不断读取数据
     *
     * @return 建立的socket，可从InputStream中持续读取设备变动数据
     * @throws UsbMuxdConnectFailedException Usbmuxd连接错误，可能是没有连接任何苹果设备
     * @throws IOException                   输入输出或解析错误
     */
    public ConnectOperator startWatchDevice() throws UsbMuxdConnectFailedException, IOException {
        Socket socket = createSocket();

        ListenDeviceRequest listenDevicePayload = new ListenDeviceRequest();
        return new ConnectOperator(socket, listenDevicePayload);
    }


    /**
     * 根据情况创建与usbmuxd的通信socket
     *
     * @return 与usbmuxd的通信socket
     */
    private Socket createSocket() throws UsbMuxdConnectFailedException, IOException {
        if (connectType == ConnectType.UNIX_SOCKET) {
            File socketFile = new File(unixSocketPath);
            if (!socketFile.exists()) {
                throw new UsbMuxdConnectFailedException("usbmuxd服务不存在", connectType, unixSocketPath, host, port);
            }
            return AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(socketFile));
        } else {
            return new Socket(host, port);
        }
    }


    /**
     * 包装Socket和它的Stream
     */
    public static class ConnectOperator implements AutoCloseable {
        public Socket socket;
        public OutputStream outputStream;
        public InputStream inputStream;

        public ConnectOperator(Socket socket, UsbMuxdPayload initPayload) throws IOException {
            this.socket = socket;
            this.outputStream = socket.getOutputStream();
            this.inputStream = socket.getInputStream();
            initPayload.write(outputStream);

            NSDictionary readData;
            try {
                readData = UsbMuxdPayload.read(inputStream);
            } catch (Exception ignore) {
                throw new IOException("从Usbmuxd读取结果数据时发生输入输出错误");
            }

            ResultResponse resultResponse = new ResultResponse(readData);
            if (!resultResponse.isSuccess()) {
                throw new IOException("Usbmuxd返回失败结果");
            }
        }

        @Override
        public void close() throws Exception {
            socket.close();
        }
    }

    /**
     * 连接到usbmuxd的通信方式
     */
    enum ConnectType {
        /**
         * 通过unix socket方式连接，用于Linux/Unix/macOS连接本机usbmuxd
         */
        UNIX_SOCKET,

        /**
         * 通过网络socket方式连接，需要提供地址和端口号，用于Windows或远程连接
         */
        NETWORK
    }


}
