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


import net.northking.cctp.upperComputer.utils.InputStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class HDCSession implements Closeable {
    private static final long HANDSHAKE_DELAY_MS = 500;
    private final Logger log = LoggerFactory.getLogger(HDCSession.class);
    private final Socket socket;
    protected final InputStream in;
    protected final OutputStream out;
    private boolean closed = false;
    private final ByteBuffer readLengthBuffer = ByteBuffer.allocate(4);
    private final ByteBuffer writeLengthBuffer = ByteBuffer.allocate(4);

    private int sessionId = 0;

    /**
     * 设备id，如果为null，则不指定设备
     */
    private final String connectKey;

    HDCSession(Socket socket) throws IOException {
        this(socket, null);
    }

    HDCSession(Socket socket, String connectKey) throws IOException {
        this.socket = socket;
        this.connectKey = connectKey;
        if (socket.isClosed()) throw new IllegalStateException("Socket已经关闭");
        in = socket.getInputStream();
        out = socket.getOutputStream();
        if (!handshake()) {
            throw new IOException("hdc握手失败");
        }
    }

    public int getSessionId() {
        return sessionId;
    }

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

    private void ensureOpen() throws IOException {
        if (isClosed()) throw new IOException("与HDC的连接已经关闭");
    }

    /**
     * 发送一句hdc文本指令
     * <br>
     * 会自动在文本末尾增加一个\0
     * <br>
     * 会检查会话是否有效
     *
     * @param command 指令，具体参考Hdc文档
     * @return 指令返回的文本
     * @throws IOException 发送语句时发生IO异常
     */
    public String sendHdcCommand(String command) throws IOException {
        ensureOpen();
        sendHdcData((command + "\0").getBytes(StandardCharsets.UTF_8));
        return new String(readHdcData());
    }

    /**
     * 读取一个HDC数据包
     * <br>
     * 由4个字节的长度+对应长度的字节数组组成
     *
     * @return 读取到的字节数组
     * @throws IOException 读取时发生了异常
     */
    public byte[] readHdcData() throws IOException {
        if (isClosed()) return new byte[0];
        readLengthBuffer.clear();
        if (InputStreamUtils.readNBytes(in,readLengthBuffer.array(), 0, 4) != 4) throw new EOFException();
        int length = readLengthBuffer.getInt();
        if (length < 0) throw new EOFException();
        byte[] readData;
        try {
            readData = InputStreamUtils.readNBytes(in,length);
        } catch (IOException e) {
            closed = true;
            throw e;
        }
        return readData;
    }


    /**
     * 发送一个HDC数据包
     * <br>
     * 会自动先发送数据包长度，不适合shell
     *
     * @param data 要发生的数据本体
     * @throws IOException 发送时发生IO异常
     */
    public void sendHdcData(byte[] data) throws IOException {
        sendHdcData(data, 0, data.length);
    }

    /**
     * 发送一个HDC数据包
     * <br>
     * 会自动先发送数据包长度，不适合shell
     *
     * @param data   要发生的数据本体
     * @param offset 要发送的数据偏移
     * @param len    要发送的数据长度
     * @throws IOException 发送时发生IO异常
     */
    public void sendHdcData(byte[] data, int offset, int len) throws IOException {
        if (isClosed()) return;
        writeLengthBuffer.clear();
        writeLengthBuffer.putInt(len);
        try {
            out.write(writeLengthBuffer.array());
            out.write(data, offset, len);
        } catch (IOException e) {
            closed = true;
            throw e;
        }
    }

    /**
     * 建立连接后的协议握手
     *
     * @return 是否握手成功
     */
    private boolean handshake() {
        byte[] hostData;
        try {
            hostData = readHdcData();
        } catch (IOException e) {
            log.error("读取hdc握手首条数据发生IO错误", e);
            close();
            return false;
        }
        if (hostData.length != 44) {
            log.error("读取到的hdc握手首条数据长度不为44，长度不匹配。读取到的长度为：{}", hostData.length);
            close();
            return false;
        }
        //检查banner `OHOS HDC`字符串
        String banner = new String(hostData, 0, 8);
        if (!banner.equals("OHOS HDC")) {
            log.error("读取hdc握手数据中的banner时内容与预期不符，读取到：{}", banner);
            close();
            return false;
        }
        ByteBuffer hostDataBuffer = ByteBuffer.wrap(hostData);
        hostDataBuffer.position(8);
        int authType = hostDataBuffer.getInt();
        if (authType != 0 && authType != 72) {
            log.error("读取到的hdc握手首条数据中，告知了一个不支持的验证类型：{}", authType);
            close();
            return false;
        }
        sessionId = hostDataBuffer.getInt();

        if (connectKey == null) {
            //仅保留banner，回复hdc
            for (int i = 12; i < hostData.length; i++) {
                hostData[i] = 0;
            }
            try {
                sendHdcData(hostData);
            } catch (IOException e) {
                log.error("回复hdc握手信息时发生IO错误", e);
                close();
                return false;
            }
        } else {
            byte[] connectKeyBytes = connectKey.getBytes();
            ByteBuffer buffer = ByteBuffer.allocate(12 + connectKeyBytes.length);
            buffer.put("OHOS HDC".getBytes());
            buffer.position(buffer.position() + 4);
            buffer.put(connectKeyBytes);
            try {
                sendHdcData(buffer.array());
            } catch (IOException e) {
                log.error("回复hdc握手信息并同时指定设备时发生IO错误", e);
                close();
                return false;
            }
        }

        try {
            //等待一会，因为hdc server端异步处理session状态
            Thread.sleep(HANDSHAKE_DELAY_MS);
        } catch (InterruptedException e) {
            return false;
        }

        return true;
    }

    public String readLine() {
        String result = null;
        try {
            result = new String(readHdcData());
        } catch (IOException ignored) {

        }
        return result;
    }

    /**
     * 读取所有数据
     *
     * @return 读取的所有数据
     */
    public String readAllLines() {
        StringBuilder result = new StringBuilder();
        String line;
        while ((line = readLine()) != null) {
            result.append(line).append("\n");
        }
        return result.toString();
    }

    /**
     * 关闭session
     */
    @Override
    public void close() {
        closed = true;
        try {
            socket.close();
        } catch (IOException ignored) {
        }
    }

}
