package net.northking.cctp.upperComputer.driver.harmony.hyppium;

import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.driver.harmony.hdc.*;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.action.*;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.CaptureLayoutData;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.ResultData;
import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent;
import net.northking.cctp.upperComputer.utils.InputStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 通过Hyppium的agent.so控制和获取设备信息
 * <br>
 * 鸿蒙后门：通过uitest的隐藏start-daemon singleness模式，可以加载/data/local/tmp/agent.so
 */
public class HyppiumAgent {

    private static final Logger log = LoggerFactory.getLogger(HyppiumAgent.class);
    /**
     * 设备上的Agent文件绝对路径
     */
    private static final String DEVICE_AGENT_PATH = "/data/local/tmp/agent.so";

    /**
     * 数据协议头
     */
    public static final byte[] PROTOCOL_HEADER = "_uitestkit_rpc_message_head_".getBytes();

    /**
     * 数据协议尾
     */
    public static final byte[] PROTOCOL_TAIL = "_uitestkit_rpc_message_tail_".getBytes();

    /**
     * 数据协议基础长度
     * 头+请求码+数据长度+尾，仍需要加上一个数据本体
     */
    public static final int PROTOCOL_BASIC_LENGTH = ((PROTOCOL_HEADER.length + 4) + 4 + PROTOCOL_TAIL.length);

    /**
     * 如果某个数据捕获请求已经正在运行，此值为返回的exception的内容开头
     */
    private static final String DATA_CAPTURE_ALREADY_RUNNING_PREFIX = "Capture already running:";

    /**
     * 杀死uitest的shell
     */
    private static final String KILL_UITEST_SHELL = "killall -9 uitest";

    /**
     * 删除agent文件的shell
     */
    private static final String DELETE_AGENT_FILE_SHELL = "rm -f /data/local/tmp/agent.so";


    private static final int AGENT_PORT = 8012;

    private final HDCDevice hdcDevice;

    private final RequestCodeProvider requestCodeProvider = new RequestCodeProvider();

    /**
     * 本地访问Agent的端口
     * <br>
     * 此端口转发至设备内部的8012端口
     */
    private int localPort;

    public HyppiumAgent(HDCDevice hdcDevice) throws IOException {
        this.hdcDevice = hdcDevice;
        initAgent();
    }

    public void initAgent() throws IOException {
        if (!isForwardExists() && !createForward()) {
            throw new IOException("无法为设备" + hdcDevice.getConnectKey() + "的HyppiumAgent创建端口转发");
        }
        if (!ensureAgentIsRunning()) {
            throw new IOException("无法确保设备正在执行Agent.so");
        }
    }

    /**
     * 启动Agent
     * <br>
     * 会杀死之前的Agent进程
     *
     * @return 是否确保了Agent已启动
     */
    public boolean ensureAgentIsRunning() {
        String psContent = "";
        try (HDCSession psSession = Hdc.getInstance().shell(hdcDevice, "ps -ef | grep uitest")) {
            psContent = psSession.readAllLines();
        }
        if (psContent.contains("uitest start-daemon singleness")) {
            try (HDCSession session = Hdc.getInstance().shell(hdcDevice, KILL_UITEST_SHELL)) {
                log.debug("鸿蒙设备{}杀死uitest输出：{}", hdcDevice.getConnectKey(), session.readAllLines());
            }
        }

        //没有Agent正在运行，为设备准备文件
        //首先确认设备CPU架构
        String archContent = "";
        try (HDCSession archSession = Hdc.getInstance().shell(hdcDevice, "file /bin/uitest")) {
            archContent = archSession.readAllLines();
        }
        AgentABI abi = AgentABI.UNKNOWN;
        if (archContent.contains("aarch64.so")) {
            abi = AgentABI.ARM64;
        }

        if (abi == AgentABI.UNKNOWN) {
            log.error("为鸿蒙设备{}准备Agent时发现设备是不支持的CPU架构，相关信息：{}", hdcDevice.getConnectKey(), archContent);
            return false;
        }

        File agentFile = HarmonyDeviceManager.getAgentFile(abi);
        if (agentFile == null) {
            log.error("没有准备架构为{}的鸿蒙设备Agent", abi);
            return false;
        }

        try (HDCSession session = Hdc.getInstance().shell(hdcDevice, DELETE_AGENT_FILE_SHELL)) {
            log.debug("鸿蒙设备{}删除agent文件输出：{}", hdcDevice.getConnectKey(), session.readAllLines());
        }

        boolean sendFileResult = Hdc.getInstance().sendFile(hdcDevice, agentFile, DEVICE_AGENT_PATH);
        if (!sendFileResult) {
            log.error("为设备{}传输Agent文件失败", hdcDevice.getConnectKey());
            return false;
        }

        String uiTestContent = "";
        try (HDCSession uiTestSession = Hdc.getInstance().shell(hdcDevice, "uitest start-daemon singleness")) {
            uiTestContent = uiTestSession.readAllLines();
        }
        log.info("设备运行uitest启动Agent输出：\n{}", uiTestContent);

        //再校验一次
        try (HDCSession psSession = Hdc.getInstance().shell(hdcDevice, "ps -ef | grep uitest")) {
            psContent = psSession.readAllLines();
        }
        boolean result = psContent.contains("uitest start-daemon singleness");
        if (result) {
            log.info("鸿蒙设备{}的Agent(uitest)已启动", hdcDevice.getConnectKey());
        } else {
            log.info("鸿蒙设备{}的Agent(uitest)启动失败，输出：{}", hdcDevice.getConnectKey(), psContent);
        }
        return result;
    }

    /**
     * 需要的端口转发是否存在
     * <br>
     * 若存在，则使用已存在的记录
     *
     * @return 是否存在
     */
    private boolean isForwardExists() {
        for (ForwardRule forwardRecord : Hdc.getInstance().listForward()) {
            if (forwardRecord.getConnectKey().equals(hdcDevice.getConnectKey())
                    && forwardRecord.deviceString().equals("tcp:" + AGENT_PORT)) {
                try {
                    localPort = Integer.parseInt(forwardRecord.getLocalContent());
                    log.info("鸿蒙设备{}存在对Agent的端口转发{}", hdcDevice.getConnectKey(), forwardRecord.taskString());
                    return true;
                } catch (NumberFormatException ignored) {

                }
            }
        }
        return false;
    }

    /**
     * 创建需要的端口转发
     * <br>
     * 会重新寻找一个可用端口
     *
     * @return 是否创建成功
     */
    private boolean createForward() {
        int port = getAvailablePort();
        if (port == 0) {
            log.error("无法找到可用端口");
            return false;
        }
        ForwardRule forwardRule = new ForwardRule(hdcDevice.getConnectKey(), ForwardRule.ListenScheme.TCP, String.valueOf(AGENT_PORT), ForwardRule.ListenScheme.TCP, String.valueOf(port), false);
        boolean createResult = Hdc.getInstance().createForward(forwardRule);
        if (createResult) {
            localPort = port;
        }
        return createResult;
    }

    /**
     * 取消此设备所有的Agent相关的端口转发
     */
    private void cancelForward() {
        for (ForwardRule forwardRule : Hdc.getInstance().listForward()) {
            if (forwardRule.getConnectKey().equals(hdcDevice.getConnectKey())
                    && !forwardRule.isReverse()
                    && forwardRule.deviceString().equals("tcp:" + AGENT_PORT)
            ) {
                if (!Hdc.getInstance().removeForwardRule(forwardRule)) {
                    log.warn("移除鸿蒙设备端口转发失败：{}", forwardRule.taskString());
                }
            }
        }
    }

    /**
     * 获得一个可用的TCP监听端口
     *
     * @return 可用的监听端口，0为获取失败
     */
    private int getAvailablePort() {
        int port = 0;
        try {
            ServerSocket serverSocket = new ServerSocket(0);
            serverSocket.setReuseAddress(true);
            port = serverSocket.getLocalPort();
            serverSocket.close();
        } catch (IOException e) {
            log.error("无法为设备{}的HyppiumAgent获取可用转发端口", hdcDevice.getConnectKey());
        }
        return port;
    }

    /**
     * 获取本地监听的端口号
     *
     * @return 本地监听的端口号
     */
    public int getLocalPort() {
        return localPort;
    }

    /**
     * 创建一个Agent Session
     *
     * @return 如果成功返回一个Session实例，null则为创建失败
     */
    public Session createSession() {
        try {
            return new Session();
        } catch (IOException e) {
            log.error("为鸿蒙设备{}的Agent创建session失败", hdcDevice.getConnectKey(), e);
        }
        return null;
    }


    private boolean doRequest(SessionRequest request, OutputStream outputStream) {
        if (request.isTimeout()) {
            return false;
        }
        byte[] sendData = request.sendData;
        ByteBuffer buffer = ByteBuffer.allocate(sendData.length + PROTOCOL_BASIC_LENGTH);
        buffer.put(PROTOCOL_HEADER).putInt(request.requestCode).putInt(sendData.length).put(sendData).put(PROTOCOL_TAIL);
        buffer.position(0);
        try {
            outputStream.write(buffer.array());
            outputStream.flush();
        } catch (IOException e) {
            log.error("向鸿蒙设备{}发送数据失败", hdcDevice.getConnectKey(), e);
            return false;
        }

        return true;
    }


    public class Session implements Closeable, Runnable {
        private final Socket socket;
        private final DataInputStream inputStream;
        private final OutputStream outputStream;
        private boolean closed = false;
        private final Object requestLock = new Object();
        private final Thread thread = new Thread(this);
        private final ConcurrentHashMap<Integer, SessionRequest> requestMap = new ConcurrentHashMap<>();
        private Runnable onCloseListener = null;

        private Session() throws IOException {
            socket = new Socket("127.0.0.1", localPort);
            inputStream = new DataInputStream(socket.getInputStream());
            outputStream = socket.getOutputStream();
            this.thread.setName("Harmony-Agent-Session-" + hdcDevice.getConnectKey());
            this.thread.start();
        }

        @Override
        public void run() {
            while (!isClosed() && !thread.isInterrupted() && hdcDevice.getConnectStatus() == HDCConnectStatus.CONNECTED) {
                doRead();
            }
//            log.debug("停止读取数据包");
            close();
        }

        private void doRead() {
            //数据包头
//            log.debug("开始读取数据包");
            try {
                byte[] readHeader = InputStreamUtils.readNBytes(inputStream,PROTOCOL_HEADER.length);
                if (!Arrays.equals(readHeader, PROTOCOL_HEADER)) {
                    log.error("读取鸿蒙设备{}的响应头校验失败，期望：{}，实际：[{}]{}", hdcDevice.getConnectKey(), new String(PROTOCOL_HEADER), readHeader.length, new String(readHeader));
                    close();
                    return;
                }
            } catch (IOException e) {
                if (!isClosed()) {
                    log.error("读取鸿蒙设备{}的响应头失败", hdcDevice.getConnectKey(), e);
                    close();
                }
                return;
            }

            SessionRequest targetRequest = null;
            //请求码
            try {
                int readRequestCode = inputStream.readInt();
                targetRequest = requestMap.get(readRequestCode);
                if (targetRequest == null) {
                    log.warn("读取鸿蒙设备{}的请求时无法找到对应的请求，请求码：{}", hdcDevice.getConnectKey(), readRequestCode);
                }
            } catch (IOException e) {
                if (!isClosed()) {
                    log.error("读取鸿蒙设备{}的请求码失败", hdcDevice.getConnectKey(), e);
                    close();
                }
                return;
            }


            //数据长度
            int dataLength = 0;
            try {
                dataLength = inputStream.readInt();
                if (dataLength <= 0) {
                    throw new IOException("鸿蒙设备" + hdcDevice.getConnectKey() + "回复的数据长度小于等于0");
                }
            } catch (IOException e) {
                if (!isClosed()) {
                    log.error("读取鸿蒙设备{}的数据长度失败", hdcDevice.getConnectKey(), e);
                    close();
                }
                return;
            }

            //数据内容
            byte[] data = new byte[dataLength];
            try {
                inputStream.readFully(data);
            } catch (IOException e) {
                if (!isClosed()) {
                    log.error("读取鸿蒙设备{}的数据失败", hdcDevice.getConnectKey(), e);
                    close();
                }
                return;
            }

            //数据包尾
            try {
                byte[] readTail = InputStreamUtils.readNBytes(inputStream,PROTOCOL_TAIL.length);
                if (!Arrays.equals(readTail, PROTOCOL_TAIL)) {
                    log.warn("鸿蒙设备{}数据包尾不匹配，数据可能被截断", hdcDevice.getConnectKey());
                    close();
                    return;
                }
            } catch (IOException e) {
                if (!isClosed()) {
                    log.error("读取鸿蒙设备{}的响应尾失败", hdcDevice.getConnectKey(), e);
                    close();
                }
                return;
            }
            if (targetRequest != null && !targetRequest.isTimeout()) {
                targetRequest.response(data);
            }
//            log.debug("数据包读取处理完毕");
        }

        public boolean isClosed() {
            return closed && socket.isClosed();
        }

        public void setOnCloseListener(Runnable onCloseListener) {
            this.onCloseListener = onCloseListener;
        }

        private boolean requestAction(RequestData<?> requestData, long timeout, String description) {
            if (isClosed()) return false;
            boolean result = true;
            SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), timeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            try {
                result = request(sessionRequest, description);
            } catch (CaptureAlreadyRunningException ignored) {
                result = false;
            }
            requestMap.remove(sessionRequest.requestCode);
            return result;
        }

        private boolean requestStartCaptureDataAction(RequestData<?> requestData, long startTimeout, String description, OnCaptureData onCaptureData) throws CaptureAlreadyRunningException {
            if (isClosed()) return false;
            boolean result = false;
            SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), startTimeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            result = request(sessionRequest, description);
            if (result) {
                sessionRequest.setOnCaptureData(onCaptureData);
            }
            return result;
        }

        private boolean requestCancelCaptureDataAction(RequestData<?> requestData, long timeout, String description, OnCaptureData onCaptureData) {
            if (isClosed()) return false;
            boolean result = false;
            SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), timeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            try {
                result = request(sessionRequest, description);
            } catch (CaptureAlreadyRunningException ignored) {
            }
            int foundRequestCode = 0;
            for (Integer key : requestMap.keySet()) {
                if (requestMap.get(key).getOnCaptureData() == onCaptureData) {
                    foundRequestCode = key;
                    log.debug("找到请求码: {}", foundRequestCode);
                    log.debug("从map中移除一个数据监听");
                    break;
                }
            }
            if (foundRequestCode > 0) {
                requestMap.remove(foundRequestCode);
            }
            return result;
        }

        private boolean request(SessionRequest request, String description) throws CaptureAlreadyRunningException {
            if (isClosed()) return false;
            boolean result = true;
            synchronized (requestLock) {
                if (!doRequest(request, outputStream)) {
                    log.error("对鸿蒙设备{}的Agent请求{}失败。超时状态：{}", hdcDevice.getConnectKey(), description, request.isTimeout());
                    result = false;
                }
            }

            if (result) {
                byte[] responseData = request.waitResponse();
                if (responseData == null) {
                    log.error("未能获取鸿蒙设备{}的{}请求结果。超时状态：{}", hdcDevice.getConnectKey(), description, request.isTimeout());
                    result = false;
                }

                if (result) {
                    String responseJsonText = new String(responseData);
                    Response response = HarmonyDeviceManager.gson.fromJson(responseJsonText, Response.class);
                    result = response.isOk();
                    if (!result) {
                        log.error("鸿蒙设备{}的{}请求失败，原因：{}，原始响应：{}", hdcDevice.getConnectKey(), description, response.exception, responseJsonText);
                        if (response.exception != null && response.exception.startsWith(DATA_CAPTURE_ALREADY_RUNNING_PREFIX)) {
                            throw new CaptureAlreadyRunningException(response.exception.substring(DATA_CAPTURE_ALREADY_RUNNING_PREFIX.length()));
                        }
                    }
                }
            }
            return result;
        }


        /**
         * 按下一次返回键
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressBack(long timeout) {
            return requestAction(PressBack.getInstance(), timeout, "按下返回键");
        }

        /**
         * 按下一次主屏键
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressHome(long timeout) {
            return requestAction(PressHome.getInstance(), timeout, "按下主屏键");
        }

        /**
         * 按下最近应用按钮
         * <br>
         * 目前只是滑动屏幕中心，Mate60上并无实际效果
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressRecentApp(long timeout) {
            return requestAction(PressRecentApp.getInstance(), timeout, "按下最近App键");
        }

        /**
         * 按下电源键
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressPowerKey(long timeout) {
            return requestAction(PressPowerKey.getInstance(), timeout, "按下电源键");
        }

        /**
         * 按下音量上键
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressUpVolume(long timeout) {
            return requestAction(PressUpVolume.getInstance(), timeout, "按下音量上键");
        }

        /**
         * 按下音量下键
         *
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pressDownVolume(long timeout) {
            return requestAction(PressDownVolume.getInstance(), timeout, "按下音量下键");
        }

        /**
         * 进行一个双指捏屏操作
         *
         * @param scale   缩放系数
         * @param timeout 响应超时时间
         * @return 是否操作成功
         */
        public boolean pinch(float scale, long timeout) {
            return requestAction(Pinch.getInstance(scale), timeout, "双指捏");
        }

        public boolean touchDown(int x, int y, long timeout) {
            return requestAction(new TouchDown(x, y), timeout, "按下坐标");
        }

        public boolean touchMove(int x, int y, long timeout) {
            return requestAction(new TouchMove(x, y), timeout, "移动坐标");
        }

        public boolean touchUp(int x, int y, long timeout) {
            return requestAction(new TouchUp(x, y), timeout, "抬起坐标");
        }

        /**
         * 开启屏幕图片流
         *
         * @param scale         缩放倍率 0-1之间
         * @param onCaptureData 屏幕流每张图片数据回调接口
         * @param timeout       超时时间
         * @return 开启成功与否
         * @throws CaptureAlreadyRunningException 如果当前正在捕捉数据，则抛出此异常，需要确认是否在别处开启了，如果还是需要开启，则应该先调用一次停止
         */
        public boolean startCaptureScreenImageStream(float scale, OnCaptureData onCaptureData, long timeout) throws CaptureAlreadyRunningException {
            return requestStartCaptureDataAction(StartCaptureScreenStream.getInstance(scale), timeout, "开始捕捉屏幕图片流", onCaptureData);
        }

        /**
         * 停止捕获屏幕图片流
         *
         * @param onCaptureData 数据回调接口
         * @param timeout       超时时间
         * @return 是否停止成功
         */
        public boolean stopCaptureScreenImageStream(OnCaptureData onCaptureData, long timeout) {
            return requestCancelCaptureDataAction(StopCaptureScreenStream.getInstance(), timeout, "停止捕获屏幕图片流", onCaptureData);
        }

        /**
         * 开始捕获UI动作
         *
         * @param onCaptureData 数据回调接口
         * @param timeout       超时时间
         * @return 请求是否成功
         * @throws CaptureAlreadyRunningException 如果当前正在捕捉数据，则抛出此异常，需要确认是否在别处开启了，如果还是需要开启，则应该先调用一次停止
         */
        public boolean startCaptureUiAction(OnCaptureData onCaptureData, long timeout) throws CaptureAlreadyRunningException {
            return requestStartCaptureDataAction(StartCaptureUiAction.getInstance(), timeout, "开始捕获UI动作", onCaptureData);
        }

        /**
         * 停止捕获UI动作
         *
         * @param onCaptureData 数据回调接口
         * @param timeout       超时时间
         * @return 是否停止成功
         */
        public boolean stopCaptureUiAction(OnCaptureData onCaptureData, long timeout) {
            return requestCancelCaptureDataAction(StopCaptureUiAction.getInstance(), timeout, "停止捕获UI动作", onCaptureData);
        }

        /**
         * 捕获UI层级树
         *
         * @param timeout 超时时间
         * @return 获取到的UI层级树数据，null为获取失败
         */
        public UiComponent captureLayout(long timeout) {
            if (isClosed()) return null;
            SessionRequest sessionRequest = new SessionRequest(CaptureLayout.getInstance().toByteArray(), timeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            synchronized (requestLock) {
                if (!doRequest(sessionRequest, outputStream)) {
                    log.error("对鸿蒙设备{}的Agent请求布局数据失败。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                    requestMap.remove(sessionRequest.requestCode);
                    return null;
                }
            }
            byte[] responseData = sessionRequest.waitResponse();
            requestMap.remove(sessionRequest.requestCode);
            if (responseData == null) {
                log.error("未能获取鸿蒙设备{}的布局数据。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                return null;
            }
            String jsonText = new String(responseData).replace(":\"\"", ":null");
            CaptureLayoutData captureLayoutData;
            try {
                captureLayoutData = HarmonyDeviceManager.gson.fromJson(jsonText, CaptureLayoutData.class);
            } catch (JsonParseException e) {
                log.error("解析鸿蒙设备{}的布局json数据时发生错误", hdcDevice.getConnectKey(), e);
                return null;
            }
            return captureLayoutData.result;
        }

        /**
         * 设置屏幕方向
         *
         * @param direction 屏幕方向值，逆时针：0为0°，1为90°，2为180°，3为270°
         * @param timeout   超时时间
         * @return 是否操作成功
         */
        public boolean setDisplayRotation(int direction, long timeout) {
            return requestAction(new RotationDisplay(direction), timeout, "设置屏幕方向：" + direction);
        }

        /**
         * 获得屏幕方向
         * <br>
         *
         * @param timeout 超时时间
         * @return 屏幕方向值，-1为获取失败，逆时针：0为0°，1为90°，2为180°，3为270°
         */
        public int getDisplayRotation(long timeout) {
            if (isClosed()) return -1;
            SessionRequest sessionRequest = new SessionRequest(GetDisplayRotation.getInstance().toByteArray(), timeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            synchronized (requestLock) {
                if (!doRequest(sessionRequest, outputStream)) {
                    log.error("对鸿蒙设备{}的Agent请求屏幕方向数据失败。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                    requestMap.remove(sessionRequest.requestCode);
                    return -1;
                }
            }
            byte[] responseData = sessionRequest.waitResponse();
            requestMap.remove(sessionRequest.requestCode);
            if (responseData == null) {
                log.error("未能获取鸿蒙设备的{}的屏幕方向。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                return -1;
            }
            String jsonText = new String(responseData);
            try {
                ResultData<Integer> displayRotationData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken<ResultData<Integer>>() {
                }.getType());
                return displayRotationData.result;
            } catch (JsonParseException e) {
                log.error("解析鸿蒙设备{}屏幕方向数据时发生JSON解析错误，文本：{}", hdcDevice.getConnectKey(), jsonText);
            }
            return -1;
        }

        public DisplaySize getScreenSize(long timeout) {
            if (isClosed()) return null;
            SessionRequest sessionRequest = new SessionRequest(GetDisplaySize.getInstance().toByteArray(), timeout);
            requestMap.put(sessionRequest.requestCode, sessionRequest);
            synchronized (requestLock) {
                if (!doRequest(sessionRequest, outputStream)) {
                    log.error("对鸿蒙设备{}的Agent请求屏幕大小数据失败。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                    requestMap.remove(sessionRequest.requestCode);
                    return null;
                }
            }
            byte[] responseData = sessionRequest.waitResponse();
            requestMap.remove(sessionRequest.requestCode);
            if (responseData == null) {
                log.error("未能获取鸿蒙设备的{}的屏幕大小。超时状态：{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
                return null;
            }
            String jsonText = new String(responseData);
            try {
                ResultData<DisplaySize> resultData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken<ResultData<DisplaySize>>() {
                }.getType());
                return resultData.result;
            } catch (JsonParseException e) {
                log.error("解析鸿蒙设备{}屏幕大小数据时发生JSON解析错误，文本：{}", hdcDevice.getConnectKey(), jsonText);
            }
            return null;
        }

        @Override
        public void close() {
            if (closed) {
                return;
            }
            closed = true;
            requestMap.clear();
            try {
                socket.close();
            } catch (IOException e) {
                log.warn("关闭鸿蒙设备{}的一个Agent Session时发生IO错误", hdcDevice.getConnectKey(), e);
            }
            if (onCloseListener != null) {
                try {
                    onCloseListener.run();
                } catch (Exception e) {
                    log.error("关闭鸿蒙设备{}的一个Agent Session后，执行关闭监听回调时发生异常", hdcDevice.getConnectKey());
                }
            }
        }
    }

    private class SessionRequest {
        final int requestCode = requestCodeProvider.next();
        final byte[] sendData;
        final long createTime = System.currentTimeMillis();
        final long timeout;
        byte[] responseData = null;
        final Object waitResponseLock = new Object();
        OnCaptureData onCaptureData = null;

        public SessionRequest(byte[] sendData, long timeout) {
            this.sendData = sendData;
            this.timeout = timeout;
        }

        public boolean isTimeout() {
            return onCaptureData == null && (System.currentTimeMillis() - createTime > timeout);
        }

        public void setOnCaptureData(OnCaptureData onCaptureData) {
            this.onCaptureData = onCaptureData;
        }

        public OnCaptureData getOnCaptureData() {
            return onCaptureData;
        }

        public void response(byte[] data) {
            if (onCaptureData == null) {
                responseData = data;
                synchronized (waitResponseLock) {
                    waitResponseLock.notifyAll();
                }
            } else {
                try {
                    onCaptureData.onCaptureData(data);
                } catch (Exception e) {
                    log.error("处理鸿蒙设备{}的Agent捕获到的数据时发生异常", hdcDevice.getConnectKey(), e);
                }
            }
        }

        public byte[] waitResponse() {
            synchronized (waitResponseLock) {
                if (timeout > 0 && onCaptureData == null) {
                    try {
                        waitResponseLock.wait(timeout);
                    } catch (InterruptedException e) {
                        return null;
                    }
                } else {
                    try {
                        waitResponseLock.wait();
                    } catch (InterruptedException e) {
                        return null;
                    }
                }
            }
            return responseData;
        }
    }

    /**
     * 捕获到数据的回调
     * <br>
     * 用于图片流或者UI层级树的持续获取
     */
    public interface OnCaptureData {
        /**
         * 当捕获的目标数据到来时
         * <br>
         * 此方法将在Session线程运行
         *
         * @param data 数据
         */
        void onCaptureData(byte[] data);
    }

    /**
     * 要开始的指定数据捕捉已经正在运行
     * <br>
     * 需要进行一次取消后再次开始
     */
    public static class CaptureAlreadyRunningException extends Exception {
        public CaptureAlreadyRunningException(String message) {
            super(message + "已经正在运行");
        }
    }

}
