package net.northking.cctp.upperComputer.deviceManager.common;


import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import net.northking.cctp.upperComputer.deviceManager.entity.*;
import net.northking.cctp.upperComputer.deviceManager.utils.OutputLineCallback;
import net.northking.cctp.upperComputer.driver.usbmuxd.AppleDevice;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;

/**
 * 对pymobiledevice3的调用封装
 */
public class PyMobileDevice extends Python3 {

    private Logger logger = LoggerFactory.getLogger(PyMobileDevice.class);

    private static final String MODULE_NAME = "pymobiledevice3";

    private static final Pattern IPV6_PORT_PATTERN =
            Pattern.compile("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\\\\b|%[0-9a-zA-Z]{1,})?$");
    private static PyMobileDevice instance = null;

    public static PyMobileDevice getInstance() {
        if (instance == null) {
            instance = new PyMobileDevice();
        }
        return instance;
    }

    private final Gson gson = new Gson();


    private PyMobileDevice() {
    }

    @Override
    public String getModuleName() {
        return MODULE_NAME;
    }

    /**
     * 检查运行环境
     *
     * @return 是否检查通过
     */
    @Override
    public boolean checkEnvironment() {
        OutputReceiver outputReceiver = new OutputReceiver();
        String[] command = createModuleCommand("version");
        if (!runModule(command, outputReceiver)) {
            logger.error("获取pymobiledevice3版本失败");
            return false;
        }
        if (outputReceiver.getAllOutput().isEmpty()) {
            logger.error("尝试获取版本时pymobiledevice3没有输出任何内容");
            return false;
        }
        if (!outputReceiver.getAllOutput().get(0).startsWith("4.")) {
            logger.error("不支持的pymobiledevice3版本：" + outputReceiver.getAllOutput().get(0));
            return false;
        }
        return true;
    }

    /**
     * 创建iOS 17通信隧道
     * <br>
     * 低于17的不会创建
     *
     * @param deviceInfo 指定设备用的信息，隧道信息也将被填充到此对象内
     * @return 是否成功
     */
    public boolean createDeviceTunnel(SpecificAppleDeviceInfo deviceInfo) {
        if (!deviceInfo.appleDevice.isNeedTunnel()) return true;
        final Object lock = new Object();
        AtomicReference<String> address = new AtomicReference<>(null);
        DaemonRunner runner = new DaemonRunner((line) -> {
            if (IPV6_PORT_PATTERN.matcher(line).matches()) {
                if (line != null) {
                    address.set(line);
                }
                synchronized (lock) {
                    lock.notifyAll();
                }
            }
        }, true, "remote", "start-tunnel", "--script-mode", "--udid", deviceInfo.getUdid());
        runner.start();
        synchronized (lock) {
            if (address.get() == null) {
                try {
                    lock.wait(20 * 1000);
                } catch (InterruptedException ignored) {
                    return false;
                }
            }
        }
        String fullAddress = address.get();
        if (fullAddress == null) {
            logger.error("无法获取tunnel地址");
            return false;
        }
        String[] infos = fullAddress.split(" ");
        if (infos.length != 2) {
            logger.error("无法将字符串[" + fullAddress + "]按空格分割为2组");
            return false;
        }
        String host = infos[0];
        if (host.length() < 12) {
            logger.error("获取ipv6地址时结果太短：" + host);
            return false;
        }
        int port = 0;
        try {
            port = Integer.parseInt(infos[1]);
        } catch (NumberFormatException e) {
            logger.error("无法将文本" + infos[1] + "转换为端口号");
            return false;
        }
        deviceInfo.setIpv6Address(host);
        deviceInfo.setIpv6Port(port);
        return true;

    }

    public DaemonRunner watchSystemLog(SpecificAppleDeviceInfo deviceInfo, OutputLineCallback callback) {
        ArrayList<String> args = createArgs("syslog", "live-old");
        deviceInfo.appendArgs(args);
        DaemonRunner runner = new DaemonRunner(callback, false, args);
        runner.start();
        return runner;
    }

    /**
     * 屏幕截图
     *
     * @param deviceInfo  指定的设备的信息
     * @param workDirPath 工作文件夹，pymobiledevice3会先将图片写入磁盘
     * @return 截图内容，null则为截图失败
     */
    public byte[] takeScreenshot(SpecificAppleDeviceInfo deviceInfo, String workDirPath) {
        File workDir = new File(workDirPath);
        if (!workDir.exists()) {
            if (!workDir.mkdirs()) {
                logger.error("pymobiledevice3截图前创建工作文件夹失败");
                return null;
            }
        }
        if (!workDir.isDirectory()) {
            logger.error("指定的pymobiledevice3截图工作文件夹路径不是一个文件夹");
            return null;
        }
        if (!workDir.canWrite()) {
            logger.error("pymobiledevice3截图前发现工作文件夹无法写入");
            return null;
        }
        if (workDir.getFreeSpace() < 10 * 1024 * 1024) {
            logger.error("pymobiledevice3截图前发现工作文件夹所在磁盘可用空间小于10MiB");
            return null;
        }
        String cacheFileName = "pymobiledevice3-screenshot-" + deviceInfo.udid + "-" + System.currentTimeMillis() + ".png";
        ArrayList<String> args = createArgs("developer", "dvt", "screenshot");
        deviceInfo.appendArgs(args);
        args.add(workDir.getAbsolutePath() + "/" + cacheFileName);
        boolean result = runModule(args, null);
        if (!result) {
            logger.error("执行pymobiledevice3截图失败");
            return null;
        }
        File cacheFile = new File(workDir.getAbsolutePath() + "/" + cacheFileName);
        if (!cacheFile.exists()) {
            logger.error("pymobiledevic3截图后无法找到截图文件：" + cacheFile.getAbsolutePath());
            return null;
        }
        if (!cacheFile.canRead()) {
            logger.error("pymobiledevice3截图后无法读取截图文件：" + cacheFile.getAbsolutePath());
            deleteCacheFile(cacheFile);
            return null;
        }
        if (cacheFile.length() < 10) {
            logger.error("pymobiledevice3截图出来的文件太小，无效，大小：" + cacheFile.length());
            deleteCacheFile(cacheFile);
            return null;
        }
        byte[] bytes;

        try {
            bytes = Files.readAllBytes(cacheFile.toPath());
        } catch (IOException e) {
            logger.error("读取pymobiledevice3截图文件时发生IO错误", e);
            deleteCacheFile(cacheFile);
            return null;
        }
        deleteCacheFile(cacheFile);

        return bytes;
    }

    /**
     * 屏幕截图
     *
     * @param deviceInfo  指定的设备的信息
     * @param workDirPath 工作文件夹，pymobiledevice3会先将图片写入磁盘
     * @return 截图内容，null则为截图失败
     */
    public File takeScreenshotSaveAsFile(SpecificAppleDeviceInfo deviceInfo, String workDirPath) {
        File workDir = new File(workDirPath);
        if (!workDir.exists()) {
            if (!workDir.mkdirs()) {
                logger.error("pymobiledevice3截图前创建工作文件夹失败");
                return null;
            }
        }
        if (!workDir.isDirectory()) {
            logger.error("指定的pymobiledevice3截图工作文件夹路径不是一个文件夹");
            return null;
        }
        if (!workDir.canWrite()) {
            logger.error("pymobiledevice3截图前发现工作文件夹无法写入");
            return null;
        }
        if (workDir.getFreeSpace() < 10 * 1024 * 1024) {
            logger.error("pymobiledevice3截图前发现工作文件夹所在磁盘可用空间小于10MiB");
            return null;
        }
        String cacheFileName = "pymobiledevice3-screenshot-" + deviceInfo.udid + "-" + System.currentTimeMillis() + ".png";
        ArrayList<String> args = createArgs("developer", "dvt", "screenshot");
        deviceInfo.appendArgs(args);
        args.add(workDir.getAbsolutePath() + "/" + cacheFileName);
        boolean result = runModule(args, null);
        if (!result) {
            logger.error("执行pymobiledevice3截图失败");
            return null;
        }
        File cacheFile = new File(workDir.getAbsolutePath() + "/" + cacheFileName);
        if (!cacheFile.exists()) {
            logger.error("pymobiledevic3截图后无法找到截图文件：" + cacheFile.getAbsolutePath());
            return null;
        }
        if (!cacheFile.canRead()) {
            logger.error("pymobiledevice3截图后无法读取截图文件：" + cacheFile.getAbsolutePath());
            deleteCacheFile(cacheFile);
            return null;
        }
        return cacheFile;
    }

    /**
     * 获取设备壁纸
     * <br>
     * png格式
     *
     * @param deviceInfo  指定设备信息
     * @param workDirPath 工作文件夹，pymobiledevice3会先将图片写入磁盘
     * @return 壁纸图片内容，null则为获取失败
     */
    public byte[] wallpaper(SpecificAppleDeviceInfo deviceInfo, String workDirPath) {
        File workDir = new File(workDirPath);
        if (!workDir.exists()) {
            if (!workDir.mkdirs()) {
                logger.error("pymobiledevice3获取桌面壁纸前创建工作文件夹失败");
                return null;
            }
        }
        if (!workDir.isDirectory()) {
            logger.error("指定的pymobiledevice3获取桌面壁纸工作文件夹路径不是一个文件夹");
            return null;
        }
        if (!workDir.canWrite()) {
            logger.error("pymobiledevice3获取桌面壁纸前发现工作文件夹无法写入");
            return null;
        }
        if (workDir.getFreeSpace() < 10 * 1024 * 1024) {
            logger.error("pymobiledevice3获取桌面壁纸前发现工作文件夹所在磁盘可用空间小于10MiB");
            return null;
        }
        String cacheFileName = "pymobiledevice3-wallpaper-" + deviceInfo.udid + "-" + System.currentTimeMillis() + ".png";
        ArrayList<String> args = createArgs("springboard", "wallpaper");
        deviceInfo.appendArgs(args);
        args.add(workDir.getAbsolutePath() + "/" + cacheFileName);
        boolean result = runModule(args, null);
        if (!result) {
            logger.error("执行pymobiledevice3获取桌面壁纸失败");
            return null;
        }
        File cacheFile = new File(workDir.getAbsolutePath() + "/" + cacheFileName);
        if (!cacheFile.exists()) {
            logger.error("pymobiledevic3获取桌面壁纸后无法找到壁纸文件：" + cacheFile.getAbsolutePath());
            return null;
        }
        if (!cacheFile.canRead()) {
            logger.error("pymobiledevice3获取桌面壁纸后无法读取壁纸文件：" + cacheFile.getAbsolutePath());
            deleteCacheFile(cacheFile);
            return null;
        }
        if (cacheFile.length() < 10) {
            logger.error("pymobiledevice3获取桌面壁纸的文件太小，无效，大小：" + cacheFile.length());
            deleteCacheFile(cacheFile);
            return null;
        }
        byte[] bytes;

        try {
            bytes = Files.readAllBytes(cacheFile.toPath());
        } catch (IOException e) {
            logger.error("读取pymobiledevice3获取桌面壁纸文件时发生IO错误", e);
            deleteCacheFile(cacheFile);
            return null;
        }
        deleteCacheFile(cacheFile);

        return bytes;
    }

    /**
     * 获取应用图标
     * <br>
     * png格式
     *
     * @param deviceInfo  指定设备信息
     * @param bundleId    App包名称
     * @param workDirPath 工作文件夹，pymobiledevice3会先将图片写入磁盘
     * @return 应用图标内容，null则为获取失败
     */
    public byte[] appIcon(SpecificAppleDeviceInfo deviceInfo, String bundleId, String workDirPath) {
        File workDir = new File(workDirPath);
        if (!workDir.exists()) {
            if (!workDir.mkdirs()) {
                logger.error("pymobiledevice3获取应用图标前创建工作文件夹失败");
                return null;
            }
        }
        if (!workDir.isDirectory()) {
            logger.error("指定的pymobiledevice3获取应用图标工作文件夹路径不是一个文件夹");
            return null;
        }
        if (!workDir.canWrite()) {
            logger.error("pymobiledevice3获取应用图标前发现工作文件夹无法写入");
            return null;
        }
        if (workDir.getFreeSpace() < 10 * 1024 * 1024) {
            logger.error("pymobiledevice3获取应用图标前发现工作文件夹所在磁盘可用空间小于10MiB");
            return null;
        }
        String cacheFileName = "pymobiledevice3-icon-" + deviceInfo.udid + "-" + System.currentTimeMillis() + ".png";
        ArrayList<String> args = createArgs("springboard", "icon");
        deviceInfo.appendArgs(args);
        args.add(bundleId);
        args.add(workDir.getAbsolutePath() + "/" + cacheFileName);
        boolean result = runModule(args, null);
        if (!result) {
            logger.error("执行pymobiledevice3获取应用图标失败");
            return null;
        }
        File cacheFile = new File(workDir.getAbsolutePath() + "/" + cacheFileName);
        if (!cacheFile.exists()) {
            logger.error("pymobiledevic3获取应用图标后无法找到壁纸文件：" + cacheFile.getAbsolutePath());
            return null;
        }
        if (!cacheFile.canRead()) {
            logger.error("pymobiledevice3获取应用图标后无法读取壁纸文件：" + cacheFile.getAbsolutePath());
            deleteCacheFile(cacheFile);
            return null;
        }
        if (cacheFile.length() < 10) {
            logger.error("pymobiledevice3获取应用图标的文件太小，无效，大小：" + cacheFile.length());
            deleteCacheFile(cacheFile);
            return null;
        }
        byte[] bytes;

        try {
            bytes = Files.readAllBytes(cacheFile.toPath());
        } catch (IOException e) {
            logger.error("读取pymobiledevice3获取应用图标文件时发生IO错误", e);
            deleteCacheFile(cacheFile);
            return null;
        }
        deleteCacheFile(cacheFile);

        return bytes;
    }

    /**
     * 获取App列表
     *
     * @param deviceInfo 要查询的设备的信息
     * @param systemApp  是否查询系统App
     * @param userApp    是否查询用户App
     * @return App信息集合
     */
    public HashMap<String, AppleApplicationInfo> listApp(SpecificAppleDeviceInfo deviceInfo, boolean systemApp, boolean userApp) {
        if (!systemApp && !userApp) return new HashMap<>();//啥也不要直接返回空
        ArrayList<String> args = createArgs("apps", "list", "--no-color");
        if (systemApp) {
            args.add("-t");
            args.add("System");
        }
        if (userApp) {
            args.add("-t");
            args.add("User");
        }
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("运行pymobiledevice3获取App列表失败");
            return new HashMap<>();
        }
        int allSize = 0;
        for (String s : outputReceiver.getAllOutput()) {
            allSize += s.length();
        }
        StringBuilder builder = new StringBuilder(allSize);
        for (String s : outputReceiver.getAllOutput()) {
            builder.append(s);
        }
        String jsonText = builder.toString();
        HashMap<String, AppleApplicationInfo> applicationInfos = new HashMap<>();
        try {
            if (null != jsonText && !jsonText.startsWith("{")) {
                jsonText = jsonText.substring(jsonText.indexOf("{"));
            }
            applicationInfos = gson.fromJson(jsonText, new TypeToken<HashMap<String, AppleApplicationInfo>>() {
            }.getType());
        } catch (JsonSyntaxException e) {
            logger.error("解析pymobiledevice3返回的App列表数据时发生解析错误：\n" + jsonText, e);
        }

        return applicationInfos;
    }

    public boolean installApp(SpecificAppleDeviceInfo deviceInfo, String ipaPath, ProgressCallback progressCallback) {
        if (ipaPath == null) {
            logger.error("ipaPath传了个null");
            return false;
        }
        File file = new File(ipaPath);
        if (!file.exists()) {
            logger.error("要安装的ipa文件不存在：" + file.getAbsolutePath());
            return false;
        }
        if (!file.isFile()) {
            logger.error("要安装的ipa不是一个文件：" + file.getAbsolutePath());
            return false;
        }
        if (!file.canRead()) {
            logger.error("要安装的ipa无法读取：" + file.getAbsolutePath());
            return false;
        }
        ArrayList<String> args = createArgs("apps", "install");
        deviceInfo.appendArgs(args);
        args.add(file.getAbsolutePath());
        OutputReceiver outputReceiver = createOutputReceiverIfProgressCallbackNotNull(progressCallback);
        boolean runResult = runModule(args, outputReceiver);

        if (!runResult) {
            logger.error("安装ipa失败：" + file.getAbsolutePath());
            if (progressCallback != null) {
                progressCallback.onFailed();
            }
            return false;
        }

        if (progressCallback != null) {
            progressCallback.onSuccess();
        }
        return true;
    }

    /**
     * 运行指定App
     *
     * @param deviceInfo 指定设备信息
     * @param bundleId   App包名称
     * @return 执行是否成功，不代表App一定启动了（可能无界面，可能直接崩溃，可能无权运行）
     */
    public boolean launchApp(SpecificAppleDeviceInfo deviceInfo, String bundleId) {
        ArrayList<String> args = createArgs("developer", "dvt", "launch");
        deviceInfo.appendArgs(args);
        args.add(bundleId);

        boolean runResult = runModule(args, null);
        if (!runResult) {
            logger.error("执行运行App失败，参数：" + args);
            return false;
        }
        return true;
    }

    /**
     * 杀死指定包名称的App
     *
     * @param deviceInfo 指定设备信息
     * @param bundleId   App包名称
     * @return 执行是否成功，不代表一定有进程被杀死
     */
    public boolean pkill(SpecificAppleDeviceInfo deviceInfo, String bundleId) {
        ArrayList<String> args = createArgs("developer", "dvt", "pkill");
        deviceInfo.appendArgs(args);
        args.add("--bundle");
        args.add(bundleId);
        boolean runResult = runModule(args, null);
        if (!runResult) {
            logger.error("执行杀死App失败，参数：" + args);
            return false;
        }
        return true;
    }

    /**
     * 卸载App
     *
     * @param deviceInfo       指定设备的信息
     * @param bundleId         要卸载的包名称
     * @param progressCallback 进度监听
     * @return 操作是否成功，不代表App一定卸掉了（可以再获取一次已安装列表来检查是否真的卸掉了）
     */
    public boolean uninstallApp(SpecificAppleDeviceInfo deviceInfo, String bundleId, ProgressCallback progressCallback) {
        ArrayList<String> args = createArgs("apps", "uninstall");
        deviceInfo.appendArgs(args);
        args.add(bundleId);
        OutputReceiver outputReceiver = createOutputReceiverIfProgressCallbackNotNull(progressCallback);
        boolean runResult = runModule(args, outputReceiver);

        if (!runResult) {
            logger.error("执行卸载App失败，参数：" + args);
            if (progressCallback != null) {
                progressCallback.onFailed();
            }
            return false;
        }
        if (progressCallback != null) {
            progressCallback.onSuccess();
        }
        return true;
    }

    /**
     * 获取进程信息
     *
     * @param deviceInfo 指定设备信息
     * @return 进程信息集合
     */
    public ArrayList<ProcessInfo> ps(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("processes", "ps", "--no-color");
        deviceInfo.appendArgs(args);
        ArrayList<ProcessInfo> result = new ArrayList<ProcessInfo>();
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("获取进程信息执行失败");
            return result;
        }
        try {
            JsonObject rootObject = gson.fromJson(outputReceiver.getHoleOutput(false), JsonObject.class);
            for (String pidText : rootObject.keySet()) {
                try {
                    int pid = Integer.parseInt(pidText);
                    JsonObject obj = rootObject.getAsJsonObject(pidText);
                    if (!obj.has("ProcessName")) {
                        continue;
                    }
                    ProcessInfo processInfo = new ProcessInfo(pid, obj.get("ProcessName").getAsString());
                    result.add(processInfo);
                } catch (Exception e) {
                    logger.error("处理进程信息时发生异常", e);
                }

            }
        } catch (JsonSyntaxException e) {
            logger.error("获取进程信息时json解析失败：\n" + outputReceiver.getHoleOutput(false), e);
        }

        return result;
    }

    /**
     * 获得设备基本信息
     *
     * @param deviceInfo 指定设备的信息
     * @return 设备基本信息
     */
    public DeviceBaseInfo deviceBaseInfo(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("lockdown", "info", "--no-color");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("执行获取设备基本信息失败");
            return null;
        }
        String jsonText = outputReceiver.getHoleOutput(false);
        try {
            if (null != jsonText && !jsonText.startsWith("{")) {
                jsonText = jsonText.substring(jsonText.indexOf("{"));
            }
            return gson.fromJson(jsonText, DeviceBaseInfo.class);
        } catch (JsonSyntaxException e) {
            logger.error("读取设备基本信息时发生json解析错误：\n" + jsonText, e);
        }
        return null;
    }

    /**
     * 观察性能信息
     *
     * @param deviceInfo 指定设备信息
     * @param callback   性能数据回调
     * @return 运行的程序
     */
    public DaemonRunner watchDeveloperDvtGraphics(SpecificAppleDeviceInfo deviceInfo, DeveloperDvtGraphicsCallback callback) {
        if (callback == null) throw new IllegalArgumentException("callback不能为null");
        ArrayList<String> args = createArgs("developer", "dvt", "graphics");
        deviceInfo.appendArgs(args);
        DaemonRunner runner = new DaemonRunner(line -> {
            if (line == null) {
                callback.onData(null);
                return;
            }
            int startIndex = line.indexOf("INFO ");
            if (startIndex == -1) {
                return;
            }
            String jsonText = line.substring(startIndex + 5);
            if (null != jsonText && !jsonText.startsWith("{")) {
                jsonText = jsonText.substring(jsonText.indexOf("{"));
            }
            if (jsonText.startsWith("{") && jsonText.endsWith("}")) {
                try {
                    DeveloperDvtGraphics data = gson.fromJson(jsonText, DeveloperDvtGraphics.class);
                    callback.onData(data);
                } catch (JsonSyntaxException e) {
                    logger.error("反序列化性能数据时发生JSON格式错误，JSON:\n" + jsonText, e);
                }

            } else {
                logger.debug("分割日志中的JSON出错，分割结果：" + jsonText);
            }
        }, true, args);
        runner.start();
        return runner;
    }

    /**
     * 获取屏幕方向
     *
     * @param info 指定设备信息
     * @return 屏幕方向, null为获取失败
     */
    public Orientation screenOrientation(SpecificAppleDeviceInfo info) {
        ArrayList<String> args = createArgs("springboard", "orientation");
        info.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("运行获取屏幕方向失败");
            return null;
        }
        String text = outputReceiver.getHoleOutput(false);
        if (text.contains("PORTRAIT")) {
            return Orientation.PORTRAIT;
        }
        return Orientation.LANDSCAPE;
    }

    /**
     * 获取设备电池详情
     * <br>
     * 厂商、规格、序列号、电压、寿命……
     *
     * @param deviceInfo 指定设备信息
     * @return 电池详细信息，nul为获取失败
     */
    public BatteryDetail batteryDetail(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("diagnostics", "battery");
        args.add("single");
        args.add("--no-color");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("运行获取电池详细信息失败");
            return null;
        }
        String jsonText = outputReceiver.getHoleOutput(true);
//        System.out.println("jsonText = \n" + jsonText);
        BatteryDetail batteryDetail;
        try {
            if (null != jsonText && !jsonText.startsWith("{")) {
                jsonText = jsonText.substring(jsonText.indexOf("{"));
            }
            batteryDetail = gson.fromJson(jsonText, BatteryDetail.class);
        } catch (JsonSyntaxException e) {
            logger.error("反序列化设备电池信息时发生json格式错误", e);
            return null;
        }
        return batteryDetail;
    }

    /**
     * 查询设备信息
     * <br>
     * 因为返回的结构不确定，所以直接返回原始字符串
     *
     * @param deviceInfo 指定的设备信息
     * @param key        要查询的字段，null则为查询所有字段
     * @return 结果json文本，null为查询运行失败
     */
    public String mg(SpecificAppleDeviceInfo deviceInfo, String key) {
        ArrayList<String> args = createArgs("diagnostics", "mg", "--no-color");
        if (key != null && !StringUtils.isBlank(key)) {
            args.add(key);
        }
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("查询信息时运行失败，key:" + key);
            return null;
        }
        return outputReceiver.getHoleOutput(true);
    }

    /**
     * 设备待机休眠
     * <br>
     * 执行后有的设备要连按两次电源键才会亮起
     *
     * @param deviceInfo 指定的设备信息
     * @return 是否执行成功（不代表效果）
     */
    public boolean sleep(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("diagnostics", "sleep");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        return runModule(args, outputReceiver);
    }

    /**
     * 设备重启
     *
     * @param deviceInfo 指定的设备信息
     * @return 是否执行成功（不代表效果）
     */
    public boolean restart(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("diagnostics", "restart");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        return runModule(args, outputReceiver);
    }

    /**
     * 设备关机
     * <br>
     * 关机后需要人工按下开机按键
     *
     * @param deviceInfo 指定的设备信息
     * @return 是否执行成功（不代表效果）
     */
    public boolean shutdown(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("diagnostics", "shutdown");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        return runModule(args, outputReceiver);
    }

    /**
     * 监听电池简要信息
     * <br>
     * 用于连续电量监控
     *
     * @param deviceInfo 指定的设备信息
     * @param callback   信息回调
     * @return 运行的程序
     */
    public DaemonRunner watchBattery(SpecificAppleDeviceInfo deviceInfo, BatteryInfoCallback callback) {
        ArrayList<String> args = createArgs("diagnostics", "battery");
        args.add("monitor");
        deviceInfo.appendArgs(args);
        OutputLineCallback lineCallback = new OutputLineCallback() {

            @Override
            public void onLine(String line) {
                if (line == null || StringUtils.isBlank(line)) return;
                int index = line.indexOf("INFO {");
                if (index > 0) {
                    String jsonText = line.substring(index + 5);
                    try {
                        if (null != jsonText && !jsonText.startsWith("{")) {
                            jsonText = jsonText.substring(jsonText.indexOf("{"));
                        }
                        BatteryBaseInfo batteryBaseInfo = gson.fromJson(jsonText, BatteryBaseInfo.class);
                        callback.onBattery(batteryBaseInfo);
                    } catch (JsonSyntaxException e) {
                        logger.warn("监控电池基本信息时发生json解析错误", e);
                    }
                }
            }
        };
        DaemonRunner daemon = new DaemonRunner(lineCallback, true, args);
        daemon.start();
        return daemon;
    }

    /**
     * 从设备lockdown服务查询信息
     * <br>
     * 暂时比usbmux那边的封装查询的要全
     *
     * @param deviceInfo 指定的设备信息
     * @return 查询到的数据，null则为查询失败
     */
    public LockdownInfo lockdownInfo(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("lockdown", "info", "--no-color");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("从lockdown查询信息时运行出错");
            return null;
        }
        String jsonText = outputReceiver.getHoleOutput(false);
        try {
            if (null != jsonText && !jsonText.startsWith("{")) {
                jsonText = jsonText.substring(jsonText.indexOf("{"));
            }
            return gson.fromJson(jsonText, LockdownInfo.class);
        } catch (JsonSyntaxException e) {
            logger.error("处理lockdown查询结果时发生json反序列化错误", e);
        }
        return null;
    }

    /**
     * 获得设备时间
     * <br>
     * 高版本系统查不出来（iOS 17及以上）
     *
     * @param deviceInfo 指定的设备信息
     * @return 时间字符串，null或不符合的都是发生了错误
     */
    public String deviceTime(SpecificAppleDeviceInfo deviceInfo) {
        ArrayList<String> args = createArgs("lockdown", "date");
        deviceInfo.appendArgs(args);
        OutputReceiver outputReceiver = new OutputReceiver();
        boolean runResult = runModule(args, outputReceiver);
        if (!runResult) {
            logger.error("执行获取设备时间失败");
            return null;
        }
        ArrayList<String> allOutput = outputReceiver.getAllOutput();
        if (allOutput.isEmpty()) {
            return null;
        }
        String lastLine = allOutput.get(allOutput.size() - 1);
        if (lastLine.contains("rror")) {
            return null;
        }
        return lastLine;
    }


    /**
     * 安静的删除pymobiledevice3产生的临时文件
     *
     * @param file 要删除的文件，不能是目录
     */
    private void deleteCacheFile(File file) {
        if (!file.delete()) {
            logger.error("无法立即删除pymobiledevice3的临时文件：" + file.getAbsolutePath());
            file.deleteOnExit();
        }
    }

    private ArrayList<String> createArgs(String... args) {
        return new ArrayList<>(Arrays.asList(args));
    }

    /**
     * 如果progressCallback不为null创建一个OutputReceiver
     * <br>
     * 用于仅在需要进度时才处理pymobiledevice3的输出信息的情况
     * <br>
     * 会立即调用一次progressCallback.onStart
     *
     * @param progressCallback 进度回调
     * @return 可能需要的OutputReceiver
     */
    private OutputReceiver createOutputReceiverIfProgressCallbackNotNull(ProgressCallback progressCallback) {
        if (progressCallback == null) return null;
        OutputReceiver receiver = new OutputReceiver();
        receiver.setProgressCallback(progressCallback);
        progressCallback.onStart();
        return receiver;
    }

    /**
     * 用于指定目标设备的信息
     */
    public static class SpecificAppleDeviceInfo {
        private final String udid;

        private String ipv6Address = null;
        private int ipv6Port = 0;

        private final AppleDevice appleDevice;

        public SpecificAppleDeviceInfo(AppleDevice appleDevice) {
            this.appleDevice = appleDevice;
            this.udid = appleDevice.getConnectionDetail().serialNumber;
        }

        public String getUdid() {
            return udid;
        }

        public String getIpv6Address() {
            return ipv6Address;
        }

        public void setIpv6Address(String ipv6Address) {
            this.ipv6Address = ipv6Address;
        }

        public int getIpv6Port() {
            return ipv6Port;
        }

        public void setIpv6Port(int ipv6Port) {
            this.ipv6Port = ipv6Port;
        }

        public boolean isIpv6() {
            return ipv6Address != null;
        }

        public AppleDevice getAppleDevice() {
            return appleDevice;
        }

        /**
         * 在给定的参数集合末尾追加指定设备用的参数
         *
         * @param args 参数集合
         */
        public void appendArgs(ArrayList<String> args) {
            if (isIpv6()) {
                args.add("--rsd");
                args.add(getIpv6Address());
                args.add(String.valueOf(getIpv6Port()));
            } else {
                args.add("--udid");
                args.add(getUdid());
            }
        }

        @Override
        public String toString() {
            return "SpecificAppleDeviceInfo{" +
                    "udid='" + udid + '\'' +
                    ", ipv6Address='" + ipv6Address + '\'' +
                    ", ipv6Port=" + ipv6Port +
                    '}';
        }
    }

    /**
     * 带进度的回调
     */
    public interface ProgressCallback {
        void onStart();

        /**
         * 进度
         *
         * @param progress 0-100
         */
        void onProgress(int progress);

        void onSuccess();

        void onFailed();
    }

    public interface InstallProgressCallback extends ProgressCallback {
        void onPrepared(String sessionId);

        void onFileProgress(int progress);
    }

    public interface DeveloperDvtGraphicsCallback {
        void onData(DeveloperDvtGraphics data);
    }

    public interface BatteryInfoCallback {
        void onBattery(BatteryBaseInfo baseInfo);
    }

    private static class OutputReceiver extends Python3.OutputReceiver {

        private Logger logger = LoggerFactory.getLogger(OutputReceiver.class);

        private ProgressCallback progressCallback = null;

        @Override
        public void onLine(String line) {
            super.onLine(line);
            if (progressCallback != null) {
                if (line.endsWith("% Complete")) {
                    try {
                        int index = line.lastIndexOf("INFO ");
                        String numberText = line.substring(index + 5, line.length() - 10);
                        int progress = Integer.parseInt(numberText);
                        progressCallback.onProgress(progress);
                    } catch (Exception e) {
                        logger.error("提取输出中的进度数据出错，行：" + line, e);
                    }
                }
            }
        }


        public void setProgressCallback(ProgressCallback progressCallback) {
            this.progressCallback = progressCallback;
        }

    }

    /**
     * 方向
     */
    public enum Orientation {
        /**
         * 竖屏
         */
        PORTRAIT,

        /**
         * 横屏
         */
        LANDSCAPE
    }

}
