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

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * 与Adb Server的通信协议流程处理
 * <br><br>
 * 实现参考：<br>
 * <a href="https://github.com/vidstige/jadb/blob/master/src/se/vidstige/jadb/Transport.java">vidstige/jadb:Transport.java</a>
 * <br>
 * <a href="https://github.com/vidstige/jadb/blob/master/src/se/vidstige/jadb/SyncTransport.java">vidstige/jadb:SyncTransport.java</a>
 * <br><br>
 * 相关开源许可：<br>
 * vidstige/jadb is licensed under the<br>
 * Apache License 2.0
 */
public class AdbTransport implements Closeable {
    private final InputStream mInputStream;

    private final OutputStream mOutputStream;

    private final DataInputStream mDataInputStream;
    private final DataOutputStream mDataOutputStream;

    public AdbTransport() throws IOException {
        this(new Socket("127.0.0.1", 5037));
    }

    public AdbTransport(Socket socket) throws IOException {
        this(socket.getInputStream(), socket.getOutputStream());
    }

    public AdbTransport(InputStream inputStream, OutputStream outputStream) {
        mInputStream = inputStream;
        mOutputStream = outputStream;
        mDataInputStream = new DataInputStream(inputStream);
        mDataOutputStream = new DataOutputStream(outputStream);
    }

    /**
     * 此传输接口转为针对指定设备的
     *
     * @param serial 设备序列号
     * @throws IOException        发生连接错误
     * @throws TransportException 发生协议或指令错误
     */
    public void forDevice(String serial) throws IOException, TransportException {
        send("host:transport:" + serial);
        verifyResponse();
    }

    /**
     * 读取字符串，会自动首先读取字符串长度
     *
     * @return 读取到的字符串
     * @throws IOException 发生连接错误
     */
    public String readString() throws IOException {
        String encodedLength = readString(4);
        int length = Integer.parseInt(encodedLength, 16);
        return readString(length);
    }


    /**
     * 发送指令后，读取Ack（必须）<br>
     * 如果指令执行正常，返回“OKAY”
     *
     * @throws IOException        发生连接错误
     * @throws TransportException 发生协议或指令错误
     */
    public void verifyResponse() throws IOException, TransportException {
        String response = readString(4);
        if (!"OKAY".equals(response)) {
            String error = readString();
            throw new TransportException("命令失败: " + error);
        }
    }

    /**
     * 读取指定长度字符串内容
     *
     * @param length 要读取的长度
     * @return 读取的字符串内容
     * @throws IOException 发生连接错误
     */
    public String readString(int length) throws IOException {
        byte[] responseBuffer = new byte[length];
        mDataInputStream.readFully(responseBuffer);
        return new String(responseBuffer, StandardCharsets.UTF_8);
    }

    public InputStream getInputStream() {
        return mInputStream;
    }

    public OutputStream getOutputStream() {
        return mOutputStream;
    }

    /**
     * 获得指令字节长度
     *
     * @param command 指令
     * @return 指令转为Bytes后的长度
     */
    private String getCommandLength(String command) {
        return String.format("%04x", command.getBytes().length);
    }

    /**
     * 发送指令
     *
     * @param command 要发送的指令内容
     * @throws IOException 发生连接错误
     */
    public void send(String command) throws IOException {
        OutputStreamWriter writer = new OutputStreamWriter(mOutputStream, StandardCharsets.UTF_8);
        writer.write(getCommandLength(command));
        writer.write(command);
        writer.flush();
    }

    /**
     * 转为同步传输端口
     *
     * @return 同步传输端口对象
     * @throws IOException        发生连接错误
     * @throws TransportException 发生协议错误
     */
    public SyncTransport startSync() throws IOException, TransportException {
        send("sync:");
        verifyResponse();
        return new SyncTransport();
    }

    /**
     * 关闭此传输端口
     *
     * @throws IOException 发生连接错误
     */
    @Override
    public void close() throws IOException {
        mDataInputStream.close();
        mDataOutputStream.close();
    }

    /**
     * 同步传输端口，主要用于文件操作相关
     */
    class SyncTransport {

        public void send(String syncCommand, String name) throws IOException {
            if (syncCommand.length() != 4) throw new IllegalArgumentException("sync commands must have length 4");
            mDataOutputStream.writeBytes(syncCommand);
            byte[] data = name.getBytes(StandardCharsets.UTF_8);
            mDataOutputStream.writeInt(Integer.reverseBytes(data.length));
            mDataOutputStream.write(data);
        }

        public void sendStatus(String statusCode, int length) throws IOException {
            mDataOutputStream.writeBytes(statusCode);
            mDataOutputStream.writeInt(Integer.reverseBytes(length));
        }

        public void verifyStatus() throws IOException, TransportException {
            String status = readString(4);
            int length = readInt();
            if ("FAIL".equals(status)) {
                String error = readString(length);
                throw new TransportException(error);
            }
            if (!"OKAY".equals(status)) {
                throw new TransportException("Unknown error: " + status);
            }
        }

        private int readInt() throws IOException {
            return Integer.reverseBytes(mDataInputStream.readInt());
        }

        private String readString(int length) throws IOException {
            byte[] buffer = new byte[length];
            mDataInputStream.readFully(buffer);
            return new String(buffer, StandardCharsets.UTF_8);
        }


        public RemoteFileRecord readDirectoryEntry() throws IOException {
            String id = readString(4);
            int mode = readInt();
            int size = readInt();
            int time = readInt();
            int nameLength = readInt();
            String name = readString(nameLength);

            if (!"DENT".equals(id)) return null;
            return new RemoteFileRecord(name, mode, size, time);
        }

        private void sendChunk(byte[] buffer, int offset, int length) throws IOException {
            mDataOutputStream.writeBytes("DATA");
            mDataOutputStream.writeInt(Integer.reverseBytes(length));
            mDataOutputStream.write(buffer, offset, length);
        }

        private int readChunk(byte[] buffer) throws IOException, TransportException {
            String id = readString(4);
            int n = readInt();
            if ("FAIL".equals(id)) {
                throw new TransportException(readString(n));
            }
            if (!"DATA".equals(id)) return -1;
            mDataInputStream.readFully(buffer, 0, n);
            return n;
        }

        public void sendStream(InputStream in,PushBytesCountCallback callback) throws IOException {
            byte[] buffer = new byte[1024 * 64]; //不得大于64k
            int n = in.read(buffer);
            long totalCount = 0;
            while (n != -1) {
                sendChunk(buffer, 0, n);
                totalCount += n;
                if (null != callback) {
                    callback.onSendBytes(totalCount);
                }
                n = in.read(buffer);
            }
        }

        public void readChunksTo(OutputStream stream) throws IOException, TransportException {
            byte[] buffer = new byte[1024 * 64]; //不会大于64k
            int n = readChunk(buffer);
            while (n != -1) {
                stream.write(buffer, 0, n);
                n = readChunk(buffer);
            }
        }
    }
}
