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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

/**
 * Adb client<br>
 * 对着adb命令提供相应功能<br>
 * 协议资料：<a href="https://android.googlesource.com/platform/packages/modules/adb/+/master/SERVICES.TXT">SERVICE.TXT</a>，
 * <a href="https://android.googlesource.com/platform/packages/modules/adb/+/master/SYNC.TXT">SYNC.TXT</a>
 */
public class Adb {

    private String serverHostname = null;

    private Integer serverPort = null;

    private Logger log = LoggerFactory.getLogger(Adb.class);


    public Adb() throws IOException {
    }

    public Adb(String hostname, int port) {
        serverHostname = hostname;
        serverPort = port;
    }

    /**
     * 创建一个与Adb Server的传输端口
     *
     * @return 传输端口对象
     * @throws IOException 发生连接错误
     */
    private AdbTransport createTransport() throws IOException {
        String hostname = serverHostname;
        Integer port = serverPort;
        if (port != null) {
            return new AdbTransport(new Socket(hostname, port));
        } else {
            return new AdbTransport();
        }
    }


    /**
     * 查询 Adb Server的版本
     *
     * @return Adb Server的版本
     */
    public String hostVersion() {
        try(AdbTransport transport = createTransport()) {
            transport.send("host:version");
            transport.verifyResponse();
            return transport.readString();
        } catch (IOException ioException) {
            log.warn("获取Adb Server版本失败", ioException);
        } catch (TransportException transportException) {
            log.warn("获取Adb Server版本时发生传输协议错误", transportException);
        }

        return null;
    }

    /**
     * 获得Adb所有连接设备描述内容<br>
     * 需要进一步处理使用
     *
     * @return Adb所有连接设备的描述内容
     */
    private String deviceListContent() {
        try (AdbTransport transport = createTransport()){
            transport.send("host:devices-l");
            transport.verifyResponse();
            return transport.readString();
        } catch (IOException ioException) {
            log.warn("获取Adb设备列表失败", ioException);
        } catch (TransportException transportException) {
            log.warn("获取Adb设备列表时发生传输协议错误", transportException);
        }
        return null;
    }

    /**
     * 获得当前已连接设备列表及信息
     *
     * @return 当前设备基本信息集合
     */
    public ArrayList<AdbDevice> listDevices() {
        String content = deviceListContent();
        ArrayList<AdbDevice> list = new ArrayList<AdbDevice>();
        if (content != null) {
            for (String line : content.split("\n")) {
                if (line != null && !line.isEmpty()) {
                    list.add(new AdbDevice(line));
                }
            }
        }
        return list;
    }

    /**
     * 观察设备变化<br>
     * 此方法会阻塞当前线程运行！不会报告连贯的变化<br>
     * 短时间内快速变化多个多次状态将只会触发一次观察<br>
     * 此方法使用8k buffer
     *
     * @param deviceWatcher 设备变化观察者
     */
    @SuppressWarnings({"ResultOfMethodCallIgnored", "BusyWait"})
    public void watchDevice(DeviceWatcher deviceWatcher) {
        try (AdbTransport transport = createTransport()){
            transport.send("host:track-devices");
            transport.verifyResponse();
            InputStream inputStream = transport.getInputStream();
            Thread currentThread = Thread.currentThread();
            byte[] buffer = new byte[8192];
            while (deviceWatcher.isWatching() && currentThread.isAlive() && !currentThread.isInterrupted()) {
                int available = inputStream.available();
                if (available > 0) {
                    while (available > 0) {
                        int read = inputStream.read(buffer);
                        available = inputStream.available();
                    }
                    deviceWatcher.update(listDevices());
                }
                Thread.sleep(100);
            }
        } catch (IOException ioException) {
            log.warn("开始监听设备变化失败", ioException);
        } catch (TransportException transportException) {
            log.warn("无法开始监听设备", transportException);
        } catch (InterruptedException e) {
            log.warn("线程中断", e);
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 刷新设备状态<br>
     * 设备可能会处于枚举状态外完全不可用的状态，根据方法返回值判断
     *
     * @param adbDevice 需要刷新的设备
     * @return 此设备是否还可用
     */
    public boolean refreshState(AdbDevice adbDevice) {
        try (AdbTransport transport = createTransport()){
            transport.send("host-serial:" + adbDevice.getSerial() + ":get-state");
            transport.verifyResponse();
            String state = transport.readString();
            adbDevice.updateStateByText(state);
            return true;
        } catch (IOException ioException) {
            log.error("尝试刷新[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]时发生传输接口错误", ioException);
            return false;
        } catch (TransportException transportException) {
            log.error("尝试刷新[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]时发生传输协议错误", transportException);
            return false;
        }
    }



    /**
     * 在设备上运行shell或开启shell会话<br>
     *
     * @param adbDevice       要操作的设备，需要提前刷新为当前最新状态
     * @param adbShellSession 新的Shell会话对象，在此方法后生效，用于提供有效InputStream和OutputStream
     * @param execute         null或留空时将开启交互shell会话，直到执行exit，否则将直接执行指令，并在指令退出时结束shell
     * @return 操作是否成功
     */
    public boolean shell(AdbDevice adbDevice, AdbShellSession adbShellSession, String execute) {
        if (!adbDevice.state.canCreateShell()) {
            log.warn("无法在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]请求创建shell会话，状态为" + adbDevice.state);
            return false;
        }
        AdbTransport transport = null;
        try {
            transport = createTransport();
            transport.forDevice(adbDevice.getSerial());
            if (execute != null && !execute.isEmpty()) {
                transport.send("shell: '" + execute + "'");
            } else {
                transport.send("shell:");
            }
            transport.verifyResponse();
            adbShellSession.init(transport.getInputStream(), transport.getOutputStream(),transport);
            return true;
        } catch (IOException e) {
            log.error("请求在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]创建shell会话时，创建传输端口出错", e);
        } catch (TransportException e) {
            log.error("请求在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]创建shell会话时，创建失败", e);
        }
        if (transport != null) {
            try {
                transport.close();
            } catch (IOException e) {
                log.error("关闭transport时发生IO异常", e);
            }
        }
        return false;
    }
    /**
     * 在设备上运行shell或开启shell会话<br>
     *
     * @param adbDevice 要操作的设备，需要提前刷新为当前最新状态
     * @param execute   null或留空时将开启交互shell会话，直到执行exit，否则将直接执行指令，并在指令退出时结束shell
     * @return 操作是否成功
     */
    public AdbTransport shell(AdbDevice adbDevice, String execute) {
        if (!adbDevice.state.canCreateShell()) {
            log.warn("无法在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]请求创建shell会话，状态为" + adbDevice.state);
            return null;
        }
        AdbTransport transport = null;
        try {
            transport = createTransport();
            transport.forDevice(adbDevice.getSerial());
            if (execute != null && !execute.isEmpty()) {
                transport.send("exec: " + execute);
            } else {
                transport.send("shell:");
            }
            transport.verifyResponse();
            return transport;
        } catch (IOException e) {
            log.error("请求在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]创建shell会话时，创建传输端口出错", e);
        } catch (TransportException e) {
            log.error("请求在设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")]创建shell会话时，创建失败", e);
        }
        if (transport != null) {
            try {
                transport.close();
            } catch (IOException e) {
                log.error("关闭transport时发生IO异常", e);
            }
        }
        return null;
    }

    /**
     * 从Adb server返回的记录中创建Tunnel集合记录
     *
     * @param result        数据将添加到此集合中
     * @param resultContent Adb server返回的记录内容文本
     */
    private void readTunnelRecordFromTextContent(ArrayList<Tunnel> result, String resultContent) {
        if(resultContent.length() < 10) return;

        for(String line:resultContent.split("\n")){
            if( line.length() > 3){
                result.add(new Tunnel(line));
            }
        }
    }

    /**
     * 设备变化观察者
     */
    public interface DeviceWatcher {
        /**
         * 保持观察<br>
         * 如果需要停止观察，将其设为false
         */
        boolean isWatching();

        /**
         * 有设备发生变化<br>
         * 新增变动删除需要在后续逻辑自行判断处理
         *
         * @param currentDevices 当前设备的基本数据，均为新的实例对象
         */
        void update(ArrayList<AdbDevice> currentDevices);
    }

    /**
     * 推送数据到设备储存
     *
     * @param adbDevice    要操作的设备
     * @param inputStream  数据流
     * @param lastModified 数据最后修改时间(精度：秒)
     * @param mode         储存文件权限
     * @param remotePath   设备上的储存位置
     * @return 是否操作成功
     */
    public boolean push(AdbDevice adbDevice, InputStream inputStream, int lastModified, int mode, String remotePath,PushBytesCountCallback callback) {
        try(AdbTransport transport = createTransport()) {
            transport.forDevice(adbDevice.getSerial());
            AdbTransport.SyncTransport syncTransport = transport.startSync();
            syncTransport.send("SEND", remotePath + "," + mode);
            syncTransport.sendStream(inputStream,callback);
            syncTransport.sendStatus("DONE", (int) lastModified);
            syncTransport.verifyStatus();
            return true;
        } catch (IOException e) {
            log.error("请求传输文件数据到设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] " + remotePath + "时，创建传输端口失败", e);
        } catch (TransportException e) {
            log.error("请求传输文件数据到设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] " + remotePath + "时，发生协议错误", e);
        }
        return false;
    }

    /**
     * 从设备拉取指定文件内容
     *
     * @param adbDevice    要操作的设备
     * @param outputStream 输出流
     * @param remotePath   远程文件路径
     * @return 操作是否成功
     */
    public boolean pull(AdbDevice adbDevice, OutputStream outputStream, String remotePath) {
        try (AdbTransport transport = createTransport()){
            transport.forDevice(adbDevice.getSerial());
            AdbTransport.SyncTransport syncTransport = transport.startSync();
            syncTransport.send("RECV", remotePath);
            syncTransport.readChunksTo(outputStream);
            return true;
        } catch (IOException e) {
            log.error("请求从设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] 获取" + remotePath + "时，创建传输端口失败", e);
        } catch (TransportException e) {
            log.error("请求从设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] 获取" + remotePath + "时，发生协议错误", e);
        }
        return false;
    }

    /**
     * 列出设备指定目录下的内容
     *
     * @param adbDevice  要操作的设备
     * @param remotePath 要列出的目录
     * @return 目录下的内容
     */
    public ArrayList<RemoteFileRecord> list(AdbDevice adbDevice, String remotePath) {
        ArrayList<RemoteFileRecord> result = new ArrayList<>();
        try (AdbTransport transport = createTransport()){
            transport.forDevice(adbDevice.getSerial());
            AdbTransport.SyncTransport syncTransport = transport.startSync();
            syncTransport.send("LIST", remotePath);
            boolean readDone = false;
            while (!readDone) {
                RemoteFileRecord readRecord = syncTransport.readDirectoryEntry();
                if (readRecord == null) {
                    readDone = true;
                } else {
                    if (!".".equals(readRecord.path) && !"..".equals(readRecord.path)) {
                        result.add(readRecord);
                    }
                }
            }
        } catch (IOException e) {
            log.error("请求获取设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] 目录" + remotePath + "下的信息时，创建传输端口失败", e);
        } catch (TransportException e) {
            log.error("请求获取设备[" + adbDevice.getProduct() + "(" + adbDevice.getSerial() + ")] 目录" + remotePath + "下的信息时，发生协议错误", e);
        }
        result.sort((o1, o2) -> o1.path.compareToIgnoreCase(o2.path));
        return result;
    }

}
