package net.northking.cctp.upperComputer.utils;


import net.northking.cctp.upperComputer.driver.adb.Adb;
import net.northking.cctp.upperComputer.driver.adb.AdbDevice;
import net.northking.cctp.upperComputer.driver.adb.AdbShellSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

/**
 * 与设备的AppiumHelper进行数据交互<br>
 * 此类使用adb shell方式进行，交互式会话，不需要端口转发。
 */
public final class AndroidAppiumHelper {

    private static Logger log = LoggerFactory.getLogger(AndroidAppiumHelper.class);

    private static final HashMap<String, AppiumHelperSession> sessionMap = new HashMap<>();

    /**
     * 执行获取XML格式UI层级树
     *
     * @param deviceId 设备id
     * @return Appium返回结果，null为请求失败
     */
    public static String getXmlResponse(String deviceId) {
        return doGetXmlResponse(deviceId, 0);
    }

    /**
     * 执行获取XML格式UI层级树
     *
     * @param deviceId 设备id
     * @param callTime 用于递归限制
     * @return Appium返回结果，null为请求失败
     */
    private static String doGetXmlResponse(String deviceId, int callTime) {
        if (callTime > 1) {
            return null;
        }
        if (sessionMap.containsKey(deviceId)) {
            AppiumHelperSession session = sessionMap.get(deviceId);
            try {
                session.sendCommand("source");
                int responseLength = session.readResponseLength();
                return session.readResponse(responseLength);
            } catch (IOException e) {
                log.error("发生i/o错误：" + e.getMessage());
                removeSession(deviceId);
            }
        } else {
            log.debug("createSession:" + deviceId);
            if (createSession(deviceId)) {
                return doGetXmlResponse(deviceId, ++callTime);
            }
        }
        log.error(deviceId + "session失败");
        return null;
    }

    /**
     * 创建一个会话
     *
     * @param deviceId 设备id
     * @return 是否创建成功
     */
    private static boolean createSession(String deviceId) {
        Adb adb;
        try {
            adb = new Adb();
        } catch (IOException e) {
            log.warn("adb连接报错",e);
            return false;
        }
        AdbDevice adbDevice = null;
        for (AdbDevice device : adb.listDevices()) {
            if (device.getSerial().equalsIgnoreCase(deviceId)) {
                adbDevice = device;
                break;
            }
        }
        if (adbDevice == null) {
            return false;
        }
        AppiumHelperSession session = new AppiumHelperSession();
        if (adb.shell(adbDevice, session, null)) {
            if (!session.isReady()) {
                return false;
            }
            try {
                OutputStream outputStream = session.getOutputStream();
                outputStream.write("export CLASSPATH=/data/local/tmp/agent.apk\n".getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
                outputStream.write("app_process /system/bin net.northking.product.cctpplus.AppiumHelper\n".getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
            } catch (IOException e) {
                return false;
            }
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(session.getInputStream()));
            String line;
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    line = bufferedReader.readLine();
                    if (line == null) {
                        return false;
                    } else if (line.equalsIgnoreCase("AppiumHelper")) {
                        synchronized (sessionMap) {
                            sessionMap.put(deviceId, session);
                        }
                        return true;
                    }
                } catch (IOException e) {
                    break;
                }
            }
            return false;
        }

        return true;
    }


    /**
     * 移除一个会话
     *
     * @param deviceId 设备id
     */
    private static void removeSession(String deviceId) {
        synchronized (sessionMap) {
            sessionMap.remove(deviceId);
        }
    }

    /**
     * 与AppiumHelper数据交互的会话<br>
     * 提供InputStream和OutputStream<br>
     * 数据协议为私有协议，与安卓Agent中的代码进行对应。
     */
    static class AppiumHelperSession extends AdbShellSession {

        public void sendCommand(String command) throws IOException {
            int realLength = command.length() + 2;//自己一个回车，回显一个回车，所以加2
            getOutputStream().write((command + "\n").getBytes(StandardCharsets.UTF_8));
        }

        @SuppressWarnings("ResultOfMethodCallIgnored")
        public int readResponseLength() throws IOException {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] c= new byte[1];
            int rc = 0;
            while (rc!=-1){
                rc = getInputStream().read(c);
                if(rc > 0){
                    outputStream.write(c,0,rc);
                    if(outputStream.toString().contains("result_start")){
                        long skip = getInputStream().skip(2);
                        break;
                    }
                }
            }
            ByteBuffer byteBuffer = ByteBuffer.allocate(4);
            byte[] cache = new byte[4];
            int readCount = getInputStream().read(cache);
            if (readCount != 4) {
                throw new IOException("读取响应数据长度失败，实际读取:" + readCount);
            }
            byteBuffer.put(cache);
            byteBuffer.position(0);
            return byteBuffer.getInt();
        }

        public String readResponse(int length) throws IOException {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(length);
            int currentRead = 0;
            byte[] cache = new byte[512 * 1024];
            while (currentRead != -1) {
                currentRead = getInputStream().read(cache);
                byteArrayOutputStream.write(cache, 0, currentRead);
                if (byteArrayOutputStream.size() >= length || byteArrayOutputStream.toString().endsWith("}")) {
                    break;
                }
            }
            String response = byteArrayOutputStream.toString();

            return response.substring(response.indexOf("{"));
        }
    }

}
